一、常量表达式
常量表达式:指值不会改变并且在编译过程就能得到计算结果的表达式;字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
const int i = 20; // 是常量表达式
int j = 10; // 数据类型是普通的int而非const ,不是常量表达式
int size(int i) {return i+1;}
const int k = size(10); // k 本身是一常量,但它的具体值需要调用size函数才能获取到,所以不是常量表达式
如何区分常量和常量表达式?
查看汇编代码,在编译过程中是否得到计算结果
int calculate(int i)
{
return i+1;
}
constexpr int calculate_1(int i) // constexpr 后续讲解,这里只做比较
{
return i+1;
}
int main(int argc, const char** argv) {
const int k = calculate(10);
const int k1 = calculate_1(20);
return 0;
}
汇编代码部分:
call ___main
// 计算k的值
movl $10, (%esp)
call __Z9calculatei
movl %eax, 28(%esp)
k的值需要调用calculate计算得来
// 计算k1的值
movl $21, 24(%esp)
k1值有编译器计算直接赋值
大家可以通过汇编代码,很容易理解 在编译过程中是否得到计算结果这句话
二、constexpr 变量
将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化;
constexpr int i = 20; // 20是常量表达式 正确
constexpr int j = i + 1; // i + 1是常量表达式
int j1 = 10;
constexpr int j2 = j1; // 错误 j1不是常量表达式
constexpr int k1 = calculate(10); // 错误
constexpr int k1 = calculate_1(10); // 正确
不能使用普通函数作为constexpr变量的初始值,可以用constexpr函数初始化constexpr变量;
一般情况下,当你认为变量是一个常量表达式时,可以用constexpr声明,提升代码的效率;
constexpr int i = 10;
int i1 = i + 1; // 由于i是常量表达式 => int i1 = 10 + 1;
=> int i1 = 11;
int j = 20;
int j1 = j + 1;
movl $10, 12(%esp)
movl $11, 8(%esp) // i1 = 11;
movl $20, 4(%esp)
movl 4(%esp), %eax
addl $1, %eax // j + 1
movl %eax, (%esp) // j1 = j + 1
显然执行代码一有更高的效率;
三、字面值类型
可以用constexpr 声明的类型是字面值类型。算术类型、引用和指针都属于字面值类型,而自定义类、IO库、string类型不属于字面值类型,也不能被定义成constexpr;
constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址的对象。
函数体内定义的局部变量(非静态局部变量)存放在非固定的地址上,所以constexpr的指针不能指向此类变量。定义于所有函数体之外的对象其地址固定不变,可以用来初始化constexpr的地址。允许constexpr引用绑定到静态局部变量,constexpr指针能指向这样的变量。
int j = 0;
int main(int argc, const char** argv) {
constexpr int *j1 = &j; // 成立
constexpr int &j2 = j; // 成立
static int i = 0;
constexpr int *i1 = &i; // 成立
constexpr int &i2 = i; // 成立
return 0;
}
constexpr 仅对指针有效,于指针所指的对象无关
const int *p = NULL; //p是一个指向常量的指针
constexpr int *p = nullptr; // p是一个指向整型的常量指针; constexpr 把它所定义的对象置为了顶层const
int i = 0 ;
constexpr int j = 0; // i 和 j 都定义在函数体之外,全局变量
constexpr int *p1 = &j; // p1 常量指针 指向整数j
constexpr const int *p2 = &i; // p2 是常量指针 指向常量整数i
四、constexpr函数
constexpr 函数是指能用于常量表达式的函数。函数的返回类型及所有形参的类型都得是字面值类型,而且函数体中必须有且只有一条return语句;执行初始化任务时,编译器把对constexpr函数的调用替换成其结果值。为了能在编译过程中随时展开,constexpr函数隐式地被转换为内联函数。传递的形参时常量表达式,则它的返回值也是常量表达式;
constexpr int calculate_1(int i)
{
i++;
return i+1;
}
int main(int argc, const char** argv) {
constexpr int k1 = calculate_1(1); // 等价于 cont int k1 = 3;
int i = 1;
constexpr int k1 = calculate_1(i); // error
}
constexpr函数和内联函数可以在程序中多次定义而不报错。因为编译器需要展开函数,所以光有函数声明是不够的,还需要函数的定义。但是对于某个给定的内联函数或者constexpr函数来说,它的多个定义必须完全一致。 一般将内联函数与constexpr函数定义在头文件中。
// 标准写法
// b.h
constexpr int calculate_1(int i)
{
i++;
return i+1;
}
// a.cpp
#include "b.h"
int main(int argc, const char** argv) {
constexpr int k1 = calculate_1(1); // 等价于 cont int k1 = 3;
}