news 2026/10/1 22:31:20

C++中关键字constexpr的实现示例

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C++中关键字constexpr的实现示例

constexpr 是 C++11 引入并在后续标准(C++14/C++17/C++20)中持续增强的关键字,其核心作用是在编译期计算常量或表达式,既保证了编译期的安全性检查,又能消除运行期计算的开销,是实现“零成本抽象”和编译期元编程的关键技术。

一、constexpr 的本质与核心目标

constexpr 的字面意思是“常量表达式(constant expression)”,它的设计目标有两个核心:

  1. 编译期确定值:让变量、函数或对象的取值在编译阶段就能计算完成,避免运行时的冗余计算(如重复的数学运算、常量初始化)。
  2. 强类型安全检查:强制编译器验证“是否满足编译期计算条件”,若存在运行时依赖(如动态输入、未初始化变量),则直接报错,避免潜在的逻辑错误。

从本质上看,constexpr 是对“常量”概念的强化——它不仅要求值“不可修改”,更要求值“可在编译期确定”,这是它与传统const最核心的区别。

二、constexpr 变量:编译期初始化的常量

constexpr 最基础的用法是修饰变量,要求变量在编译期完成初始化,且后续不可修改。

1. 变量声明的核心要求

  • 必须在声明时初始化,且初始化表达式必须是常量表达式(即无运行时依赖的表达式)。
  • 变量类型必须是“字面类型(Literal Type)”——即能通过 constexpr 构造函数初始化、无虚函数/虚基类、成员均为字面类型的类型(如int、double、数组、满足条件的自定义类)。

正确示例

1

2

3

4

5

6

7

// 1. 基本类型:编译期计算 3+2 的结果(值为5)

constexprinta = 3 + 2;

// 2. 数组:大小由 constexpr 变量确定(编译期已知)

constexprsize_tarr_size = 10;

intarr[arr_size];// 合法,数组大小编译期确定

// 3. 常量表达式嵌套:依赖其他 constexpr 变量

constexprintb = a * 4;// 编译期计算 5*4=20

错误示例

1

2

3

4

5

intx = 5;

// 错误:初始化表达式依赖运行时变量 x(非常量表达式)

constexprintc = x + 3;

// 错误:未初始化(constexpr 变量必须声明时初始化)

constexprdoubled;

2. 修饰指针与引用的特殊规则

constexpr 修饰指针/引用时,需明确“修饰的是指针本身”还是“指向的对象”,规则与const类似,但要求更严格(必须编译期确定地址):

  • constexpr 指针:指针本身是编译期常量(地址固定),指向的对象是否可修改需额外用const声明。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    intnum = 10;

    // 错误:num 是栈上变量(地址运行时确定),无法作为 constexpr 指针的目标

    constexprint* p1 = #

    // 正确:全局变量地址编译期确定,p2 是 constexpr 指针(地址固定)

    intglobal_num = 20;

    constexprint* p2 = &global_num;

    // 正确:constexpr 指针 + const 对象(指针地址固定,指向的内容不可改)

    constexprconstint* p3 = &global_num;

  • constexpr 引用:引用的目标必须是编译期常量(地址固定),且引用本身不可重新绑定(与const引用一致,但要求更高)。

    1

    2

    3

    // 正确:引用目标是 constexpr 变量(编译期常量)

    constexprinte = 5;

    constexprconstint& ref = e;

三、constexpr 函数:编译期可执行的函数

constexpr 函数是支持“编译期调用”的函数——当函数的实参是常量表达式时,函数会在编译期计算结果;若实参是运行时变量,则退化为普通函数在运行期执行(即“两态性”)。

const修饰的函数(修饰函数返回值或者函数类型)不可以因为constexpr的存才而删除,起到的作用不同,constexpr修饰作用可退回普通函数,但是const修饰的函数必定是常函数。

1. 函数声明的核心要求(随标准演进放宽)

constexpr 函数的限制在 C++11 到 C++20 中逐步放宽,核心要求如下:

标准版本核心限制允许的操作
C++111. 函数体仅允许 return 语句
2. 不能有局部变量/循环
3. 返回值和参数必须是字面类型
仅常量表达式计算、return
C++141. 允许定义局部变量(需是字面类型,可初始化)
2. 允许循环(for/while)
3. 允许条件判断(if/else)
局部变量、循环、条件判断、常量计算
C++171. 允许 constexpr lambda(嵌套使用)
2. 允许使用部分标准库函数(如 std::string_view)
lambda、部分标准库调用
C++201. 允许动态内存分配(new/delete,但需在编译期释放)
2. 允许使用 std::vector/std::string(部分实现)
3. 允许 try-catch(仅编译期异常)
动态内存(编译期释放)、复杂容器、异常处理

