第 18.7 節

constexpr

0瀏覽次數0訪問次數--跳出率--平均停留

本節解決什麼問題

有些計算在編譯時就已經知道結果,但如果用普通函數,卻要等到運行時才計算——浪費了 CPU 時間。此外,某些場景(數組大小、模板參數、static_assert)必須使用編譯期常量,普通變量做不到。

constexpr 讓函數和變量在編譯期就能求值,把運行時開銷轉移到編譯期,還能用在"必須編譯期常量"的地方。

這個特性是什麼

constexpr 聲明一個變量或函數可以在編譯期求值。從 C++11 開始引入,後續標準不斷增強。

C++ 版本支持
C++11簡單 constexpr 函數(一條 return 語句)、constexpr 變量
C++14constexpr 函數支持多條語句、循環
C++17constexpr lambda
C++20consteval(必須在編譯期執行)、更強的 constexpr
C++23constexpr 支持更多標準庫

C++ 標準版本

C++11(隨後的每個版本都有增強)

需要的頭文件

不需要額外頭文件。constexpr 是語言關鍵字。

基本語法

// constexpr 变量:值在编译期确定
constexpr int size = 100;

// constexpr 函数:可以在编译期执行
constexpr int square(int n)
{
    return n * n;
}

// constexpr 变量可以用编译期函数初始化
constexpr int val = square(5);  // 编译期计算

// C++20 consteval:必须在编译期执行(不能运行时调用)
consteval int cube(int n)
{
    return n * n * n;
}

const vs constexpr

方面constconstexpr
含義變量不可修改變量或函數可在編譯期求值
初始化可在運行時初始化必須在編譯期初始化
函數只能修飾成員函數函數可在編譯期執行
用途防止變量被修改編譯期計算,提高效率
示例const int x = get_value();constexpr int x = 5 * 3;

constexpr、consteval、constinit 的區別

這三個詞看起來都和"常量"有關,但解決的問題不同。初學時先掌握 constexpr,再理解 C++20 的 constevalconstinit

關鍵字含義典型場景
constexpr可以在編譯期求值,也可以在運行期調用通用編譯期函數、常量表達式
consteval必須在編譯期求值生成編譯期 ID、編譯期校驗
constinit保證靜態對象在編譯期初始化,但對象不一定是 const避免全局變量動態初始化順序問題

一句話區分:constexpr 偏"能不能算",consteval 偏"必須現在算",constinit 偏"初始化時機必須早"。

示例代碼

示例 1:constexpr 變量——必須編譯期確定

#include <iostream>

int main()
{
    constexpr int size = 10;        // 编译期常量
    int arr[size];                  // ✅ 可以用作数组大小

    const int runtime_size = size;  // const 可以用编译期常量初始化
    int arr2[runtime_size];         // ✅ 也可以用作数组大小(因为初始化值是 constexpr)

    int n = 5;
    // constexpr int s2 = n;        // ❌ n 是运行时变量,不能初始化 constexpr
    const int s3 = n;               // ✅ const 可以在运行时初始化
    // int arr3[s3];                // ❌ s3 不是编译期常量,不能用作数组大小

    std::cout << "size = " << size << "\n";

    return 0;
}

運行結果

size = 10

示例 2:在示例 1 基礎上,constexpr 函數

#include <iostream>

// constexpr 函数:编译期可执行
constexpr int factorial(int n)
{
    int result = 1;
    for (int i = 1; i <= n; ++i)  // C++14 起支持循环
    {
        result *= i;
    }
    return result;
}

int main()
{
    // 编译期计算:factorial(5) 在编译时就得到 120
    constexpr int f5 = factorial(5);
    std::cout << "factorial(5) = " << f5 << "\n";

    // 也可运行时调用
    int n;
    std::cout << "Enter a number: ";
    std::cin >> n;
    std::cout << "factorial(" << n << ") = " << factorial(n) << "\n";

    return 0;
}

運行結果

factorial(5) = 120
Enter a number: 4
factorial(4) = 24

示例 3:在示例 2 基礎上,constexpr 用於模板和編譯期檢查

#include <iostream>

// constexpr 函数:编译期判断是否为质数
constexpr bool is_prime(int n)
{
    if (n <= 1) return false;
    for (int i = 2; i * i <= n; ++i)
    {
        if (n % i == 0) return false;
    }
    return true;
}

// 编译期静态断言
static_assert(is_prime(2), "2 is prime");
static_assert(is_prime(7), "7 is prime");
static_assert(!is_prime(9), "9 is NOT prime");

// 编译期生成质数数组
constexpr int get_nth_prime(int n)
{
    int count = 0;
    int num = 1;
    while (count < n)
    {
        ++num;
        if (is_prime(num))
        {
            ++count;
        }
    }
    return num;
}

