constexpr
const
在 C++11 之前只有 const
关键字,从功能上来说这个关键字有双重语义:变量只读,修饰常量。
示例如下:
1 |
|
void func(const int num)
的参数 num
表示这个变量是只读的,但不是常量,使用 int array[num];
这种方式定义一个数组,编译器会报错。const int count = 24;
中的 count
是一个常量,可以使用这个常量来定义一个静态数组。
另外,变量只读并不等价于常量,二者不能混为一谈。const int& b = a1;
b 是一个常量的引用,所以 b 引用的变量是不能被修改的,b = a2;
这句代码语法是错误的。const
对于变量 a1 是没有任何约束的,a1 的值改变 b 的值也就变了。引用 b 是只读的,但是并不能保证它的值是不可改变的,它不是常量。
constexpr
在 C++11 中添加了一个新的关键字 constexpr
,这个关键字是用来修饰常量表达式的。
所谓常量表达式,指的就是由多个常量(值不会改变)组成并且在编译过程中就得到计算结果的表达式。
gcc/g++
工作流程,C++ 程序从编写完到执行分为四个阶段:预处理、 编译、汇编和链接 4 个阶段,得到可执行程序之后就可以运行。
注意:常量表达式和非常量表达式的计算时机不同,非常量表达式只能在程序运行阶段计算出结果,但是常量表达式的计算发生在程序的编译阶段,这可以极大提高程序的执行效率,因为表达式只需要在编译阶段计算一次,节省了每次程序运行时都需要计算一次的时间。
编译器如何识别表达式是不是常量表达式呢?
在 C++11 中添加了 constexpr
关键字之后就可以在程序中使用它来修改常量表达式,用来提高程序的执行效率。使用中建议将 const
和 constexpr
的功能区分开,表达“只读”语义的场景都使用 const
,表达“常量”语义的场景都使用 constexpr
。
在定义常量时,const
和 constexpr
是等价的,都可以在程序的编译阶段计算出结果,例如:
1 |
|
对于 C++ 内置类型的数据,可以直接用 constexpr
修饰;
如果是自定义的数据类型(struct
或者 class
实现),直接用 constexpr
修饰是不行的。
如果要定义一个结构体/类常量对象,示例如下:
1 |
|
在代码中 t.num += 100;
的操作是错误的,对象 t 是一个常量,因此它的成员也是常量,常量是不能被修改的。
常量表达式函数
为了提高 C++ 程序的执行效率,可以将程序中值不需要发生变化的变量定义为常量,也可以使用 constexpr
修饰函数的返回值,这种函数被称为常量表达式函数,这些函数主要包括以下几种:普通函数/类成员函数、类的构造函数、模板函数。
修饰函数
constexpr
并不能修改任意函数的返回值。函数成为常量表达式函数,必须要满足以下几个条件:
(1)函数必须要有返回值,并且 return 返回的表达式必须是常量表达式。
1 |
|
(2)函数在使用之前,必须有对应的定义语句。
1 |
|
(3)整个函数的函数体中,不能出现非常量表达式之外的语句。using
指令、typedef
语句以及 static_assert
断言、return
语句除外。
1 |
|
以上三条规则不仅对应普通函数适用,对应类的成员函数也适用。
修饰模板函数
C++11 语法中,constexpr
可以修饰函数模板,但由于模板中类型的不确定性,因此函数模板实例化后的模板函数是否符合常量表达式函数的要求也是不确定的。如果 constexpr
修饰的模板函数实例化结果不满足常量表达式函数的要求,则会被自动忽略,该函数就等同于一个普通函数。
1 |
|
示例程序中定义了一个函数模板 display()
,但由于其返回值类型未定,因此在实例化之前无法判断其是否符合常量表达式函数的要求:struct Person ret = dispaly(p);
由于参数 p 是变量,所以实例化后的函数不是常量表达式函数,此时 constexpr
是无效的。constexpr int ret1 = dispaly(250);
参数是常量,符合常量表达式函数的要求,此时 constexpr
是有效的。constexpr struct Person p2 = dispaly(p1);
参数是常量,符合常量表达式函数的要求,此时 constexpr
是有效的。
修饰构造函数
如果想用直接得到一个常量对象,也可以使用 constexpr
修饰一个构造函数,这样就可以得到一个常量构造函数。
常量构造函数有一个要求:构造函数的函数体必须为空,并且必须采用初始化列表的方式为各个成员赋值。
1 |
|