文章目录

C++ constexpr和const的区别:编译期常量与运行时常量

发布于 2026-04-25 02:19:25 · 浏览 12 次 · 评论 0 条

C++ constexpr和const的区别:编译期常量与运行时常量

在C++编程中,constconstexpr 都用于定义“不可变”的量,但它们在初始化时机、编译器处理方式以及应用场景上存在本质区别。掌握这两者的差异,是编写高效C++代码的关键。


1. 理解 const:运行期与编译期的双重性

const 关键字的核心含义是“只读”。它告诉编译器,这个变量在初始化后不能被修改。然而,const 并不强制要求值必须在编译期确定,它既可以是编译期常量,也可以是运行期常量。

定义一个只读变量 x,其值由运行时输入决定:

#include <iostream>

int main() {
    int input;
    std::cin >> input; // 运行时获取输入
    const int x = input; // x 是运行时常量,只读,但值在运行时才确定
    // x = 10; // 错误:不能修改 const 变量
    std::cout << x << std::endl;
    return 0;
}

分析上述代码:

  1. xconst 修饰,因此程序尝试修改 x(如 x = 10)会导致编译错误。
  2. x 的值依赖于用户输入 input,这意味着编译器在编译阶段无法知道 x 的具体数值。
  3. 因此,x 占用内存,且必须在程序运行时才能被初始化。

注意:如果用常量表达式初始化 const 变量(如 const int y = 10;),编译器通常会将其优化为编译期常量,但这并不是 const 的强制要求,而是编译器的优化行为。


2. 理解 constexpr:强制编译期计算

constexpr (constant expression) 的核心含义是“编译期常量”。它强制规定变量或函数的值必须在编译期间计算出来。如果传入的参数或初始值无法在编译期确定,编译器将直接报错。

定义一个编译期常量 size 并用于数组定义:

#include <iostream>

int main() {
    constexpr int size = 10; // 强制在编译期确定值为 10
    int arr[size]; // 合法:size 是编译期常量,可以作为数组大小

    int val = 20;
    // constexpr int runtime_size = val; // 错误:val 是运行时变量,无法在编译期确定

    std::cout << arr[0] << std::endl;
    return 0;
}

分析上述代码:

  1. sizeconstexpr 修饰,编译器必须在编译阶段计算出 size 的值。
  2. 因为 size 是编译期确定的,它可以用作数组维度 arr[size]
  3. 如果尝试将运行时变量 val 赋值给 constexpr 变量 runtime_size,编译会失败,因为这违反了“编译期确定”的原则。

3. 核心差异对比

为了更直观地展示两者的区别,请参考下表。

特性 const constexpr
核心语义 只读 编译期常量
初始化时机 编译期或运行期 必须是编译期
修饰函数 仅修饰成员函数,表示不修改对象状态 修饰普通/成员函数,表示可能用于编译期计算
用途限制 不能用于需要编译期常量的场景(如非静态数组大小) 可用于任何需要常量表达式的场景(如模板参数、数组大小)
灵活性 高,兼容运行期数据 严,仅限编译期可知的数据

4. 修饰函数的区别与实战

constconstexpr 在修饰函数时表现截然不同。

使用 const 修饰成员函数

在类成员函数后加 const,表示该函数不会修改对象的任何非 mutable 成员变量。这是一种运行时的契约,主要用于代码安全检查。

class MyClass {
public:
    int value;

    // const 成员函数
    int getValue() const {
        // value = 10; // 错误:const 函数不能修改成员变量
        return value;
    }
};

使用 constexpr 修饰函数

constexpr 修饰函数时,表示该函数在参数是常量表达式的情况下,允许在编译期计算出结果。这意味着该函数既能在编译期运行,也能在运行期运行。

编写一个计算平方的 constexpr 函数:

#include <iostream>

// constexpr 函数
constexpr int square(int x) {
    return x * x;
}

