const关键字

0 阅读4分钟

const的核心本质只有两个字:约束。它的逻辑体系可以完美地按照“约束的对象是谁”来划分为三个层次:

面向过程:修饰变量与指针

核心逻辑是:保护这份数据不被改写。

  1. const修饰基础变量

把变量变为了常量,const int a = 10;之后a不能被修改。

另外一个冷知识:在全局作用域下,const变量默认自带static属性即内部链接属性。

之前说过普通全局变量是外部链接的,多个文件定义会冲突,必须加static才能限制在文件内。但如果你在.h中写const int MAX_VAL = 100;,并把它#include到多个.cpp中,链接器不会报错。

背后的逻辑:编译器默认给全局const加上了类似static的属性。因为const主要是为了替代宏定义(#define),如果它不是内部链接的,每定义一个常量都需要去写个.cpp来初始化,那程序员会疯掉的。

  1. const修饰指针与引用

常见两种:

const int *pint * const p,很好区分,前者是锁住内容,内容不可变,但指针可以指向别处;后者是锁住自身,指针指向固定,但内容可以改。

函数传参时经常使用前者以及const的引用,保证函数内部不会偷偷修改实参的值。


面向对象:修饰类成员

核心逻辑是:保证对象的状态(成员变量)不被破坏。

  1. const修饰成员变量

逻辑:每个对象都有这个常量。

关键点:必须在初始化列表中初始化,因为一旦构造函数体开始执行,它就被视为“已经存在”了,此时再赋值就晚了。

  1. const修饰成员函数

本质:其实是把隐式的this指针从T* const升级为了const T* const

权限

  • const对象只能调用const成员函数(因为权限不能放大,如果没有const成员函数就会报错)
  • 普通对象既可以调用普通函数,也可以调用const函数(权限可以缩小,当只存在const时会去调用const函数,都存在时会调用普通成员函数,其实是构成了函数重载)

逃逸逻辑(mutable) :如果某个变量(如缓存、计时器)逻辑上不属于“状态”,即使在const函数里也想改,就给它加mutable

class DataProvider {
public:
    // 逻辑:这是一个 Getter,逻辑上它不应该修改对象
    // 所以必须声明为 const,这样 const 对象也能调用它
    int getData() const {
        if (cacheValid) {
            return cachedValue; // 直接返回缓存
        }

        // 逻辑冲突点:
        // getData 是 const 的,但由于我们要更新缓存,必须修改变量。
        // 如果没有 mutable,下面这两行会直接编译报错。
        cachedValue = doExpensiveComputation(); 
        cacheValid = true;

        return cachedValue;
    }

private:
    int doExpensiveComputation() const { return 42; }

    // 关键点:用 mutable 修饰
    mutable int  cachedValue = 0;     // 缓存值
    mutable bool cacheValid  = false; // 缓存是否有效
};

另一个经典场景:多线程锁(mutex)

#include <mutex>

class Counter {
public:
    int getCount() const {
        std::lock_guard<std::mutex> lock(mtx); // 如果 mtx 不是 mutable,这里报错
        return count;
    }

private:
    int count = 0;
    mutable std::mutex mtx; // mutex 几乎总是标记为 mutable
};

类成员中的“黄金搭档”:static const

当这两个关键字在类成员里碰头时,它们共同完成了一个逻辑目标:定义一个真正的“类常量”。

  • static的贡献:保证这份数据在内存中只有一份,不随对象重复创建。
  • const的贡献:保证这份数据是只读的,谁也别想改。
class Circle {
    static const double PI = 3.1415926;
};

这是C++中定义“类内常量”的标准方式。正如之前在static部分讨论的,只有static const的整型/枚举类型才允许在类内直接赋初值。

另外,编译器在处理static const这种组合时,会进行非常激进的优化。如果编译器发现一个变量又是静态的(地址固定)又是常量的(值固定),它往往根本不会为这个变量分配物理内存,而是直接把它的值“硬编码”到调用它的指令中(类似于宏替换,但带有类型检查)。