当前位置: 首页 > article >正文

C++ 中的 const 和 constexpr: 深入对比与最佳实践

C++ 是一门强调性能和灵活性的编程语言, constconstexpr 是其中两个非常重要的修饰符, 用于处理常量. 然而, 许多开发者在使用时容易混淆它们的用法和适用场景. 本文将深入对比这两个关键字, 提供全面的指导, 帮助您更好地理解和使用它们.


一. 使用场景对比

我们将通过不同的使用场景对 constconstexpr 的表现进行对比:

1. 存储空间

  • const:
    • const 变量可能存储在常量数据区, 也可能存储在栈上, 这取决于变量是否有链接性以及编译器优化.
      const int runtime_const = 42; // 在某些情况下可能存储在 .rodata 区域
      int main() {
          const int local_const = 10; // 编译器可能将其存储在栈上
          return 0;
      }
      
    • 对于大多数场景, 运行期的 const 变量不会直接节省存储空间.
  • constexpr:
    • constexpr 变量在编译期求值, 其结果直接嵌入到代码中, 可以显著减少运行时的内存占用.
    • 这种优化能避免运行时的额外存储开销, 尤其在嵌入式系统中具有巨大优势.
      constexpr int compile_time_const = 42;
      int main() {
          int result = compile_time_const + 10; // 会直接将计算结果嵌入到指令中,无需分配存储空间
          return result;
      }
      
      汇编代码
      main:
          push    rbp
          mov     rbp, rsp
          mov     DWORD PTR [rbp-4], 52
          mov     eax, DWORD PTR [rbp-4]
          pop     rbp
          ret
      

2. 修饰普通变量

  • const:
    • 运行期或编译期均可定义常量.
      const int a = 42; // 可以绑定常量
      void fun(std::string& s) {
          // 可以绑定动态值, 只表明size为只读属性
          const auto size = s.size();
          for (int i = 0; i < size; i++) {
              // ...
          }
      }
      
  • constexpr:
    • 必须在编译期确定值.
      constexpr int square(int x) { return x * x; }
      constexpr int b = 42;         // OK, 常量
      constexpr int c = square(b);  // OK, 常量函数返回值
      
    • 一个自定义的类需要有 constexpr 构造函数,才可以定义 constexpr 实例.

3. 修饰指针

  • const:

    • 可以修饰指针或指针所指向的值.

      const int* ptr = nullptr;   // 指针所指的值是常量
      int* const cptr = nullptr;  // 指针本身是常量
      
      int a = 0;
      const int c = 1;
      ptr = &a;  // OK
      ptr = &c;  // OK
      
      // ptr = 3;    // Error, 不能通过常量指针修改值
      // cptr = &a;  // Error, 不能改变指针指向
      // cptr = &c;  // Error, 不能改变指针指向
      
  • constexpr:

    • 修饰的指针必须在编译期求值.

      constexpr int* np = nullptr;  // OK
      
      static int sa = 0;         // 静态变量
      constexpr int* psa = &sa;  // OK
      
      static const int ca = 0;         // 静态常量
      constexpr const int* pca = &ca;  // OK
      
      static constexpr int sb = 0;     // 静态constexpr
      constexpr const int* psb = &sb;  // OK
      
      constexpr int x = 42;
      constexpr const int* p2 = &x;  // Error
      
      const int cy = 1;
      constexpr const int* p3 = &cy;  // Error
      

4. 修饰引用

  • const:

    • 可以修饰引用, 使其引用的值不可更改.

      int x = 0;
      
      int& ref = x;
      ref = 1;  // OK
      
      const int& cref = x;
      cref = 1;  // Error, 不可以通过常量引用改变绑定变量值
      
  • constexpr:

    • 只能绑定到静态变量.

      static int sa = 1;
      constexpr const int& rsa = sa;  // rsa 是绑定到 sa 的引用
      sa = 1;                         // OK
      rsa = 1;                        // Error
      
      static constexpr int sca = 42;   // 静态constexpr
      constexpr const int& ref = sca;  // ref 是绑定到 sca 的引用
      sca = 1;                         // Error
      ref = 1;                         // Error
      

