| 概念 | 內容 | 出現次數 | 範例 |
| 宣告 | 告知編譯器符號存在、型別、參數 | 可多次 (多檔案) | extern int x; void foo(); |
| 定義 | 分配記憶體、提供實作內容 | 全程式恰好一次 (ODR) | int x = 10; void foo() { ... } |
ODR (One Definition Rule) 違反 → 連結錯誤或未定義行為。
符號表與連結流程
- 編譯
a.cpp:遇到 foo() 調用,符號表記錄 foo 為外部符號 (未定義)。
- 編譯
b.cpp:foo() 定義於此,產生定義符號 進入目標檔。
- 連結器:掃描所有目標檔符號表,匹配外部符號與定義符號,解析地址,生成最終映像。
頭文件:聲明的標準化容器
將共用宣告集中放入 .h/.hpp,.cpp 透過 #include 引入。
// math.h
#ifndef MATH_H
#define MATH_H
double f1();
double f2(double a);
#endif
// math.cpp
#include "math.h"
double f1() { return 3.14; }
double f2(double a) { return a * a; }
// main.cpp
#include "math.h"
int main() { double x = f1(); double y = f2(x); }
頭文件防禦性設計鐵律
- 包含防衛:
#pragma once (現代通用) 或 #ifndef GUARD #define GUARD ... #endif。
- 僅含宣告: 函數宣告、類別定義、
extern 變數宣告、inline 函數定義、constexpr 變數、類別定義 (含內聯成員函數)。
- 嚴禁非內聯定義: 非
inline/constexpr 函數定義、非 const/static 變數定義 → 多檔包含導致重複定義錯誤。
- 最小依賴: 能前向宣告 (
class Foo;) 就不 #include "Foo.h";實作細節放 .cpp (PIMPL 慣用語)。
- 命名空間包裹: 避免全域污染。
三大例外:頭文件中合法的定義
| 類別 | 原因 | 範例 |
| const/constexpr 物件 | 內部連結,各編譯單元獨立實體,值相同 | constexpr int MAX = 100; |
| inline 函數 | 編譯器需見定義內聯展開,ODR 允許多重相同定義 | inline int add(int a,int b){return a+b;} |
| 類別定義 | 建構物件需知完整佈局 (大小、成員位移、vptr 位置) | class Widget { ... }; |
現代 C++ 模組 (C++20):頭文件終結者
// Math.ixx (模組介面單元)
export module Math;
export double f1() { return 3.14; }
export double f2(double a) { return a * a; }
// main.cpp
import Math;
int main() { double x = f1(); double y = f2(x); }
優點:無宏干擾、編譯加速、介面/實現物理分離、ODR 自然保證。
- 上一篇文章: C++ 面向物件核心特性全景解析:封裝、繼承、多態、友元、運算子重載與動態綁定
- 下一篇文章: C++ 頭文件設計鐵律:三大合法定義例外、包含防衛與現代模組化遷移指南
Copyright ©
程式碼之家 All Rights Reserved