跳转至

# 常量

C++ 定义了一套完整的只读量定义方法，被 `const` 修饰的变量都是只读量，编译器会在编译期进行冲突检查，避免对只读量的修改，同时可能会执行一些优化．

在通常情况下，应该尽可能使用 `const` 修饰变量、参数，提高代码健壮性．

## `const` 类型限定符

### 常量

const 修饰的变量在初始化后不可改变值

    1
    2
    3
    const int a = 0;  // a 的类型为 const int

    // a = 1; // 不能修改常量

### 常量引用、常量指针

常量引用和常量指针均限制了对指向的值的修改

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    int a = 0;
    const int b = 0;

    int *p1 = &a;
    *p1 = 1;
    const int *p2 = &a;
    // *p2 = 2; // 不能通过常量指针修改变量
    // int *p3 = &b; // 不能用 int* 指向 const int 变量
    const int *p4 = &b;

    int &r1 = a;
    r1 = 1;
    const int &r2 = a;
    // r2 = 2; // 不能通过常量引用修改变量
    // int &p3 = b; // 不能用 int& 引用 const int变量
    const int &r4 = b;

另外需要区分开的是常量指针（`const t*`）和指针常量（`t* const`），例如下列声明

    1
    2
    3
    4
    5
    6
    7
    8
    int* const p1;  // 指针常量，初始化后指向地址不可改，可更改指向的值
    const int* p2;  // 常量指针，解引用的值不可改，可指向其他 int 变量
    const int* const p3;  // 常量指针常量，值不可改，指向地址不可改

    // 使用别名能更好提高可读性
    using const_int = const int;
    using ptr_to_const_int = const_int*;
    using const_ptr_to_const_int = const ptr_to_const_int;

在函数参数里使用 `const` 限定参数类型，可以避免变量被错误地修改，同时增加代码可读性

    1
    2
    3
    4
    void sum(const std::vector<int> &data, int &total) {
      for (auto iter = data.begin(); iter != data.end(); ++iter)
        total += *iter;  // iter 是迭代器，解引用后的类型是 const int
    }

## `const` 成员函数

类型中 `const` 限定的成员函数，可以用来限制对成员的修改．

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    #include <iostream>

    struct ConstMember {
      int s = 0;

      void func() { std::cout << "General Function" << std::endl; }

      void constFunc1() const { std::cout << "Const Function 1" << std::endl; }

      void constFunc2(int ss) const {
        // func(); // const 成员函数不能调用非 const 成员函数
        constFunc1();

        // s = ss; // const 成员函数不能修改成员变量
      }
    };

    int main() {
      int b = 1;
      ConstMember c{};
      const ConstMember d = c;
      // d.func(); // 常量不能调用非 const 成员函数
      d.constFunc2(b);
      return 0;
    }

## 常量表达式 `constexpr`（C++11）

常量表达式是指编译时能计算出结果的表达式，`constexpr` 则要求编译器能在编译时求得函数或变量的值．

编译时计算能允许更好的优化，比如将结果硬编码到汇编中，消除运行时计算开销．与 `const` 的带来的优化不同，当 `constexpr` 修饰的变量满足常量表达式的条件，就强制要求编译器在编译时计算出结果而非运行时．

更直观的理解是把 `const` 理解成「只读」，`constexpr` 理解成「不可变」

    1
    2
    3
    4
    5
    6
    7
    8
    constexpr int a = 10;  // 直接定义常量

    constexpr int FivePlus(int x) { return 5 + x; }

    void test(const int x) {
      std::array<int, x> c1;            // 错误，x在编译时不可知
      std::array<int, FivePlus(6)> c2;  // 可行，FivePlus编译时可知
    }

以下例子很好说明了 `const` 和 `constexpr` 的区别，代码使用递归实现计算斐波那契数列，并用控制流输出．

实现

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    #include <iostream>

    using namespace std;

    constexpr unsigned fib0(unsigned n) {
      return n <= 1 ? 1 : (fib0(n - 1) + fib0(n - 2));
    }

    unsigned fib1(unsigned n) { return n <= 1 ? 1 : (fib1(n - 1) + fib1(n - 2)); }

    int main() {
      constexpr auto v0 = fib0(9);
      const auto v1 = fib1(9);

      cout << v0;
      cout << ' ';
      cout << v1;
    }

