接續上篇,本文深入剖析 C++ 宣告與定義的本質差異、標頭檔最佳實踐、以及廣被誤解的「封裝」真實含義。
編譯器維護**映射表**:每個實體(變數/函數)佔三欄 —— **型別、名字、地址**(成員變數存偏移量)。
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>:優先搜尋編譯器包含路徑。標準做法:
extern 變數)。若函數定義寫在 .h → 多檔 include 產生重複符號 → 連結錯誤。C++ 規定**類別內直接定義的函數隱含 inline**,允許重複定義(下篇詳述)。
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);
};
教材稱此為「封裝:隱藏內存佈局」。**荒謬之處:**
#define private public、強制轉型、或單獨定義相容結構體繞過 private。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 解除綁定但物件存活。Tiger::Eat(Rabbit&)(老虎功能)vs RabbitWrapper::BeEaten()(兔子包裝類封裝被吃操作)。兩者語義不同、層次不同,**可共存**,視領域模型而定。
理解封裝本質,避免 Getter/Setter 濫用,才能設計出高內聚、低耦合、語義清晰的 C++ 類別體系。