C++從0開始:構造函數與對象初始化

從對象說起:爲什麼需要構造函數?

想象一下,我們定義了一個“學生”類,這個類描述了學生的基本信息,比如姓名、年齡。當我們要創建一個學生對象時,直接給這些信息賦值會很麻煩,而且容易出錯。這時候,構造函數就像一個“自動初始化器”,在對象創建時自動幫我們完成成員變量的初始化工作,讓對象一“出生”就有合理的狀態。

什麼是構造函數?

構造函數是一種特殊的成員函數,它的作用是在對象創建時初始化成員變量。它有幾個關鍵特點:
- 名稱必須與類名完全相同(大小寫敏感);
- 沒有返回類型(包括 void),也不能寫 return 語句;
- 對象創建時自動調用,不需要手動調用。

例子:定義一個帶構造函數的類

假設我們要創建一個 Student 類,包含姓名(name)和年齡(age)兩個成員變量,並用構造函數初始化它們:

#include <iostream>
#include <string>
using namespace std;

// 定義學生類
class Student {
public:
    // 成員變量:學生姓名和年齡
    string name;
    int age;

    // 構造函數:參數爲姓名和年齡,用於初始化成員變量
    Student(string n, int a) {
        name = n;  // 構造函數體內賦值
        age = a;
    }
};

int main() {
    // 創建Student對象時,自動調用構造函數
    Student s("小明", 18);
    cout << "學生姓名:" << s.name << ",年齡:" << s.age << endl;
    return 0;
}

輸出結果:

學生姓名:小明,年齡:18

關鍵點:當執行 Student s("小明", 18); 時,構造函數會被自動調用,nameage 被分別賦值爲 “小明” 和 18。

默認構造函數:讓對象“無參出生”

如果我們沒有定義任何構造函數,編譯器會自動生成一個默認構造函數(空體,沒有參數)。但如果我們定義了帶參數的構造函數,編譯器就不會再自動生成默認構造函數了。

兩種默認構造函數的情況:

  1. 無參數的構造函數
   class Student {
   public:
       string name;
       int age;

       // 無參數的默認構造函數
       Student() {
           name = "未知";  // 給成員變量賦默認值
           age = 0;
       }
   };

   int main() {
       Student s;  // 無參數創建對象,調用默認構造函數
       cout << "默認姓名:" << s.name << ",默認年齡:" << s.age << endl;
       return 0;
   }

輸出:

   默認姓名:未知,默認年齡:0
  1. 帶默認參數的構造函數
    如果構造函數的參數都有默認值,編譯器也會將其視爲默認構造函數,此時即使不傳遞參數也能創建對象:
   class Student {
   public:
       string name;
       int age;

       // 帶默認參數的構造函數(相當於默認構造函數)
       Student(string n = "未知", int a = 0) {
           name = n;
           age = a;
       }
   };

   int main() {
       Student s1;       // 調用 Student("未知", 0)
       Student s2("小紅");// 調用 Student("小紅", 0)
       Student s3("小剛", 20); // 調用 Student("小剛", 20)
       return 0;
   }

初始化列表:更高效的初始化方式

除了在構造函數體內賦值,還可以用初始化列表直接初始化成員變量。這通常更高效,尤其是對於自定義類型(如字符串、對象),避免了“先默認構造再賦值”的過程。

對比:構造函數體內賦值 vs 初始化列表

// 1. 構造函數體內賦值
class Person {
public:
    string name;
    int age;

    Person(string n, int a) {
        name = n;  // 先默認構造string,再賦值
        age = a;
    }
};

// 2. 初始化列表(更高效)
class Person {
public:
    string name;
    int age;

    Person(string n, int a) : name(n), age(a) {  // 直接初始化
        // 這裏可以寫其他代碼,但成員變量已初始化
    }
};

常見錯誤與注意事項

  1. 構造函數不能有返回類型
   class A {
   public:
       A() { return; }  // 錯誤!構造函數不能有返回類型
   };
  1. const 成員變量必須用初始化列表
    如果類中有 const 成員變量(不可修改),必須在初始化列表中初始化,不能在構造函數體內賦值:
   class Test {
   public:
       const int x;  // const成員變量
       Test(int val) : x(val) {}  // 正確:用初始化列表
       // Test(int val) { x = val; }  // 錯誤!const變量不能賦值
   };
  1. 初始化列表的順序不影響成員變量的聲明順序
    成員變量的初始化順序由它們在類中的聲明順序決定,而非列表中的順序:
   class Order {
   public:
       int a;
       int b;
       Order(int x, int y) : b(y), a(x) {  // 雖然列表中先寫b再寫a
           cout << "a=" << a << ", b=" << b << endl;  // 輸出 a=x, b=y
       }
   };

總結

構造函數是 C++ 中讓對象“出生”時初始化成員變量的關鍵工具。核心要點:
- 構造函數名稱與類名相同,無返回類型;
- 默認構造函數確保對象有合理初始狀態(無參數或參數帶默認值);
- 初始化列表是高效初始化成員變量的方式;
- 注意避免常見錯誤(如構造函數返回類型、const 成員變量初始化)。

通過構造函數,我們可以確保每個對象在創建時都有明確的初始狀態,避免成員變量使用隨機值,讓代碼更安全、更易維護。

小夜