const關鍵字的核心概念、典型場景和易錯陷阱
一、const本質:類型系統的守護者
1. 與#define的本質差異
維度 | #define | const |
---|---|---|
編譯階段 | 預處理替換 | 編譯器類型檢查 |
作用域 | 無作用域(全局污染) | 遵循塊作用域 |
調試可見性 | 符號消失 | 保留符號信息 |
類型安全 | 無類型 | 強類型約束 |
內存分配 | 不占內存(文本替換) | 占用內存(可取地址) |
致命陷阱示例:
#define MAX_SIZE 1024
const int max_size = 1024;char buffer1[MAX_SIZE*2]; // 正確,宏在編譯前展開
char buffer2[max_size*2]; // C++11前錯誤,const變量不是編譯期常量
2. constexpr的救贖(C++11)
constexpr int max_size = 1024; // 真正的編譯期常量
char buffer[max_size*2]; // 合法
二、類中的const攻防戰
1. 成員變量:初始化列表的獨裁
class Matrix {
public:Matrix(int w) : width(w) {} // 必須通過初始化列表
private:const int width; // const成員變量mutable int cache; // 可變成員(即使const對象也可修改)
};
極端案例:
class Immortal {
public:Immortal() {} // 錯誤!未初始化const成員year
private:const int year = 2023; // C++11允許類內初始化
};
2. 成員函數:const的雙重含義
class DataPool {
public:void modify() const { // 錯誤!不能修改非mutable成員// count++; }void nonConstFunc() { // 非const函數可修改成員}
};
重載的黑暗法則:
class Logger {
public:void log() const { /* 讀操作 */ }void log() { /* 寫操作 */ }
};const Logger cl;
cl.log(); // 調用const版本
Logger l;
l.log(); // 調用非const版本
三、指針與const的糾纏
1. 聲明順序的死亡游戲
int a = 10;
const int* p1 = &a; // 指向常量的指針(底層const)
int const* p2 = &a; // 等同p1
int* const p3 = &a; // 常量指針(頂層const)
const int* const p4 = &a; // 雙const
指針常量 vs 常量指針:
- 左定值(const在*左邊):指向的值不可變
- 右定向(const在*右邊):指針本身不可變
2. 類型轉換的修羅場
const int* pci = &a;
int* pi = const_cast<int*>(pci); // 去const化(危險!)
*pci = 20; // 未定義行為(原始對象非常量時可能成功)
安全轉換法則:
- 只有原始對象本身是非const的,才能用const_cast去掉const屬性
四、函數簽名中的const暗戰
1. 參數傳遞:效率與安全的博弈
void process(const BigObject& obj); // 避免拷貝+防止修改
void dangerous(const int* ptr); // 可能被const_cast突破防御
2. 返回值修飾:所有權的宣誓
const std::string& getConfig(); // 返回只讀引用
const int* getRawData() const; // 承諾不修改數據
死亡陷阱:
const int& func() {int local = 42;return local; // 返回局部變量的引用!
}
五、高級戰場:模板與const
1. 類型推導的混沌法則
template<typename T>
void deduce(T param) {}const int ci = 10;
deduce(ci); // T推導為int(const被剝離)
deduce(&ci); // T推導為const int*
2. const與完美轉發
template<typename T>
void relay(T&& arg) {process(std::forward<T>(arg));
}relay(ci); // 轉發后保持const屬性
六、面試核彈級問題
-
如何讓const成員函數修改成員變量?
- 使用mutable修飾成員變量
- const_cast去除this指針的const屬性(危險操作)
-
const成員函數調用非const函數是否合法?
class Test { public:void foo() { }void bar() const {foo(); // 錯誤!const函數不能調用非const成員函數} };
-
為什么函數重載時const可以作為區分?
- 編譯器將const成員函數視為
void func(const T* this)
- 非const版本為
void func(T* this)
- 編譯器將const成員函數視為
總結:const的哲學
- 契約精神:對編譯器承諾數據不可變
- 防御性編程:限制意外修改,提升代碼健壯性
- 類型系統武器:與引用、模板等特性配合構建安全屏障
掌握const的每個細節,相當于拿到了C++類型系統的核按鈕——既能保證代碼安全,又能精準控制程序的每一塊內存。