C++ const 高阶用法与二次结论
const 是 C++ 类型系统中最精密的安全机制之一。表面上它只是“不可修改”,但在不同上下文中语义分化极大,且与指针、引用、成员函数、模板、重载决议深度交织。本文提炼 const 的高阶用法,并给出可直接用于编程与面试的二次结论。
一、顶层 const 与底层 const
顶层 const 修饰对象本身不可变;底层 const 修饰所指或所引对象不可变。
int a = 10; const int b = 20; // 顶层 const int *const p1 = &a; // 顶层 const(指针本身不可变) const int *p2 = &a; // 底层 const(所指对象只读) const int *const p3 = &a; // 左侧底层 const,右侧顶层 const
二次结论 1:
-
赋值或初始化时,顶层
const可被忽略(拷贝时不保留顶层const)。 -
底层
const必须严格匹配,不能将底层const的指针/引用转换为非const。
int i = 0; const int ci = 1; int *p = &ci; // 错误:底层 const 不匹配 const int *pc = &i; // 正确:添加底层 const 允许
二、const 与函数参数:常引用的三重优势
void f(const std::string &s);
二次结论 2:
常引用参数同时实现:
-
避免拷贝(相比值传递);
-
保护实参不被修改(相比非
const引用); -
可接受右值与左值(非
const引用不能绑定右值)。
因此,除明确需要修改实参或移动语义的场景外,大对象参数应优先选用 const T&。
三、const 成员函数与重载
const 成员函数的本质:this 指针类型从 T* 变为 const T*。
class Buffer {
public:
char& operator[](size_t i) { return data[i]; } // ①
const char& operator[](size_t i) const { return data[i]; } // ②
};
二次结论 3:
-
编译器根据对象的
const性质选择重载版本:const对象调用const成员函数,非const对象优先调用非const版本。 -
如果只定义了非
const版本,const对象将无法调用该函数,导致编译错误。 -
同一功能的
const与非const版本建议成对出现,非const版本可调用const版本消除重复。
典型实现技巧(const_cast 消除重复):
const char& operator[](size_t i) const { return data[i]; }
char& operator[](size_t i) {
return const_cast<char&>(
static_cast<const Buffer&>(*this)[i]
);
}
二次结论 4:
将非 const 版本委托给 const 版本是安全的;反向则会导致未定义行为(修改原本的 const 对象)。
四、mutable:突破 const 的逻辑边界
mutable 允许在 const 成员函数中修改被修饰的成员。
class Cache {
mutable std::map<int, int> cache;
public:
int query(int key) const {
if (auto it = cache.find(key); it != cache.end())
return it->second;
// 计算并缓存
cache[key] = compute(key); // 合法
return cache[key];
}
};
二次结论 5:
-
mutable应用于“逻辑上不影响对象外部状态”的成员(如缓存、互斥锁、引用计数)。 -
const成员函数保证的是逻辑常量性,而非物理(比特)常量性。
五、const_cast 的正确使用场景与危险边界
const_cast 用于添加或移除 const/volatile 限定。仅当原始对象本身不是 const 时,移除 const 才安全。
void print(char *s); // 旧接口 const char *msg = "hello"; print(const_cast<char*>(msg)); // 未定义行为!msg 指向常量区
二次结论 6:
-
const_cast最常见的合理用途是消除const/非const成员函数的代码重复(见上节)。 -
对真正的
const对象(如const int a = 10;或字符串字面量)使用const_cast并修改,是未定义行为。
六、const 与引用绑定:临时量生命周期延长
常引用绑定右值时,临时对象的生命周期会延长至引用本身的生命周期。
std::string get(); const std::string &s = get(); // 临时对象生命周期延长
二次结论 7:
-
只有
const T&(或T&&)能延长临时对象的生命周期,非const引用不能。 -
该特性常用于避免不必要的拷贝,并在函数返回值的接收中提高效率。
七、const 与 volatile 的联合使用
const 表示“程序不应修改”,volatile 表示“硬件可能修改”。二者可以同时出现。
const volatile int *status_reg = ...;
二次结论 8:
-
const volatile修饰的对象只能读不能写(程序侧),但每次读取都会从实际地址加载(不优化缓存)。 -
常见于内存映射 I/O、信号处理共享变量。
八、const 与 constexpr
const 是“只读”,constexpr 是“编译期常量”,二者正交但可组合。
const int a = f(); // 运行时只读 constexpr int b = 42; // 编译期常量 constexpr const int c = 42; // 编译期常量且只读
二次结论 9:
-
所有
constexpr变量都隐式是const,但const不一定是编译期常量。 -
对于编译期已知的值,使用
constexpr可以保证其在编译期参与常量表达式计算。
九、const 与模板类型推导
在模板类型推导中,顶层 const 被忽略,底层 const 保留。
template<typename T> void f(T t); const int ci = 0; f(ci); // T 推导为 int(顶层 const 丢失) f(&ci); // T 推导为 const int*(底层 const 保留)
二次结论 10:
-
值传递时实参的
const消失(因为副本不需要保持原对象的只读性)。 -
引用传递可保留顶层
const:void g(T& t);g(ci)推导T为const int。 -
当需要保留
const特性时,优先使用T&&或const T&参数。
十、const 与重载决议优先级
const 影响重载决议的顺序。对于成员函数:
void f(); // 非 const void f() const; // const
-
非
const对象优先匹配非const版本,否则匹配const版本。 -
const对象只能匹配const版本。
对于非成员函数,const 限定的指针/引用参数同样参与匹配。
二次结论 11:
设计接口时,若对象状态不同需要不同行为,const 重载是最自然的表达;若行为一致,应将非 const 版本委托给 const 版本。
十一、综合防错清单
-
值传递函数不需要为参数加顶层
const(因为拷贝本身不会影响实参),在声明中写void f(const int x)无意义且会干扰重载决议。 -
函数返回
const值类型通常无意义:const int f()会禁止移动,且接收方通常拷贝一份可修改对象。 -
常量成员变量必须在初始化列表中初始化:
const int x;不能在构造函数体内赋值。 -
静态成员变量可以是非整型的
const,但需要在类外定义(除非constexpr且 inline)。 -
const_iterator与const迭代器不同:const vector<int>::iterator是迭代器本身不可变,而vector<int>::const_iterator是迭代器指向的元素不可变。
更多推荐



所有评论(0)