C++(十七):constexpr 函数

constexpr 函数

在 C++ 中,constexpr 是一个关键字,用于声明一个函数,该函数可以在编译时(compile time)进行求值。这意味着,如果 constexpr 函数的参数在编译时是已知的值,那么编译器就可以直接计算出函数的结果,并将结果嵌入到最终的可执行代码中,而不是在程序运行时再进行计算。

你可以把 constexpr 函数想象成一个超级机智的厨师,如果提前知道你需要多少食材,他就能在你真正开始做饭之前就把它们准备好,这样你就不用在做饭的时候再花时间去切菜或者量米了。

constexpr 函数的主要目的是:

  • 提高性能:通过在编译时进行计算,可以减少程序运行时的计算量,从而提高程序的执行速度。
  • 用于常量表达式constexpr 函数的结果可以用在需要常量表达式的上下文中,例如定义数组的大小、作为模板参数、或者初始化 constexpr 变量。
  • 进行编译时检查:如果 constexpr 函数的参数在编译时不是常量表达式,编译器可能会发出警告或错误。

constexpr 函数的语法:

声明一个 constexpr 函数与声明普通函数类似,只需要在函数返回类型前面加上 constexpr 关键字。

constexpr 返回类型 函数名(参数列表) {
    // 函数体
}

constexpr 函数的要求和限制:

为了能够在编译时进行求值,constexpr 函数需要满足一些限制:

  • 函数体限制

    • 在 C++11 和 C++14 中constexpr 函数的函数体必须超级简单,一般只能包含单一的 return 语句(除了 typedefusing 声明和静态断言 static_assert)。
    • 从 C++14 开始constexpr 函数的限制被放宽,可以包含更复杂的语句,例如循环、条件判断、局部变量等,但这些语句不能有副作用(例如修改全局变量或调用非 constexpr 函数)。
  • 只能调用其他 constexpr 函数或特定的内置函数constexpr 函数内部调用的任何函数也必须是 constexpr 函数,或者是一些特定的可以在常量表达式中使用的内置函数。
  • 不能有副作用constexpr 函数不能修改全局变量或具有任何其他的副作用。它们的主要目的是计算并返回值。
  • 参数和返回类型必须是字面值类型(Literal Types):字面值类型包括算术类型(intfloat 等)、void、指针类型、引用类型以及某些用户自定义类型(如果它们的构造函数和析构函数满足特定条件)。

constexpr 函数的求值时机:

一个 constexpr 函数可以在编译时求值,也可以在运行时求值。

  • 编译时求值:如果调用 constexpr 函数时,传递给它的参数是常量表达式(例如字面量、const 变量、其他 constexpr 函数的调用结果),那么编译器会在编译时计算出结果。
  • 运行时求值:如果调用 constexpr 函数时,传递给它的参数不是常量表达式(例如运行时才能确定的变量),那么函数将像普通的内联函数一样在运行时被调用。

constexpr 函数的优点:

  • 性能提升:对于在编译时就能确定的计算,避免了运行时的开销。
  • 可以用在常量表达式中:这是 constexpr 最重大的用途之一,允许在需要常量表达式的地方使用函数调用的结果。
  • 编译时检查:如果 constexpr 函数被期望在编译时求值,但由于参数不是常量表达式而无法做到,编译器可能会给出警告或错误。

constexpr 函数的例子:

#include <iostream>
#include <array>

// C++11 版本的 constexpr 函数,只能包含一个 return 语句
constexpr int power(int base, int exp) {
    return (exp == 0) ? 1 : base * power(base, exp - 1);
}

// C++14 版本的 constexpr 函数,可以包含更复杂的逻辑
constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) {
        result *= i;
    }
    return result;
}

int main() {
    // 在编译时求值:
    constexpr int compileTimePower = power(2, 5);
    std::cout << "编译时计算的 2 的 5 次方: " << compileTimePower << std::endl;

    // 可以在需要常量表达式的地方使用:
    std::array<int, power(3, 2)> myArray; // 数组大小在编译时确定

    // 在运行时求值(如果参数不是常量表达式):
    int runtimeBase = 3;
    int runtimeExp = 4;
    int runtimePower = power(runtimeBase, runtimeExp);
    std::cout << "运行时计算的 3 的 4 次方: " << runtimePower << std::endl;

    constexpr int compileTimeFactorial = factorial(5);
    std::cout << "编译时计算的 5 的阶乘: " << compileTimeFactorial << std::endl;

    return 0;
}

constinline 的区别:

  • const: 用于声明常量变量,表明变量的值在初始化后不能被修改。
  • inline: 是一个函数提示符,提议编译器将函数体直接插入到调用的地方以减少函数调用的开销,但这只是一个提议,编译器可能会忽略。inline 函数依旧是在运行时进行求值的。
  • constexpr: 用于声明函数或变量,表明其值可以在编译时确定。constexpr 函数可以在编译时或运行时求值,而 constexpr 变量的值必须在编译时确定。

constexpr 函数是 C++ 中一个强劲的特性,它允许函数在编译时进行求值,从而提高性能并支持在需要常量表达式的场景中使用。constexpr 函数需要满足一些限制,例如函数体只能包含特定的语句,并且只能调用其他 constexpr 函数或特定的内置函数。一个 constexpr 函数既可以在编译时求值(如果参数是常量表达式),也可以在运行时求值(如果参数不是常量表达式)。

想象一下,你正在做数学题。

  • constexpr 函数就像是一个超级机智的计算器,它可以在你真正需要答案之前就把一些题目提前算好。

  • 例子:

    • 老师问你:“2 乘以 5 等于多少?” 你很快就能回答是 10。这个就像电脑在编译时就能算出来。
    • 如果老师问你:“如果今天来了 10 个小朋友,每人发 3 颗糖,一共需要多少颗糖?” 你也能很快算出来是 30。
  • constexpr 函数就是告知电脑:“嘿,如果我知道你需要哪些数字,我就能提前把答案算好,这样你真正运行程序的时候就更快了!”

我们可以画一个场景:

[电脑] --> [机智的计算器 (在电脑里面)] --> [答案]

  • 画一台电脑,电脑里面有一个特别机智的计算器。
  • 告知小朋友,这个机智的计算器可以在电脑真正开始工作之前,提前计算一些简单的数学题。

目前,我们用更接近代码的语言,但依旧保持简单的方式来解释:

  • constexpr int multiplyByTwo(int number) { return number * 2; } -> “告知电脑,这是一个叫做 ‘multiplyByTwo’ 的小工具。如果你给它一个数字,它会帮你把这个数字乘以 2。而且,如果它一开始就知道这个数字是多少,它就能提前算出答案。”

    • constexpr -> “表明这是一个很机智的工具,可以提前计算”
    • int -> “表明这个工具会给你一个整数(数字)作为答案”
    • multiplyByTwo -> “这是这个工具的名字”
    • (int number) -> “你需要给这个工具一个整数作为输入,我们叫它 ‘number’ ”
    • { return number * 2; } -> “这是这个工具的工作方式:把输入的数字乘以 2,然后告知你结果”
  • 如果我们在写程序的时候就告知电脑一个固定的数字,列如 constexpr int result = multiplyByTwo(5);,那么电脑在真正运行程序之前就能算出 result 是 10。

  • 但是,如果我们在程序运行的时候才给 multiplyByTwo 一个数字(列如从用户那里输入),那么电脑就只能在程序运行的时候再计算答案了。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容