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

C++ 基礎深度講解:類的完整介紹(二)——宣告定義機制、標頭檔組織與封裝本質

     編輯:C++ 入門

接續上篇,本文深入剖析 C++ 宣告與定義的本質差異、標頭檔最佳實踐、以及廣被誤解的「封裝」真實含義。

宣告 vs 定義:映射表視角

編譯器維護**映射表**:每個實體(變數/函數)佔三欄 —— **型別、名字、地址**(成員變數存偏移量)。

  • 宣告: 生成映射表項,填「型別、名字」,地址欄留空,留下符號資訊供連結器後續填補。
  • 定義: 填補地址欄。變數定義觸發記憶體分配;函數定義提供代碼位址。
  • 合併寫法: void ABC(float) {} 同時完成宣告與定義(三欄全滿)。

成員函數定義的特殊規則


struct ABC { void AB(float); };  // 類別內宣告:生成映射項 ABC::AB,地址欄空
void ABC::AB(float) {}           // 類別外定義:僅填補 ABC::AB 的地址欄

類別外定義**不可單獨存在** —— 名稱 ABC::AB 違反識別碼規則,型別修飾符 ABC:: 必須先由類別定義產生。若類別內已給定義(void AB(float) {}),外部再定義會報「重複定義」。

宣告的真實作用:告訴編譯器如何生成代碼

宣告不只是「告知存在」,而是**綁定型別與名字**,決定編譯器如何為操作生成機器碼:

  • char 乘法 vs long 乘法 → 不同指令序列。
  • long ABC(long) vs void ABC(float) → 不同呼叫慣例、參數壓棧方式。

跨檔案示例:a.cpp 宣告 extern "C" long ABC(long, long),b.cpp 定義 float ABC(float)。編譯各自通過,連結器僅匹配符號 _ABC,**型別不匹配不報錯** → 運行時災難。這揭示:宣告是編譯期代碼生成契約,連結期不檢查型別。

標頭檔:統一宣告的契約

多檔案共用型別時,必須在每檔重複類別定義 → 易出錯。#include 由預處理器將檔案內容**原封不動展開**至引用處。

  • #include "abc.h":優先搜尋來源檔目錄,再搜尋編譯器包含路徑。
  • #include <abc.h>:優先搜尋編譯器包含路徑。

標準做法:

  • .h(標頭檔): 僅放**宣告**(類別定義、函數原型、extern 變數)。
  • .cpp(來源檔): 放**定義**(函數實作、變數定義)。

若函數定義寫在 .h → 多檔 include 產生重複符號 → 連結錯誤。C++ 規定**類別內直接定義的函數隱含 inline**,允許重複定義(下篇詳述)。

封裝:被誤解的核心概念

錯誤示範:Getter/Setter 瀑布


class Person {
private: char m_Name[20]; unsigned long m_Age; bool m_Sex;
public:  const char* GetName() const; void SetName(const char*);
         unsigned long GetAge() const; void SetAge(unsigned long);
         bool GetSex() const; void SetSex(bool);
};

教材稱此為「封裝:隱藏內存佈局」。**荒謬之處:**

  1. 有門無鎖: Getter/Setter 完全暴露內部欄位,零封裝價值。
  2. 無法隱藏佈局: 編譯器生成物件代碼**必須知內存佈局**(大小、偏移)。使用者可透過 #define private public、強制轉型、或單獨定義相容結構體繞過 private。
  3. 語義錯位: Person 若為資料庫記錄緩衝區,本質是「箱子」,不應有 Get/Set;若為領域模型,行為應為 ChangeName、Promote 等業務動作,非裸露欄位存取。

真正的封裝:操作的封裝

MFC CFile 封裝「檔案操作」而非「檔案資料」:


class CFile {
    HANDLE m_hFile;  // 被封裝資源的句柄
public:
    void Open(...);   // 被動功能:被開啟
    void Close();     // 被動功能:被關閉
    void Read(...);   // 被動功能:被讀取
    void Write(...);  // 被動功能:被寫入
};

封裝定義: 將**對某資源的一組操作**集合視為一種新資源,映射為**包裝類**。包裝類物件持有被包裝資源的引用(m_hFile),提供操作介面。

  • 包裝類映射「動詞性概念」(操作),非「名詞性概念」(實體)。
  • 包裝物件獨立於被包裝資源:CFile f; 初始 m_hFile=0(無效),需 Open 綁定真實核心物件,Close 解除綁定但物件存活。
  • 優點:將多個「動詞性概念」合併為一「名詞性概念」,簡化架構,趨向面向物件。

封裝 vs 繼承:老虎吃兔子

Tiger::Eat(Rabbit&)(老虎功能)vs RabbitWrapper::BeEaten()(兔子包裝類封裝被吃操作)。兩者語義不同、層次不同,**可共存**,視領域模型而定。

理解封裝本質,避免 Getter/Setter 濫用,才能設計出高內聚、低耦合、語義清晰的 C++ 類別體系。

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