int main() {
    // 场景 1:编译期计算
    // 编译器直接计算出 square(5) = 25,arr 的大小被确定为 25
    int arr[square(5)]; 

    // 场景 2:运行期计算
    int y = 10;
    int result = square(y); // y 是变量,函数在运行时执行

    std::cout << "Array size: " << sizeof(arr)/sizeof(int) << std::endl;
    std::cout << "Result: " << result << std::endl;
    return 0;
}

分析

  1. 当传入字面量 5 时,编译器在编译期调用 square(5),计算出结果 25 用于定义数组。
  2. 当传入变量 y 时,编译器生成运行时代码,程序运行时计算 square(y)

5. 修饰指针的特殊规则

当涉及指针时,两者的行为容易混淆,需要特别注意“顶层 const”和“底层 const”的概念。

const 修饰指针

const 的位置决定了它是指针本身不可变,还是指向的内容不可变。

  • const int* p:底层 const,指针指向的内容不可改。
  • int* const p:顶层 const,指针本身的指向不可改。

观察以下代码:

int a = 10;
int b = 20;

// 底层 const:*p 不可改,p 可以改
const int* p1 = &a; 
// *p1 = 15; // 错误
p1 = &b;     // 正确

// 顶层 const:p2 不可改,*p2 可以改
int* const p2 = &a;
*p2 = 15;    // 正确
// p2 = &b; // 错误

constexpr 修饰指针

constexpr 修饰指针时,它隐含的意义是“顶层 const”。即,指针本身的值(地址)必须在编译期确定且不可变,但指向的内容是可以修改的。

演示 constexpr 指针的用法:

// 全局变量,存放在静态存储区,地址在编译期可知
int global_val = 100;

int main() {
    // constexpr 指针 p 指向 global_val
    // p 的地址固定(顶层 const),但可以通过 p 修改 global_val 的值
    constexpr int* p = &global_val; 

    *p = 200; // 正确:修改指向的内容
    // p = nullptr; // 错误:p 本身是 constexpr,不可修改指向

    std::cout << global_val << std::endl; // 输出 200
    return 0;
}

注意constexpr 指针只能指向全局或静态存储区的变量,因为这些变量的地址在链接阶段是固定的。局部变量的地址在运行时才在栈上分配,因此不能用于初始化 constexpr 指针。


6. 进阶应用:编译期优化

利用 constexpr,我们可以将复杂的计算从运行期转移到编译期,从而显著提升程序运行时的性能。

定义一个阶乘计算函数,并在编译期完成计算:

#include <iostream>

// constexpr 递归函数
constexpr int factorial(int n) {
    return (n <= 1) ? 1 : (n * factorial(n - 1));
}

int main() {
    // 编译器在编译阶段就算出了 factorial(5) = 120
    // 程序运行时,直接使用 120,无需任何计算开销
    constexpr int result = factorial(5); 

    std::cout << "Factorial of 5 is: " << result << std::endl;
    return 0;
}

在上述示例中,程序编译完成后,机器码中已经直接包含了数值 120。相比之下,如果使用普通函数,程序每次运行到这里都需要执行乘法和递归操作。


7. 常见误区与修正

误区:所有 const 变量都会被优化掉,不需要用 constexpr
修正const 变量的值如果是运行时确定的,编译器绝对无法在编译期优化它。如果需要确保变量用于数组大小、模板参数或非类型模板参数,必须使用 constexpr

误区constexpr 函数只能在编译期运行。
修正constexpr 函数是“双重身份”。如果参数是常量,它在编译期运行;如果参数是变量,它退化为普通函数在运行期运行。

误区constexpr 修饰的成员函数不能修改对象成员。
修正:这是 const 成员函数的特性。constexpr 修饰成员函数时,如果不加 const,理论上可以修改成员(只要逻辑允许),但为了在编译期调用,通常不涉及修改对象状态或依赖于外部可变状态。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文