2. 典型示例:编译期计算阶乘

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

// C++14 及以上:constexpr 函数支持循环

constexprintfactorial(intn) {

if(n < 0)throw"n must be non-negative";// C++20 允许 try-catch

intresult = 1;

for(inti = 1; i <= n; ++i) {

result *= i;

}

returnresult;

}

// 1. 编译期调用:实参是常量表达式(5),结果在编译期计算为 120

constexprintf5 = factorial(5);

// 2. 运行期调用:实参是运行时变量(x),结果在运行期计算

intx = 6;

intf6 = factorial(x);

3. 关键特性:两态性与编译期验证

  • 两态性:constexpr 函数并非“必须编译期执行”,而是“可编译期执行”。实参是常量表达式时编译期计算,否则运行期计算,兼顾灵活性与性能。
  • 编译期验证:若强制要求函数在编译期执行(如赋值给 constexpr 变量),编译器会严格检查——若函数存在无法编译期执行的操作(如依赖运行时输入),则直接报错。

四、constexpr 类与对象:编译期构造的自定义类型

C++11 允许类通过constexpr 构造函数创建“编译期对象”,即对象的所有成员在编译期初始化,且对象的成员函数可通过 constexpr 修饰实现编译期调用。

1. constexpr 构造函数的要求

  • 必须初始化类的所有非静态成员(确保无未初始化成员)。
  • C++11 中函数体必须为空(仅通过初始化列表初始化);C++14 及以上允许简单函数体(如条件判断)。
  • 构造函数的参数必须是字面类型。

示例:编译期可构造的 Point 类

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

classPoint {

private:

intx_, y_;

public:

// C++11 风格:空函数体,通过初始化列表初始化成员

constexpr Point(intx,inty) : x_(x), y_(y) {}

// constexpr 成员函数:编译期计算两点距离的平方

constexprintdistance_sq(constPoint& other)const{

intdx = x_ - other.x_;

intdy = y_ - other.y_;

returndx*dx + dy*dy;

}

// 普通成员函数:仅运行期调用

voidprint()const{

std::cout <<"("<< x_ <<","<< y_ <<")\n";

}

};

// 1. 编译期创建对象:构造函数实参是常量表达式

constexpr Point p1(1, 2);

constexpr Point p2(4, 6);

// 2. 编译期调用成员函数:计算距离平方(结果 25)

constexprintdist_sq = p1.distance_sq(p2);

// 3. 运行期调用普通成员函数

p1.print();

2. constexpr 对象的特性

  • constexpr 对象的所有非静态成员均为编译期常量,可直接用于常量表达式(如作为 constexpr 函数的实参)。
  • constexpr 对象的this指针在编译期可见,因此其 constexpr 成员函数可直接访问成员并完成编译期计算。

五、C++17/C++20 关键增强:constexpr lambda、consteval 与 constinit

随着标准演进,constexpr 的能力大幅扩展

1. C++17:constexpr lambda

C++17 允许 lambda 表达式通过constexpr修饰(或隐式满足 constexpr 条件),使其可在编译期执行。

示例:编译期使用 lambda 计算数组总和

1

2

3

4

5

6

7

8

9

10

11

12

13

#include <array>

constexpr auto sum_array = [](constauto& arr) {

intsum = 0;

for(auto val : arr) {

sum += val;

}

returnsum;

};

// 编译期计算数组总和:sum = 1+2+3+4+5 = 15

constexpr std::array<int, 5> arr = {1,2,3,4,5};

constexprinttotal = sum_array(arr);

2. C++20:consteval 与 constinit

C++20 新增两个关键字,进一步细化“编译期计算”的语义,与 constexpr 形成互补:

  • consteval:强制函数“必须在编译期执行”(即“立即函数”),若无法编译期计算(如实参是运行时变量),直接报错。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    // consteval 函数:必须编译期执行

    constevalintsquare(intn) {

    returnn * n;

    }

    constexprints4 = square(4);// 正确:编译期计算(16)

    inty = 5;

    // 错误:实参 y 是运行时变量,无法编译期执行

    ints5 = square(y);

  • constinit:确保变量“在编译期初始化”(仅适用于静态/线程局部变量),但不要求变量是“常量”(即后续可修改,只要初始化在编译期完成)。

    1

    2

    3

    4

    5

    // 错误:静态变量默认运行时初始化(全局变量除外)

    staticintstatic_num = 10;

    // 正确:constinit 强制静态变量编译期初始化(值 20)

    constinitstaticintconstinit_num = 20;

    constinit_num = 30;// 合法:constinit 仅限制初始化,不限制后续修改

