别再把 const 当"只读"了:4 种用法,大部分人只懂第一种

别再把 const 当"只读"了:4 种用法,大部分人只懂第一种

提到 C/C++ 里的 const,很多人的第一反应是:"哦,只读变量嘛。"然后就没了。

但实际上 const 是 C/C++ 里语义最丰富的关键字之一,用得好能让编译器帮你抓 Bug、让接口自文档化、让多线程代码更安全。用不好,就是一堆编译错误让你怀疑人生。

这篇文章把 const 的 4 种核心用法拆开讲透,每种都配代码,看完你应该能重新审视自己写的每一行带 const 的代码。


用法一:修饰普通变量(只读变量)

这是最广为人知的用法,但很多人只停留在"不能改"这个层面。

const int max_size = 100;
max_size = 200;  // 编译错误

表面上看,这就是个"不能修改的变量"。但它真正的价值在于编译器替你守门

void process(const int threshold) {
    threshold = 0;  // 编译错误,threshold 是只读的
    // ...
}

调用者传进来的值,在函数体内不会被意外修改。这在大型项目里非常重要——你不需要通读函数实现,光看签名就知道"这个函数不会改我的参数"。

const 与 #define 的区别

很多人用 #define 定义常量,但 const 变量有类型检查:

#define MAX 100        // 无类型,预处理期替换
const int max = 100;  // 有类型,编译器检查

const 变量还能取地址、参与类型推导,在调试时也能在符号表里看到,而宏在预处理后就已经消失了。


用法二:修饰指针(左定值,右定向)

这是最多人踩坑的地方。核心口诀:const 在 * 左边,值不能改;const 在 * 右边,指针不能改。

四种组合,逐个击破

int a = 10, b = 20;

1. 指向常量的指针(pointer to const)

const int *p = &a;
*p = 30;  // 错误:不能通过 p 修改指向的值
p = &b;   // 正确:p 本身可以指向别的地址

2. 常量指针(const pointer)

int *const p = &a;
*p = 30;  // 正确:可以修改指向的值
p = &b;   // 错误:p 本身不能指向别的地址

3. 指向常量的常量指针

const int *const p = &a;
*p = 30;  // 错误
p = &b;   // 错误

4. 无 const

int *p = &a;
*p = 30;  // 正确
p = &b;   // 正确

记忆技巧

* 当作分界线:

const int *p;     // const 在 * 左边 → 指向的内容是常量
int *const p;     // const 在 * 右边 → 指针本身是常量

读代码时从右往左读:

声明读法
const int *pp is a pointer to const int
int *const pp is a const pointer to int
const int *const pp is a const pointer to const int

用法三:修饰函数参数与返回值

这是 const 在接口设计中最有价值的用法,但很多人写函数时根本想不到加。

修饰函数参数

场景:你不打算修改参数,就加上 const

void print_string(const char *str) {
    // 函数体内不能修改 str 指向的内容
    printf("%s\n", str);
}

调用者看到这个签名,立刻知道:"传进去的字符串不会被改。"这消除了调用者的心理负担。

对比不加 const 的版本:

void print_string(char *str);  // 调用者会想:你到底改不改我的字符串?

修饰引用参数(C++ 特有)

void process(const std::string &s) {
    // s 不能被修改,同时避免了拷贝
}

这是 C++ 里最高频的 const 用法之一。不加 const 的引用参数,调用者会担心函数内部修改了传入的对象。

修饰返回值

返回 const 值:

const int getValue() {
    return 10;
}

这个用法比较少见,主要是防止对返回值做意外修改:

getValue() = 20;  // 编译错误

返回 const 指针/引用(重要):

const char* get_name() {
    static char name[] = "Alice";
    return name;
}

调用者拿到的是 const char*,不能修改内部字符串。这在库开发中非常重要——你不想让调用者修改你内部维护的数据。

char* get_name() {
    static char name[] = "Alice";
    return name;
}
// 调用者可以:get_name()[0] = 'B';  // 改了内部数据!危险!

用法四:修饰成员函数(C++ 特有)

这是 C++ 中 const 最独特、最强大的用法,也是区分 C 和 C++ 的关键特征之一。

基本用法

class Person {
    std::string name;
    int age;
public:
    std::string get_name() const {
        return name;  // 不能修改成员变量
    }

    void set_name(const std::string &n) {
        name = n;  // 可以修改成员变量
    }
};

const 放在成员函数末尾,表示这个函数不会修改对象的状态(mutable 成员除外)。

为什么这很重要?

1. const 对象只能调用 const 成员函数

const Person p;
p.get_name();  // 正确:get_name() 是 const 的
p.set_name("Bob");  // 错误:set_name() 不是 const 的

2. const 重载

class Buffer {
    char data[1024];
public:
    char& operator[](size_t i) {
        return data[i];
    }

    const char& operator[](size_t i) const {
        return data[i];
    }
};
Buffer buf;
const Buffer cbuf;

buf[0] = 'A';      // 调用非 const 版本,可以修改
char c = cbuf[0];  // 调用 const 版本,只读

这是标准库容器(如 std::vector)的工作方式。

3. 编译器强制保证

class Counter {
    int count;
public:
    int get() const {
        count++;  // 编译错误!const 成员函数不能修改成员变量
        return count;
    }
};

编译器帮你保证:只要函数声明为 const,函数体内任何修改成员变量的行为都会被拦截。

mutable 关键字

如果你确实需要在 const 成员函数中修改某个成员(比如缓存、互斥锁),用 mutable

class Cache {
    mutable std::mutex mtx;  // 锁可以被 const 函数修改
    mutable bool dirty;
public:
    void invalidate() const {
        std::lock_guard<std::mutex> lock(mtx);
        dirty = true;  // 合法
    }
};

进阶:const 与类型转换

const_cast(C++)

const int a = 10;
int *p = const_cast<int*>(&a);
*p = 20;  // 未定义行为!a 原本是 const 变量

规则:不要用 const_cast 去掉指向 const 变量的指针的 const 属性去修改它。 ​ 这是未定义行为。

const_cast 的正确用途是:去掉指向非 const 变量的 const 指针的 const 属性(比如调用一个不修改参数但没声明 const 的老接口)。

C 风格转换

const int *p = &a;
int *q = (int *)p;  // C 风格,能编译,但同样危险

总结

用法位置核心语义
修饰变量const int x变量不可修改
修饰指针const int *p / int *const p区分"指向的内容"和"指针本身"不可改
修饰函数参数/返回值void f(const T&) / const T f()接口契约,告诉调用者"我不会改你的数据"
修饰成员函数int get() const函数不修改对象状态,const 对象可调用

一句话总结:

const 不只是"只读",它是编译期的契约系统。它让接口自文档化,让编译器成为你的代码审查员,让并发代码更安全。养成"能加 const 就加 const"的习惯,你的代码质量会有一个可见的提升。

0
0
0
0
评论
未登录
暂无评论