int main()
{
    constexpr int primes[] = {
        get_nth_prime(1),  // 2
        get_nth_prime(2),  // 3
        get_nth_prime(3),  // 5
        get_nth_prime(4),  // 7
        get_nth_prime(5)   // 11
    };

    std::cout << "First 5 primes: ";
    for (int p : primes)
    {
        std::cout << p << " ";
    }
    std::cout << "\n";

    return 0;
}

運行結果

First 5 primes: 2 3 5 7 11 

示例 4:C++17 constexpr lambda

#include <iostream>

int main()
{
    // constexpr lambda:编译期可调用
    constexpr auto square = [](int x) constexpr {
        return x * x;
    };

    constexpr int result = square(5);  // 编译期计算
    std::cout << "square(5) = " << result << "\n";

    // constexpr lambda 也可运行时调用
    int n = 7;
    std::cout << "square(" << n << ") = " << square(n) << "\n";

    return 0;
}

運行結果

square(5) = 25
square(7) = 49

示例 5:在示例 4 基礎上,constexpr 可以運行期調用,consteval 不行

#include <iostream>

constexpr int square(int x)
{
    return x * x;
}

consteval int compile_time_square(int x)
{
    return x * x;
}

int main()
{
    constexpr int a = square(5);              // 编译期计算
    int n = 7;
    int b = square(n);                        // 运行期调用,也允许

    constexpr int c = compile_time_square(6); // 必须编译期计算
    // int d = compile_time_square(n);        // ❌ 编译错误:n 不是编译期常量

    std::cout << "a = " << a << "\n";
    std::cout << "b = " << b << "\n";
    std::cout << "c = " << c << "\n";

    return 0;
}

運行結果

a = 25
b = 49
c = 36

運行結果

見上方每個示例的"運行結果"。

示例中的關鍵語法解釋

示例講了什麼新出現的語法為什麼這樣寫注意事項
示例 1constexpr vs constconstexpr int size = 10;、數組大小constexpr 必須編譯期初始化,可以用作數組大小const 可以在運行時初始化,不保證編譯期
示例 2constexpr 函數循環在 constexpr 函數中同一個函數可編譯期調用也可運行時調用編譯期調用時所有參數也必須是編譯期常量
示例 3constexpr 用於 static_assertstatic_assert()編譯期檢查,不通過直接編譯失敗static_assert 的參數必須是編譯期常量
示例 4constexpr lambdaconstexpr auto f = [](int x) constexpr {...};lambda 也能編譯期求值C++17 起支持,兩個 constexpr 都要寫
示例 5constexpr vs constevalconstevalconsteval 函數只能編譯期調用C++20 才有 consteval

常見錯誤

錯誤 1:把運行時變量賦給 constexpr

int n = 5;
constexpr int x = n;  // ❌ n 不是编译期常量

正確做法:const int x = n;(用 const 而不是 constexpr)。

錯誤 2:constexpr 函數假設所有情況都能編譯期求值

constexpr int divide(int a, int b)
{
    return a / b;  // 编译期 b=0 会导致编译错误
}
constexpr int x = divide(10, 0);  // ❌ 编译错误!

正確做法:編譯期調用時注意參數合法性。

錯誤 3:constexpr 函數內部有未定義行為

constexpr int get(int* p)
{
    return *p;  // 编译期不能解引用空指针
}
constexpr int x = get(nullptr);  // ❌ 编译错误!

正確做法:constexpr 函數中避免未定義行為。

錯誤 4:以為 constexpr 函數一定在編譯期執行

constexpr int square(int x) { return x * x; }

int n = 5;
int y = square(n);  // ✅ 这是运行期调用,不是编译期调用

正確做法:需要強制編譯期時,把結果放進 constexpr 變量、static_assert、模板參數中;C++20 起也可以使用 consteval 函數。

使用建議

  1. 能用 constexpr 表達的就用 constexpr:把運行時計算移到編譯期,程序性能更好。
  2. constexpr 函數既編譯期又運行期:不必寫兩份代碼。
  3. static_assert 做編譯期檢查:配合 constexpr 函數非常強大。
  4. constexpr 不是"更快"的魔法:對於小函數,編譯器本來就會優化。但對於常量表、預計算等場景很有用。
  5. 需要"必須編譯期"才考慮 consteval:普通工具函數優先寫 constexpr,限制更少。

小結

  • constexpr 變量必須在編譯期確定,可以用作數組大小、模板參數等。
  • constexpr 函數可以在編譯期執行(參數也都是 constexpr 時)。
  • constexpr 函數也可以運行期調用;consteval 才是必須編譯期調用。
  • const 強調"不可修改",constexpr 強調"編譯期求值"。
  • C++17 起 lambda 可以用 constexpr
  • static_assert + constexpr 函數 = 編譯期安全網。
音乐页