在C++里,const修饰指针时,位置不同,语义截然不同。这可不是什么“差不多”的事,编译器会据此进行完全不同的类型检查。位置写错,轻则函数调用失败,重则逻辑被绕过却毫无报错。
const int* p 和 int* const p:编译期就不是同一个类型
前者是“指向常量的指针”,后者是“常量指针”。它们在类型系统里互不兼容,不能隐式转换,也不能用于重载区分(因为顶层cv-qualifier不参与函数签名)。
const int* p:可以改变p的值(即换地址),但不能改变*p(即不能写*p = 5)。int* const p = &x:必须初始化;之后p = &y会报错,但*p = 5是合法的。const int* const p = &x:两者都禁止,是最严格的只读访问。- 一个实用的读法是从右往左:
int* const p→ “p is a const pointer to int”。
字符串字面量:强制要求const char*
像"hello"这种字面量,类型是const char[6]。现代C++(C++11起)禁止隐式转成char*。这不是警告,是硬性编译错误。
- 错误写法:
char* p = "hello";→ error: cannot convert 'const char*' to 'char*' - 正确写法:
const char* p = "hello"; - 如果非要非常量指针(极少见且危险):
char* p = const_cast,但后续写操作仍是未定义行为(UB)。("hello"); - 安全替代方案:
char p[] = "hello";(数组初始化,不涉及const转换)。
const成员函数:内部只能调用const成员函数
声明为void foo() const的函数,其this指针类型是const MyClass*。这意味着所有非mutable成员变量都变成只读,且只能调用其他const成员函数。
- 常见报错:
error: passing 'const MyClass' as 'this' argument discards qualifiers - 典型场景:
const std::vector只能调用& v v.size()、v.at(i),不能调用v.push_back(x)。 mutable是唯一例外:它允许在const函数中修改(比如缓存计算结果),但该字段不能影响对象的逻辑状态。- 返回
*this时,如果写成const MyClass&,链式调用后续将无法接非const函数。
const引用参数:不只是“防手抖”,更是契约与生命周期管理
写void f(const std::string& s),重点不在于防止你内部误改s,而在于告诉调用方:“我不会动你传来的对象”,并让编译器据此优化(如省去隐式拷贝)和延长临时对象生命周期。
- 接受临时对象:
f(std::string("tmp"))合法;而void f(std::string& s)不合法。 - 注意:
const T&延长的是绑定到它的那个临时对象的生命周期,不传递给函数内部创建的新引用。 - 一个更隐蔽的坑:如果T自身没实现const正确性(比如成员函数没加
const),哪怕参数是const T&,调用其非const成员也会失败。 - 基本类型(
int、double)传const int&没实际收益,编译器通常忽略;大对象才值得这么做。
真正难的不是记住哪边是“指针常量”,而是写出const正确的类——每个访问器是否标记const,哪些字段该加mutable,返回引用时要不要const,这些组合起来才构成完整的只读契约。漏掉一个点,整个const系统就形同虚设。