C++ 繼承了 C 的基礎,並引入了許多強大的特性,其中 const 關鍵字是取代 C 時代 #define 宏常量的重要工具。掌握 const 的正確用法,不僅能提升程式碼的型別安全性,還能增強程式的健壯性與可維護性。本文將系統整理 const 的核心用法、常見陷阱與最佳實踐。
const 宣告的變數只能被讀取,任何嘗試修改的操作都會導致編譯錯誤:
const int i = 5;
int j = 0;
// i = j; // 錯誤:無法修改 const 變數
j = i; // 正確:可讀取 const 變數的值
const 變數在宣告時必須立即初始化,否則編譯失敗:
const int i = 5; // 正確
// const int j; // 錯誤:未初始化
使用 extern 可在其他連結檔案中引用 const 常量,但不可再次賦值:
extern const int i; // 正確:宣告外部常量
// extern const int j = 10; // 錯誤:常量不可重複賦值
相較於 #define 單純的文字替換,const 擁有明確的資料型別,編譯器能執行型別安全檢查:
#define I 10
const long &ref = 10; // 明確分配記憶體位址
char c1 = I; // 無警告,但隱含型別轉換風險
char c2 = ref; // 編譯警告:可能發生截斷
const 定義的常量在程式執行期間僅有一份拷貝,而 #define 每次使用都會產生立即數拷貝:
#define STR "abcdefghijklmn"
const char str[] = "abcdefghijklmn";
printf(STR); // 每次呼叫皆分配記憶體
printf(str); // 僅首次分配,後續複用同一記憶體
const 常量可由函數回傳值初始化,這對嵌入式系統(如 ROM 程式碼區)特別有用:
int getValue();
const int val = getValue(); // 一般初始化
const int &refVal = getValue(); // 參考繫結,可控制記憶體位置
透過強制型別轉換,仍可繞過編譯器限制修改 const 變數(僅供理解原理,實務嚴禁使用):
const int i = 0;
int *p = (int*)&i;
*p = 100; // 強制修改,行為未定義
掌握「const 位於星號左側修飾指向內容,右側修飾指標本身」的口訣:
int val = 0;
const int *p1 = &val; // 指向常量的指標,內容不可改
int * const p2 = &val; // 常量指標,指標本身不可改
const int * const p3 = &val; // 指標與內容皆不可改
enum 或 static constclass A {
const int size; // 僅宣告
enum { MAX_SIZE = 100 }; // 編譯期常數,可用於陣列大小
int arr[MAX_SIZE];
public:
A(int s) : size(s) {} // 初始化列表賦值
};
宣告於函數尾部,承諾不修改物件狀態:
class Stack {
int count;
public:
int getCount() const { // const 成員函數
// ++count; // 錯誤:嘗試修改資料成員
// pop(); // 錯誤:呼叫非 const 函數
return count;
}
};
const 可防止函數內部修改原始物件const(已自動複製)const T& 避免複製開銷void func(const MyClass& obj); // 高效且安全
void func(int x); // 內建型別傳值即可
const 物件/參考可防止類似 (a*b)=c 的無意圖賦值operator* 回傳新物件)const 物件,會限制呼叫端操作const Rational operator*(const Rational& lhs, const Rational& rhs) {
return Rational(lhs.num()*rhs.num(), lhs.den()*rhs.den());
}
// (a*b) = c; // 編譯錯誤:無法賦值給暫時常量物件
const 能帶來型別安全與最佳化提示const 變數賦值給非 const 指標/參考const 達成高效與保護雙重效果// 思考1:錯誤。const A* 指向常量,不可轉給 A*(會失去保護)
const A* c = new A();
A* e = c; // 編譯錯誤
// 思考2:正確。A* const 指標本身不可改,但指向內容可修改
A* const c = new A();
A* b = c; // 合法,複製指標值
// 思考3:錯誤。賦值運算子回傳 const A& 會導致 (a=b)=c 失敗
const A& operator=(const A& a); // 連續賦值會出錯