5. 修饰函数

  • const:
    • 不能修饰函数本身.
    • 可用于修饰类成员函数, 表示不会修改类的成员变量, 后面会讲到.
  • constexpr:
    • 支持修饰函数.
    • constexpr 函数在编译期求值, 但需要满足以下限制:
      • 返回值和参数必须是常量表达式.
      • 函数体中仅允许使用常量表达式.
      constexpr int square(int x) {
          return x * x;
      }
      

6. 修饰类成员函数

  • const:

    • 表示成员函数不会修改类的成员变量.

      class MyClass {
      public:
      // 表示该函数不修改成员变量
      int size() const { return size_; }
      
      private:
      int size_ = 0;
      };
      
  • constexpr:

    • 表示成员函数可以在编译期求值.

      class MyClass {
      public:
      // 表示该函数不修改成员变量, 且可以在编译时求值
      constexpr int size() const { return size_; }
      
      private:
      int size_ = 0;
      };
      

二. constconstexpr 的历史演进

随着 C++ 标准的迭代, constexpr 的功能得到了极大增强.

C++11

  • 引入 constexpr, 允许声明编译期常量和常量表达式函数.
  • 受限于当时的标准, constexpr 函数:
    • 返回值必须是常量表达式.
    • 函数体中只能包含单一 return 语句.
    • 不支持 if, for 等复杂语句.

C++14

  • 解除了一些限制:
    • 函数体中允许包含局部变量.
    • 支持更复杂的控制流, 如 if-elsefor.

C++17

  • 引入 inline constexpr, 支持更灵活的代码组织.
  • 扩展了 constexpr 对常量表达式构造的支持.

C++20

  • 几乎解除所有限制:
    • 允许动态分配内存(通过 new).
    • 可以捕获异常.

五. 常见问题解答(FAQ)

Q: constconstexpr 可以互相替代吗?

A: 不可以. constexpr 适用于编译期计算, 而 const 主要限制值的修改.

Q: 什么时候优先使用 constexpr?

A: 当值必须在编译期确定时, 优先使用 constexpr.

Q: constexpr 的性能优势体现在什么地方?

A: 通过将运行时计算迁移到编译期, 可以减少运行时开销.


六. 总结回顾

通过本文, 我们详细比较了 constconstexpr 在存储空间, 变量修饰, 函数修饰, 指针与引用修饰, 以及类成员函数中的表现. 同时, 我们探讨了它们的历史演进和模板应用, 并列举了常量表达式的具体应用场景.

掌握这两个关键字的区别与用法, 不仅可以写出更加高效的代码, 还能避免很多潜在的错误. 希望本文对您有所帮助!


http://www.kler.cn/a/472635.html

相关文章:

  • HTML基础入门——简单网页页面
  • 电致变色和电泳技术在低功耗显示器中大放异彩
  • 一次完成Win10下MySQL 9.1 的安装
  • 算法的五个重要特性和4个基本标准
  • 医疗可视化大屏 UI 设计新风向
  • el-tree拖拽光标错位问题
  • oracle闪回恢复数据:(闪回查询,闪回表,闪回库,回收站恢复)
  • 本地导入封装的模块 在docker内报错ImportError
  • C#核心技术---Lambda表达式
  • SSM-SpringMVC-请求响应、REST、JSON
  • 基于 Nuxt3 + Obsidian 搭建个人博客
  • Synthesia技术浅析(四):自然语言处理
  • 深度学习J8周 Inception v1算法实战与解析
  • (leetcode算法题)2271. 毯子覆盖的最多白色砖块数
  • C++ 复习总结记录三
  • minibatch时,损失如何记录
  • 机器学习之随机森林算法实现和特征重要性排名可视化
  • Three.js 12中利用着色器进行材质加工深度解析
  • Backend - C# asp .net core MVC
  • 制造业该怎么做数据治理?