constexpr、consteval、constinit 对比

关键字核心语义是否允许运行期执行适用场景
constexpr可编译期执行(两态性)是(实参为运行时变量时)兼顾编译期计算与运行期灵活调用
consteval必须编译期执行(强制)否(否则报错)确保零运行时开销(如编译期哈希)
constinit强制编译期初始化是(变量可后续修改)静态变量的编译期初始化(如全局配置)

六、constexpr 的核心应用场景

constexpr 并非“语法糖”,而是解决实际问题的工具,核心应用场景包括:

1. 编译期计算:消除运行时开销

对于固定逻辑(如数学公式、常量配置),通过 constexpr 在编译期计算结果,避免运行时重复计算。例如:

  • 编译期计算数组大小(替代sizeof(arr)/sizeof(arr[0]))。
  • 编译期生成哈希值(如字符串字面量的哈希,避免运行时哈希计算)。

2. 静态断言(static_assert):编译期合法性检查

static_assert的条件必须是常量表达式,constexpr 可提供复杂的编译期条件判断,实现更灵活的断言。例如:

1

2

3

4

5

6

7

8

// 编译期检查模板参数是否为偶数

template<intN>

voidprocess() {

static_assert(N % 2 == 0,"N must be even");// 条件依赖 constexpr 计算

}

process<4>();// 正确:4 是偶数

// process<5>(); // 错误:编译期断言失败,提示 "N must be even"

3. 编译期元编程:生成代码逻辑

结合模板与 constexpr,可在编译期生成代码(如循环展开、类型判断),实现“元编程”。例如:

  • 编译期遍历数组并计算结果(无需运行时循环)。
  • 编译期根据类型生成不同的处理逻辑(替代运行时if-else)。

4. 常量表达式接口:增强类型安全

通过 constexpr 定义接口(如 constexpr 函数、constexpr 类),强制调用者使用编译期常量,避免运行时错误。例如:

  • 自定义数值类型(如角度、长度),通过 constexpr 构造函数确保输入合法(如角度范围 0-360)。

七、常见误区与注意事项

  1. “constexpr 变量一定在编译期存储”:错误。constexpr 仅要求“值在编译期确定”,存储位置仍由编译器决定(如可能存储在数据段,也可能直接内联到代码中)。
  2. “constexpr 函数只能返回常量”:错误。constexpr 函数的返回值是否为常量,取决于实参——实参是常量表达式时返回常量,实参是运行时变量时返回普通值。
  3. “constexpr 与 const 完全等价”:错误。const仅表示“值不可修改”,不要求“值在编译期确定”(如const int a = rand();是合法的,但constexpr int a = rand();是错误的);而 constexpr 同时要求“不可修改”和“编译期确定”。
  4. “C++20 后 constexpr 可随意使用动态内存”:错误。C++20 允许 constexpr 函数使用new,但必须在编译期通过delete释放(否则编译器报错),无法将动态内存泄露到运行时。

补充const与constexpr

const和constexpr修饰函数时的作用和场景完全不同,核心区别在于:const关注函数是否修改对象状态(仅用于成员函数),而constexpr关注函数是否能在编译时求值(可用于任意函数)。

1.const修饰函数(仅适用于类的非静态成员函数)

  • 作用:声明该成员函数是“常量成员函数”,承诺不会修改对象的非静态数据成员,也不能调用非const的成员函数(防止间接修改对象)。
    本质是对函数的“行为约束”,确保调用该函数时对象状态不变,属于运行时的类型安全保障。

  • 语法:const放在函数参数列表后(const成员函数的标志)。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    classA {

    private:

    intx;

    public:

    // const 成员函数:承诺不修改 x,也不能调用非 const 函数

    intgetX()const{

    // x = 10; // 编译错误:const 函数不能修改成员

    returnx;

    }

    voidsetX(intval) {// 非 const 函数:可以修改成员

    x = val;

    }

    };

  • 适用场景:
    当需要获取对象状态(如访问器getter)但不修改它时,用const修饰,确保函数的“只读”行为。
    例如:const A a; a.getX();合法(const对象只能调用const成员函数),但a.setX(5);会报错。

