constexpr 和 const 的区别

在 C++ 中,constconstexpr 都用于定义常量,但它们的用途和行为有显著区别。

简单来说const可以是运行时变量也可以是编译时变量,而constexpr必须是编译时变量

而数组的大小这种就必须是编译时变量

以下是详细对比:


1. 定义和用途

  • **const**:

    • 表示变量的值在初始化后不可修改(只读)。
    • 可以用于运行时常量或编译时常量,具体取决于初始化方式。
    • 适用场景:需要确保变量不被修改,但不一定要求编译时求值。
    • 示例:
      1
      2
      3
      const int x = 10; // 编译时常量
      int y = 5;
      const int z = y; // 运行时常量(y 的值在运行时确定)
  • **constexpr**:

    • 表示变量或函数的值或结果必须在编译时可求值。
    • 用于定义编译时常量或编译时计算的函数,确保结果在编译阶段确定。
    • 适用场景:需要编译时常量(如数组大小、模板参数)或性能敏感的编译时计算。
    • 示例:
      1
      2
      3
      constexpr int x = 10; // 编译时常量
      constexpr int square(int n) { return n * n; }
      int arr[square(5)]; // square(5) 在编译时求值为 25

2. 初始化要求

  • **const**:

    • 可以用编译时常量或运行时变量初始化。
    • 初始化后不可修改,但初始化时间可以是运行时。
    • 示例:
      1
      2
      3
      int input;
      std::cin >> input;
      const int value = input; // 运行时初始化
  • **constexpr**:

    • 必须用编译时可求值的表达式初始化。
    • 初始化必须在编译时完成,不能依赖运行时值。
    • 示例:
      1
      2
      3
      4
      constexpr int value = 10; // 合法
      int input;
      std::cin >> input;
      constexpr int error = input; // 错误:input 是运行时值

3. 作用范围

  • **const**:

    • 可以修饰变量、指针、成员函数等,强调不可修改性。
    • 作用广泛,不限于编译时场景。
    • 示例:
      1
      2
      const int* ptr = &x; // 指针指向的值不可修改
      void foo() const; // 成员函数不修改对象
  • **constexpr**:

    • 主要用于变量和函数,强调编译时求值。
    • C++11 中仅限简单函数,C++14 及以后支持更复杂逻辑(如循环、条件语句)。
    • 示例:
      1
      2
      3
      4
      constexpr int factorial(int n) {
      return n <= 1 ? 1 : n * factorial(n - 1);
      }
      constexpr int f = factorial(5); // 编译时计算 120

4. 函数中的区别

  • **const**:

    • 用于成员函数,表示函数不会修改对象状态。
    • 不涉及编译时求值。
    • 示例:
      1
      2
      3
      4
      5
      class MyClass {
      int x;
      public:
      int getX() const { return x; } // 不会修改 x
      };
  • **constexpr**:

    • 用于函数,表示函数的结果可在编译时求值(如果输入是编译时常量)。
    • 函数体必须满足编译时求值的约束(如避免运行时操作)。
    • 示例:
      1
      2
      3
      4
      constexpr int add(int a, int b) { return a + b; }
      constexpr int result = add(3, 4); // 编译时求值为 7
      int x = 3, y = 4;
      int z = add(x, y); // 运行时调用,仍然合法

5. 存储和性能

  • **const**:

    • const 变量可能占用内存(除非优化为常量折叠)。
    • 如果是运行时常量,存储在栈或堆上。
    • 示例:
      1
      const int x = std::rand(); // 运行时初始化,占用内存
  • **constexpr**:

    • constexpr 变量通常不占用运行时内存(编译器直接内联常量值)。
    • 提高性能,因为值在编译时确定。
    • 示例:
      1
      constexpr int x = 10; // 编译时常量,通常不占用内存

6. 兼容性和限制

  • **const**:

    • C++98 就存在,兼容性强。
    • 限制较少,适用于各种场景。
    • 不保证编译时求值,不能直接用于模板参数或数组大小。
  • **constexpr**:

    • C++11 引入,功能在 C++14、C++17 等版本中扩展。
    • 限制严格(如函数体需满足编译时求值规则)。
    • 可用于模板参数、数组大小等编译时场景。
    • 示例:
      1
      2
      3
      4
      constexpr int n = 5;
      int arr[n]; // 合法,n 是编译时常量
      const int m = 5;
      int arr2[m]; // 合法,但依赖编译器优化

总结

特性 const constexpr
含义 值不可修改 编译时可求值
初始化 可运行时或编译时初始化 必须编译时初始化
适用场景 运行时/编译时常量,成员函数修饰 编译时常量,编译时计算函数
函数支持 成员函数不可修改对象 编译时求值函数
内存 可能占用内存 通常不占用内存(内联)
引入版本 C++98 C++11

建议

  • 需要编译时常量或性能敏感的场景(如模板参数、数组大小),使用 constexpr
  • 需要运行时常量或只读保护,使用 const
  • 两者可结合使用,例如:
    1
    constexpr const int x = 10; // 既是编译时常量,又不可修改