C++ constexpr 修饰符与函数

文章目录

  • [一、constexpr 修饰符](#一、constexpr 修饰符)
    • [1.1 修饰变量:定义编译期常量](#1.1 修饰变量:定义编译期常量)
    • [1.2 核心作用](#1.2 核心作用)
    • [1.3 区别对比](#1.3 区别对比)
  • [二、constexpr 函数](#二、constexpr 函数)
  • 三、字面值
    • [3.1 字面值类型](#3.1 字面值类型)
    • [3.2 指针和引用](#3.2 指针和引用)
    • [3.3 自定义字面值类型](#3.3 自定义字面值类型)
  • 四、允许递归
  • 五、泛型
    • [5.1 模板函数中的 constexpr](#5.1 模板函数中的 constexpr)
    • [5.2 if constexpr(C++17)](#5.2 if constexpr(C++17))

一、constexpr 修饰符

在 C++ 中,constexpr 是一个关键字修饰符,其核心作用是指定对象或函数可以在编译期计算,从而将计算逻辑从运行时提前到编译期,带来性能优化、代码安全性提升等好处,同时在编译时就能发现错误。

换句话说:

  • 如果上下文要求编译期常量(如数组大小、模板参数),constexpr 能保证表达式能在编译期计算;
  • 如果上下文允许运行时计算,constexpr 也能在运行时执行(并非强制仅限编译期)。

1.1 修饰变量:定义编译期常量

constexpr 修饰变量时,要求变量必须在定义时初始化 ,并且初始化值必须是常量表达式,该值不可修改(类似 const,但更严格)。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

int some_function() {
    return 999;
}

int main() {
    constexpr int max_size = 100;  // 编译期确定值,可用于数组大小等编译期上下文
    int arr[max_size];             // 合法:编译期已知大小

    // 对比 const: const 变量可能在运行时初始化
    const int runtime_val = some_function();  // 运行时确定值
    // constexpr int error = some_function(); // 错误:无法在编译期确定值
    return 0;
}

特点:

  1. constexpr 变量必须立即初始化;
  2. 初始值必须是编译期可计算的表达式;
  3. constexpr 变量一定是编译期常量 ,而 const 变量可能是运行时常量
  4. constexpr 在模板元编程、常量数组、编译期分支(if constexpr)中尤为重要。

1.2 核心作用

  1. 性能优化:结果在编译期直接计算并嵌入,减少运行时开销。
  2. 安全性提升:在编译期检查逻辑,例如数组大小是否合法。
  3. 支持元编程:结合模板,可以写出编译期执行的算法,如编译期排序、编译期字符串哈希。

1.3 区别对比

  1. constexpr 不是"只能在编译期执行",而是"允许在编译期执行";

  2. 不同标准的演进

    • C++11:constexpr 限制非常严格(函数只能有一个 return)。
    • C++14:放宽限制,允许循环、局部变量、多条语句。
    • C++20:支持 constexpr 动态内存分配、虚函数、try-catch 等更复杂场景。

constexprconst#define 区别

特性 constexpr const #define
类型检查 无(宏替换)
编译期计算 必须 可能 可能(纯替换)
作用域 块作用域 块作用域 全局宏替换,无作用域
调试支持
内存分配 无额外开销 可能有
  • constexpr最严格的常量语义(编译期 + 类型安全);
  • const 只保证不可修改,不一定编译期常量;
  • #define 只是预处理器替换,缺乏类型检查和作用域控制。

二、constexpr 函数

constexpr 函数是指可以用于常量表达式的函数

constexpr 函数指的是在编译的时候就能得到其返回值的函数,也就是说编译器将 constexpr 函数直接转换成其返回值。

简单理解就是在编译阶段函数已经执行完成并把运行结果填到了 constexpr 函数所在位置。因此,constexpr 函数都是被隐式地定义为内联函数。

编译器会尽量在编译期执行 constexpr 函数,如果参数是常量表达式,则直接计算出结果并替换;如果参数不是常量表达式,函数会退化为普通函数,运行时求值。

所有 constexpr 函数都会被隐式地视为 inline

要点

  1. C++11 限制 :函数中只能包含一个 return 语句;
  2. C++14 起:支持多条语句、局部变量、分支语句、循环等;
  3. C++20 起 :支持 try-catchnew/delete 等复杂逻辑;
  4. 返回值必须是字面值类型(算术类型、引用、指针属于字面值类型)
  5. 参数必须是字面值类型(自定义类、IO 库、string 类型不属于字面值类型)
  6. constexpr 函数被隐式地指定为内联函数
  7. 允许递归调用。

测试合法性

cpp 复制代码
#include <iostream>
using namespace std;

constexpr int add(int a, int b) {
    return a + b;
}

int main() {
    // 合法:编译期计算,结果为8
    constexpr int sum1 = add(3, 5);
    cout << sum1 << endl;
    
    // 合法:运行时计算,结果为8(仍合法),
    // 印证了constexpr不是"仅编译期执行"的标志,而是"允许编译期执行"的标志
    int x = 3, y = 5;
    int sum2 = add(x, y);
    cout << sum2 << endl;
    
    // 不合法:编译期计算,不能传入变量;
    // constexpr int sum3 = add(x, y); // 错误
    return 0;
}

constexpr 不是"强制编译期执行",而是"能编译期执行时就执行"。


三、字面值

字面值类型(Literal Types):编译器在编译期就能完全确定的类型。

在编程中,字面值类型指的是编译期就能确定其值的类型,它们的取值是固定的字面值(如数值、字符串等),且在编译阶段即可被编译器完全知晓。这类类型通常用于模板元编程、常量表达式等场景,能在编译期进行计算或校验,提升程序效率或安全性。

3.1 字面值类型

  1. 算术类型:int, long, float, double, bool
  2. 字符类型:char, wchar_t, char16_t, char32_t
  3. 布尔类型:bool
  4. 指针、引用(指向编译期已知地址的对象,如全局变量、静态变量)
  5. 从 C++11 起:自定义字面值类型(满足 constexpr 构造函数、析构函数的类)。

3.2 指针和引用

指向全局变量(编译期可确定地址)的指针和引用也属于字面值类型。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 全局变量,地址编译期可确定
int global_var = 42;

constexpr int* change(int* p) {
    *p = 12;
    return p;
}

int main() {
    // 合法:指向全局变量的指针是字面值类型,可用于constexpr
    constexpr int* ptr_to_global = &global_var;
    
    // 合法
    int* p = change(&global_var);
    
    cout << p << endl;           // 输出地址
    cout << ptr_to_global << endl; // 输出相同地址
    cout << global_var << endl;  // 输出12
    cout << *p << endl;          // 输出12
    
    return 0;
}

输出:

text 复制代码
0x56265707e010
0x56265707e010
12
12

如果指针指向的是局部变量(运行时分配地址),就不是字面值类型。

3.3 自定义字面值类型

cpp 复制代码
#include <iostream>
using namespace std;

struct Point {
    int x, y;
    constexpr Point(int a, int b) : x(a), y(b) {}
    
    // 添加一个constexpr成员函数计算距离平方
    constexpr int distanceSquared() const {
        return x * x + y * y;
    }
};

constexpr Point p1(3, 4);  // 编译期常量对象

int main() {
    // 编译期计算距离平方
    constexpr int distSq = p1.distanceSquared();
    cout << "Point p1: (" << p1.x << ", " << p1.y << ")" << endl;
    cout << "Distance squared from origin: " << distSq << endl;
    
    // 可以在编译期上下文使用
    constexpr Point p2(5, 12);
    constexpr int distSq2 = p2.distanceSquared();
    cout << "Point p2: (" << p2.x << ", " << p2.y << ")" << endl;
    cout << "Distance squared from origin: " << distSq2 << endl;
    
    // 使用constexpr对象作为数组大小
    int arr[distSq > 10 ? 10 : 5] = {0}; // 条件编译期表达式
    
    return 0;
}

输出:

text 复制代码
Point p1: (3, 4)
Distance squared from origin: 25
Point p2: (5, 12)
Distance squared from origin: 169

四、允许递归

constexpr 函数允许递归调用,这使得一些经典递归算法可以在编译期计算。

cpp 复制代码
#include <iostream>
#include <string>
using namespace std;

// 常量表达式函数
constexpr int factorial(int n) {
    return n == 1 ? 1 : n * factorial(n - 1);
}

// 常量表达式
constexpr int num = 5;

int main() {
    // 编译期间进行计算并且返回结果
    cout << factorial(num) << endl; // 输出120
    cout << factorial(3) << endl;   // 输出6
    
    // 实参为变量时,程序运行期间计算并返回结果。
    int i = 8;
    // constexpr int result = factorial(i); // 报错;表达式必须含有常量值
    int result = factorial(i); // 运行时计算
    
    cout << result << endl;    // 输出40320
    return 0;
}

输出:

text 复制代码
120
6
40320

注意:

  • 如果传入的是常量表达式(如 5),在编译期计算;
  • 如果传入的是变量(如 x),在运行时计算。

五、泛型

constexpr 在泛型编程中扮演着重要角色,特别是结合模板if constexpr(C++17 引入)时,可以让模板逻辑在编译期分支,从而实现更高效、更安全的代码。

5.1 模板函数中的 constexpr

cpp 复制代码
#include <iostream>
using namespace std;

// constexpr 模板函数
template <typename T>
constexpr T add(T a, T b) {
    return a + b;
}

int main() {
    // 编译期计算
    constexpr int sum = add(10, 20);
    cout << "Compile-time sum: " << sum << endl;
    
    // 运行时计算
    double d = add(1.1, 2.2);
    cout << "Run-time sum: " << d << endl;
    
    // 演示编译期计算的能力 - 用于数组大小
    constexpr int array_size = add(5, 5);
    int arr[array_size] = {0}; // 数组大小在编译期确定
    
    cout << "Array size: " << sizeof(arr)/sizeof(arr[0]) << endl;
    
    return 0;
}

输出:

text 复制代码
Compile-time sum: 30
Run-time sum: 3.3
Array size: 10

5.2 if constexpr(C++17)

if constexpr 是一种编译期分支,只有满足条件的分支才会被实例化。

cpp 复制代码
#include <iostream>
#include <type_traits>
using namespace std;

// 重命名函数以避免与 std::type_info 冲突
template <typename T>
constexpr auto get_type_info() {
    if constexpr (is_integral<T>::value) {
        return "Integral";
    } else {
        return "Non-integral";
    }
}

int main() {
    cout << get_type_info<int>() << endl;     // 输出 "Integral"
    cout << get_type_info<double>() << endl;  // 输出 "Non-integral"
    return 0;
}

作用:

  1. 结合模板,constexpr 能实现零运行时开销的泛型编程;
  2. if constexpr 让模板元编程逻辑更直观,不必依赖 SFINAE;
  3. 编译期递归、分支、计算,极大地增强了 C++ 的元编程能力。
相关推荐
会跑的葫芦怪3 小时前
Golang 赋值运算符与短声明 (= 与 :=)使用场景
开发语言·后端·golang
数据知道4 小时前
Go基础:Go语言函数和方法详解
开发语言·后端·golang·go语言
木觞清4 小时前
补环境-JS原型链检测:在Node.js中完美模拟浏览器原型环境
开发语言·javascript·node.js
或与且与或非4 小时前
rust使用sqlx示例
开发语言·数据库·rust
我是华为OD~HR~栗栗呀4 小时前
华为od-前端面经-22届非科班
java·前端·c++·后端·python·华为od·华为
软件黑马王子5 小时前
C#练习题——泛型实现单例模式和增删改查
开发语言·单例模式·c#
bkspiderx5 小时前
C++设计模式之创建型模式:抽象工厂模式(Abstract Factory)
c++·设计模式·抽象工厂模式
嵌入式小李.man6 小时前
C++第十篇:const关键字
开发语言·c++
码界筑梦坊6 小时前
194-基于Python的脑肿瘤患者数据分析可视化
开发语言·python·数据分析·sqlite·毕业设计·echarts·fastapi