编译后的可能的汇编代码（使用 Compiler Explorer，Clang 19）

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    fib1(unsigned int):
            push    r14
            push    rbx
            push    rax
            mov     ebx, 1
            cmp     edi, 2
            jb      .LBB0_4
            mov     r14d, edi
            xor     ebx, ebx
    .LBB0_2:
            lea     edi, [r14 - 1]
            call    fib1(unsigned int)
            add     r14d, -2
            add     ebx, eax
            cmp     r14d, 1
            ja      .LBB0_2
            inc     ebx
    .LBB0_4:
            mov     eax, ebx
            add     rsp, 8
            pop     rbx
            pop     r14
            ret

    main:
            push    r14
            push    rbx
            push    rax
            mov     edi, 9
            call    fib1(unsigned int) # `v1` 的初始化进行了函数调用
            mov     ebx, eax
            mov     r14, qword ptr [rip + std::__1::cout@GOTPCREL]
            mov     rdi, r14
            mov     esi, 55 # `v0` 被最终计算结果替代
            call    std::__1::basic_ostream<char, std::__1::char_traits<char>>::operator<<(unsigned int)@PLT
            mov     byte ptr [rsp + 7], 32
            lea     rsi, [rsp + 7]
            mov     edx, 1
            mov     rdi, r14
            call    std::__1::basic_ostream<char, std::__1::char_traits<char>>& std::__1::__put_character_sequence[abi:ne200000]<char, std::__1::char_traits<char>>(std::__1::basic_ostream<char, std::__1::char_traits<char>>&, char const*, unsigned long)
            mov     rdi, r14
            mov     esi, ebx # 读取了变量值
            call    std::__1::basic_ostream<char, std::__1::char_traits<char>>::operator<<(unsigned int)@PLT
            xor     eax, eax
            add     rsp, 8
            pop     rbx
            pop     r14
            ret

`constexpr` 修饰的 `fib0` 函数在唯一的调用处用了常量参数，使得整个函数仅在编译期运行．由于函数没有运行时执行，编译器也就判断不需要生成汇编代码．

在同时注意到汇编中，`v0` 没有初始化代码，在调用 `cout` 输出 `v0` 的代码中，`v0` 已被最终结算结果替代，说明变量值已在编译时求出，优化掉了运行时运算． 而 `v1` 的初始化还是普通的 `fib1` 递归调用．

所以 `constexpr` 可以用来替换宏定义的常量，规避 宏定义的风险．

算法题中可以使用 `constexpr` 存储数据规模较小的变量，以消除对应的运行时计算开销．尤为常见在「打表」技巧中，使用 `constexpr` 修饰的数组等容器存储答案．

编译时计算量过大会导致编译错误

编译器会限制编译时计算的开销，如果计算量过大会导致无法通过编译，应该考虑使用 `const`．

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    #include <iostream>

    using namespace std;

    constexpr unsigned long long fib(unsigned long long i) {
      return i <= 2 ? i : fib(i - 2) + fib(i - 1);
    }

    int main() {
      // constexpr auto v = fib(32); evaluation exceeded maximum depth
      const auto v = fib(32);
      cout << v;
      return 0;
    }

使用 constexpr 时 Clang 给出的编译错误

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    <source>:10:20: error: constexpr variable 'v' must be initialized by a constant expression
        10 |     constexpr auto v = fib(32);
        |                    ^   ~~~~~~~~~~~~
    <source>:6:25: note: constexpr evaluation exceeded maximum depth of 512 calls
        6 |     return i <= 2 ? i : fib(i - 2) + fib(i - 1);
        |                         ^
    <source>:6:25: note: in call to 'fib(32)'
        6 |     return i <= 2 ? i : fib(i - 2) + fib(i - 1);
        |                         ^~~~~~~~~~
    <source>:6:25: note: in call to ...

## 参考资料

- C++ 关键字——const
- C++ 关键字——constexpr

> 本页面最近更新：2026/1/7 08:56:54，更新历史
> 发现错误？想一起完善？ 在 GitHub 上编辑此页！
> 本页面贡献者：cmpute, Tiphereth-A, Enter-tainer, ouuan, billchenchina, c-forrest, c0nstexpr, CCXXXI, CoelacanthusHex, gi-b716, HeRaNO, mgt, SakamotoKurome, StudyingFather, typed-sigterm, Xeonacid
> 本页面的全部内容在 CC BY-SA 4.0 和 SATA 协议之条款下提供，附加条款亦可能应用