日期:2024年7月3日标签:C/C++

const、constexpr #

先说结论:

  1. const 修饰的变量表示该变量的值不会发生变化,但是不一定能够在编译期间就取得结果。constexpr 修饰的变量,不仅变量值不会发生变化,而且在编译期间就能取得结果。所以如果一个 const 变量能够在编译期间求值,那么它就是一个常量表达式。
  2. constexpr 函数在编译期间求值,所以每次程序运行不会重复计算,使得程序运行更快。但是如果 contexpr 函数中存在无法在编译期间求值的参数,则 constexpr 函数和普通函数一样,此时函数的返回值不是常量表达式。

何为常量 #

常量(constant value),顾名思义就是变量值不会发生变化的变量。C++ 中可以通过 const 定义一个常量。

何为常量表达式 #

满足以下条件的变量即为常量表达式:

  1. 变量的值不会发生改变。
  2. 编译期间就能够取得结果。

第一条表示变量是一个常量,所以常量表达式一定是一个常量,但是常量不一定是常量表达式。字面值一定是常量表达式。

int a = 4; // 不是常量表达式,因为 a 不是常量
const int b = a; // 不是常量表达式,因为初始值 a 不是常量表达式
const int c = 14; // 是常量表达式,因为 14 是字面值
const int d = c + 1; // 是常量表达式,因为 c + 1 是常量表达式
const int e = getNumer(); // 如果 getNumer() 是普通函数,e 的值要在运行时才能确定,所以不是常量表达式

常量表达式的使用场景:

  1. 数组大小
  2. 整型模板实参(如 std::array<T,N> 的长度参数 N)
  3. switch-case 中 case 标签
  4. 枚举量的值
  5. 对齐规格

constexpr 关键字 #

通过上面的例子可以看出,判断一个 const 对象是否为常量表达式时需要一定的思考过程,如果代码很复杂,判断难度会越来越高。所以 C++11 允许把变量声明为 constexpr 类型,利用编译器取保证变量为常量表达式,如果声明为 constexpr 的变量不满足常量表达式的条件,编译将会报错。所以当看到一个变量声明为 constexpr 时,那么它一定是常量表达式。

int getValue() {
    return 4;
}

int main()
{
    constexpr int a = 4;
    constexpr int b = getValue(); // error: function call must have a constant value in a constant expression
    return 0;
}

constexpr 函数 #

可以用 constexpr 声明常量表达式函数,它是指可以用作常量表达式的函数。但是要记住 constexpr 函数不一定返回常量表达式!需要满足:返回类型和所有形参的类型必须是字面值类型(literal type)。除了内置类型,用户自定义的类也可以是字面值类型,因为它的构造函数和成员函数也可以是 constexpr 函数

只有 constexpr 函数的所有实参都是常量表达式,constexpr 函数的结果才是常量表达式。只要有一个参数值在编译期间未知,那就和普通函数一样,在运行时计算。

constexpr int new_sz() { return 42; }
constexpr int foo = new_sz(); // 正确:foo 是一个常量表达式

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

constexpr int i1 = 42;
constexpr int i2 = sum(i1, 52); // 所有参数都是常量表达式,sum 的结果也是常量表达式,在编译期求值

int AddThree(int i) {
  return sum(i, 3); // i 不是常量表达式,此时 sum 作为普通函数使用
}

为了能保证 constexpr 函数在编译时能随时展开计算,constexpr 函数隐式内联。内联函数和 constexpr 函数不同于其他函数,允许定义多次,但要保证所有的定义一致。正因如此,内联函数和 constexpr 函数一般定义在头文件中。

内联函数 #

简单提一下内联函数。可以通过 inline 关键字将函数声明为内联函数。例如:

inline const string& shorterString(const string &s1, const string &s2) {
    return s1.size() <= s2.size() ? s1 : s2;
}

cout << shorterString(s1, s2) << endl;

在编译过程中 cout << shorterString(s1, s2) << endl; 将会被展开成为以下形式:

cout << (s1.size() <= s2.size() ? s1 : s2) << endl;

这样可以消除 shorterString 运行时的开销。

inline 关键字只是向编译器发出一个请求,编译器可以选择忽略这个请求,将函数当作普通函数对待。

(完)

目录