2.constexpr修饰函数(适用于任意函数:普通函数、成员函数、构造函数等)

  • 作用:声明该函数是“常量表达式函数”,可以在编译时被求值(前提是输入参数为常量表达式)。
    本质是对函数的“计算时机约束”,允许函数结果作为编译期常量使用(如模板参数、数组大小等),属于编译时的计算能力保障。

  • 语法:constexpr放在函数返回类型前。

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    // 普通 constexpr 函数:编译时可求值

    constexprintadd(inta,intb) {

    returna + b;

    }

    classB {

    private:

    inty;

    public:

    // constexpr 构造函数:允许创建编译期对象

    constexpr B(intval) : y(val) {}

    // constexpr 成员函数:编译时可求值(需满足条件)

    constexprintgetY()const{// 可同时是 const(双重约束)

    returny;

    }

    };

  • 适用场景:
    当需要在编译时计算结果(如常量、模板参数)时,用constexpr修饰。例如:

    1

    2

    3

    constexprintsum = add(3, 5);// 编译时计算,sum 是编译期常量(8)

    constexpr B b(10);// 编译期创建对象

    intarr[b.getY()];// 合法:数组大小是编译期常量(10)

    注意:constexpr函数并非只能在编译时调用,当输入参数是运行时变量时,它也能像普通函数一样在运行时调用。

3. 核心对比表

维度const 修饰函数constexpr 修饰函数
适用范围仅类的非静态成员函数任意函数(普通函数、成员函数、构造函数等)
核心作用保证函数不修改对象状态(只读约束)保证函数可在编译时求值(编译期计算)
本质运行时的对象状态保护编译时的计算能力支持
与常量表达式的关系无关(结果不能直接作为编译期常量)直接相关(结果可作为编译期常量)
能否同时使用可以(constexpr const 成员函数,双重约束)无冲突,constexpr 不影响 const 的作用
  • 用const修饰成员函数:告诉编译器和使用者“这个函数不会改对象,放心在const对象上调用”。
  • 用constexpr修饰函数:告诉编译器“这个函数可以在编译时算,给我编译期优化的能力”。
  • 两者可以共存(如constexpr const成员函数),此时函数既保证不修改对象,又能在编译时求值。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 22:30:21

后缀表达式求值:栈原理、中缀转逆波兰表达式完整解析

第一次在洛谷刷到P1449 后缀表达式时&#xff0c;我盯着这个名词愣了好一会儿。平时写惯了3*(5-2)7这种中缀式子&#xff0c;突然冒出一个把运算符全丢在后面的3.5.2.-*7.&#xff0c;第一反应是&#xff1a;这玩意儿真的是给人读的吗&#xff1f;不过也正是这道题&#xff0c;…

作者头像 李华
网站建设 2026/10/1 22:30:11

grep组合拳:高效排查大日志文件的实用技巧

上周三下午&#xff0c;我正在工位上改脚本&#xff0c;隔壁同事探过头来&#xff0c;一脸无奈&#xff1a;“哥&#xff0c;这个日志文件十几个 G&#xff0c;我用 VS Code 一打开就卡死&#xff0c;等半天只看到转圈。我把报错那几行复制给你行不行&#xff1f;”我说你先别急…

作者头像 李华
网站建设 2026/10/1 22:30:11

高校教材征订管理系统实战:Spring Boot+MyBatis从部署到订单汇总

简介&#xff1a;这份高校教材征订管理系统源码包面向计算机相关专业学生与Java初学者&#xff0c;提供一套可直接参考的课程设计完整实现&#xff0c;用于解决教材信息维护、学生选课订购、教师需求提交与订单统计等业务场景。压缩包共603个文件&#xff0c;约3.29MB&#xff…

作者头像 李华
网站建设 2026/10/1 22:30:09

OpenRig 实质:Codex 本地化代理与 YAML 策略引擎

1. OpenRig 是什么&#xff1a;一个被误读的开源项目代号OpenRig 这个词在当前技术社区里&#xff0c;正经历一场典型的“语义漂移”——它既不是官方发布的成熟产品&#xff0c;也不是某个知名开源组织背书的标准化工具&#xff0c;而更像是一组围绕Codex&#xff08;微软早期…

作者头像 李华
网站建设 2026/10/1 22:29:00

Spring Boot+小程序+MySQL实战:上门维修系统毕设源码这样学才有价值

简介&#xff1a;面向计算机专业毕业设计/课程设计场景的微信小程序上门维修系统源码&#xff0c;采用Java小程序MySQL架构&#xff0c;涵盖用户、维修员、管理员三个角色。用户端可查看首页、广告、新闻资讯&#xff0c;并管理维修信息、维修记录、评价与收藏&#xff1b;维修…

作者头像 李华
网站建设 2026/10/1 22:27:46

String、StringBuffer、StringBuilder 区别详解:从源码到面试

几乎每一场 Java 技术面试&#xff0c;都会抛出一个绕不开的问题&#xff1a;String、StringBuffer、StringBuilder 三者的区别是什么。我经历过的那些面试、代码评审里&#xff0c;这道题也最容易暴露一个人的真实功底。有人把标准答案背得滚瓜烂熟&#xff0c;结果被追问到源…

作者头像 李华