文章

const关键字

const修饰变量表示不可修改,保护数据安全,支持常量引用和常量成员函数。

const关键字

const关键字

1. 常量变量

const 用于普通变量时,它表示该变量的值在初始化后不能被修改。

1
2
const int x = 10;
x = 20; // 错误,不能修改常量变量

2. 常量成员函数

const 用于成员函数时,表示该函数不会修改对象的状态。通常用于 getter 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class MyClass {
public:
    int value;
    MyClass(int v) : value(v) {}

    int getValue() const {  // const 成员函数
        return value;
    }

    void setValue(int v) {
        value = v;
    }
};

int main() {
    const MyClass obj(10);  // obj 是常量对象
    int val = obj.getValue();  // 合法,可以调用 const 成员函数
    obj.setValue(20);  // 错误,不能调用非 const 成员函数
}

3. 常量数组

1
2
const int arr[3] = {1, 2, 3};
arr[0] = 4;  // 错误,不能修改数组内容

4.修饰函数参数

值传递 + const:一般没意义(参数是副本)

1
void f(const int x); // x 是副本,写 const 不影响调用方

引用传递 + const:推荐,可防止误修改,且避免拷贝开销

1
void print(const std::string& s); // 最常见的用法

5. 修饰返回值

适用于不希望调用者修改返回结果的场景。

1
const int& getValue(); // 返回只读引用(防止修改返回值)

6. 常量指针

1
int* const p = &x;
  • p 是一个常量指针指针地址不能变,但可以修改指向的值
  • 换句话说,不能让 p 指向别的地方,但可以做 *p = xx

示例:

1
2
3
4
5
int a = 10, b = 20;
int* const p = &a;  // 指针是常量,不能再指向别的变量

*p = 30;     // ✅ 可以改 a 的值
p = &b;      // ❌ 错误,p 是 const,不能修改地址

对比:

声明指针本身可变?指向值可变?含义
const int* p;✅ 是❌ 否指向常量的指针(pointer to const)
int* const p;❌ 否✅ 是常量指针(const pointer)
const int* const p;❌ 否❌ 否指向常量的常量指针(const pointer to const),p 是一个常量指针,指向一个常量 int。

快速记忆小技巧:

const*左边:指向的内容是常量。

const*右边:指针本身是常量。

比如:

1
2
3
4
5
6
7
8
// *p 是 const,p 可变
const int* p;    
const int *p;
int const *p;

// p 是 const,*p 可变
int* const p;    
int *const p;

7. 常量引用

1
const int &ref = x;
  • refx 的一个引用,但是只读的。
  • 不能通过 ref 修改 x 的值

示例:

1
2
3
int a = 10;
const int &ref = a;
ref = 20; // ❌ 错误,ref 是常量引用,不能修改 a

常用场景:

  • 函数参数传递,防止修改,提升效率:

    1
    
    void print(const std::string& s); // 传大对象的推荐方式
    
  • 延长临时变量生命周期

    1
    
    const int& r = 1 + 2; // 绑定临时变量
    

在 C++ 中没有“指向常量的引用”这种合法说法。因为引用本身不是对象,也不是指针,它只是一个别名。

8. 顶层 const 和底层 const

顶层 const(Top-level const)

修饰变量本身,表示该变量是只读的,不能修改变量本身的值或指向

1
2
const int a = 10;       // a 是顶层 const,a 不能被修改
int* const p1 = ptr;    // p1 是顶层 const,p1 不能指向别处,但 *p1 可改,p1 是个常量指针
  • 顶层 const 修饰的是对象本身(包括指针变量本身)。

  • 在函数参数传递时,顶层 const 不会影响传参(因为是按值传递,const 会被忽略)

底层 const(Low-level const)

修饰的是通过指针或引用访问到的对象,表示该对象是只读的。

1
2
3
const int* p2 = &a;     // 底层 const,p2 可以指向别的地方,但不能通过 p2 修改 *p2 的值
int const* p3 = &a;     // 同上,const 在前在后都一样
const int& r = a;       // 引用的底层 const,r 不能修改 a 的值
  • 底层 const 修饰的是指针/引用所指向的数据

  • 在指针传递中,底层 const 体现对数据的只读保护。

  • 函数参数中如果是引用/指针类型的 const,则不会被忽略。

在函数参数中

1
2
3
4
void foo1(const int x);         // 顶层 const:传值,const 会被忽略
void foo2(const int* p);        // 底层 const:函数内不能修改 *p
void foo3(int* const p);        // 顶层 const:函数内不能修改 p 的指向
void foo4(const int* const p);  // 顶层 + 底层 const:都不能改

9. 补充

const 和宏(#define)的区别:

1
2
#define PI 3.14        // 宏替换,不是类型安全的
const double pi = 3.14; // 推荐,类型安全

const 不是 compile-time 常量(用不了在数组大小里):

除非加上 constexpr(C++11 起)

1
2
3
4
5
const int n = 10;
int arr[n]; // C++98 不允许(GCC 扩展允许),C++11 中必须:

constexpr int m = 10;
int arr2[m]; // ✅ OK

const 是运行时常量,表示在运行时赋值的变量。

1
2
3
4
5
int input;
cin >> input;
const int x = input; // ❌ 不是编译期常量
int arr[x];          // ❌ 错,数组长度必须是常量表达式(C++ 标准)
// 这里 x 虽然是 const,但它的值直到程序运行时才知道 —— 所以它是运行时只读变量,不是编译期常量。

constexpr 是编译时常量,表示在编译时必须已知的常量。在编译期就会被求值并替换进代码中 —— 如果条件允许。

1
2
3
4
5
6
7
constexpr int square(int n) {
    return n * n;
}

int arr[square(5)];  // 等价于 int arr[25];
// 编译器会在编译期间直接计算出 square(5) == 25;
// 最终生成代码时,根本没有 square() 的函数调用,直接用结果。

能被 constexpr 替换的前提:

  • 所有参数和上下文都是编译期已知的;

  • 表达式没有运行时依赖;

  • constexpr 函数的语法必须受限(不能有 newmalloc、IO、异常、虚函数等操作)。

反例:

1
2
3
4
5
6
constexpr int get_val(bool b) {
    return b ? 1 : 2;
}

int x = get_val(cin.get() == 'a');  // ❌ 运行期参数 ⇒ 不能替换
// 此时虽然 get_val() 是 constexpr,但因为参数取决于运行时输入,编译器不会在编译期替换,会按普通函数处理。
本文由作者按照 CC BY 4.0 进行授权