首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> 關於C語言

C++ 構造函數深度解析:從默認構造到初始化列表的完整指南

     編輯:關於C語言

前言

軟考在即,趁機複習一下 C++ 的核心難點——構造函數。構造函數不僅是創建對象的關鍵,更關係到對象初始化的正確性與效率。本文系統梳理構造函數的機制、默認構造函數的生成規則、參數化構造函數的設計技巧,以及初始化列表的最佳實踐。

什麼是構造函數?

構造函數是用來創建並初始化對象的特殊成員函數。它的名稱與類名相同,沒有返回類型(連 void 都沒有),在對象創建時自動被調用。

默認構造函數的生成規則

關鍵規則:當且僅當類中沒有聲明任何構造函數時,編譯器才會自動生成一個默認構造函數。 這個編譯器生成的默認構造函數不執行任何初始化工作,成員變量的值將是不確定的(垃圾值)。

例如:

class Example {
    // 沒有聲明任何構造函數
};
Example testA; // 調用編譯器生成的默認構造函數

當你提供了非默認構造函數時會發生什麼?

一旦你在類中聲明了任何構造函數(哪怕只是帶參數的),編譯器就**不再**自動生成默認構造函數。此時若嘗試寫 Example testA; 將導致編譯錯誤,因為沒有匹配的無參構造函數可供調用。

這體現了 C++ 的一項核心原則:禁止創建未初始化的對象。構造函數的存在正是為了確保對象出生時就處於已知、合法、合理的狀態。

如何恢復「無參創建對象」的能力?

有兩種標準方法可以讓類同時擁有參數化構造函數和默認構造函數:

方法一:為所有參數提供默認值

class Example {
public:
    Example(int a = 0, int b = 0, int c = 0) 
        : m_a(a), m_b(b), m_c(c) {}
private:
    int m_a, m_b, m_c; // 建議用 m_ 前綴區分成員與參數
};

此時 Example testA; 合法,等價於 Example testA(0, 0, 0);。

方法二:顯式定義一個無參構造函數(函數重載)

class Example {
public:
    Example() : m_a(0), m_b(0), m_c(0) {} // 初始化列表寫法
    Example(int a, int b, int c) : m_a(a), m_b(b), m_c(c) {}
private:
    int m_a, m_b, m_c;
};

兩種方法**不能同時使用**,否則會產生歧義(兩個默認構造函數),導致編譯錯誤。

初始化列表 vs. 函數體內賦值

推薦使用初始化列表:

// 推薦:初始化列表
Example() : m_a(0), m_b(0), m_c(0) {}

// 不推薦:函數體內賦值
Example() {
    m_a = 0; m_b = 0; m_c = 0;
}

初始化列表直接初始化成員,避免了「先默認構造再賦值」的額外開銷;對於 const 成員、引用成員、無默認構造函數的類類型成員,**必須**使用初始化列表。

對象聲明的幾種形式

擁有默認構造函數後,以下聲明均合法:

  • Example testA; —— 最常用,調用默認構造函數
  • Example testB = Example(); —— 顯式調用默認構造函數(可能產生臨時對象,現代編譯器會優化掉)
  • Example* ptr = new Example; —— 動態分配,調用默認構造函數

注意: Example testD(); **不是**創建對象,而是聲明一個返回 Example 對象的函數(最令人頭疼的「最令人惱人的解析」)。

避坑指南:參數名與成員名衝突

若構造函數參數與成員變量同名:

// 錯誤示範:參數遮蓋成員
Example(int a, int b, int c) { /* 無法區分 */ }

// 正確做法:成員加 m_ 前綴
Example(int a, int b, int c) : m_a(a), m_b(b), m_c(c) {}

良好的命名規範(如 m_ 前綴)能有效避免此類問題。

經典教材中的定義

《On to C++》一書總結了默認構造函數的三大特徵:

  1. 函數名與類名相同
  2. 無返回值類型
  3. 不能有參數

代碼示例:帶有多構造函數的類

const double PI = 3.14;

class TestA {
public:
    double radius, length;
    
    // 默認構造函數
    TestA() : radius(2.0), length(4.0) {}
    
    // 參數化構造函數
    TestA(double r, double l) : radius(r), length(l) {}
    
    double volume() { return PI * radius * radius * length; }
};

思考題

  1. 上述代碼有什麼問題?(提示:封裝性)
  2. private 成員在哪裡?

下一步:拷貝構造函數與匿名對象

構造函數家族還有拷貝構造函數、移動構造函數(C++11)、委派構造函數(C++11)等進階主題,將在後續筆記中詳細展開。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved