1. const关键字
const关键字的判断:
-
限制为常量。只有在声明时使用字面量初始化的才是
常量
,可以进入符号表
。void func(const int a){ // 只读变量 ... } int x = 0; const int a = x; // 只读变量 const int b = 10; // 常量
-
说明变量是
只读变量
。不使用字面量初始化的const常量都是只读变量
。 -
使用
volatile
修饰的const常量不会进入符号表。
在实际使用中,只读变量
和常量
的作用并不一致,而编译器对于在编译期间不能确定初始值的const标识符,都作为只读变量处理。
这就容易导致二义性的出现。因此C++11提供了新的关键字:constexpr
,用于专门修饰常量表达式。
2. constexpr关键字
2.1 概述
constexpr
被专门用于修饰常量表达式(就是由多个常量组成并在编译阶段就确认得到计算结果
的表达式)。
通过使用constexpr
来定义常量,const
就可以被用于定义只读变量,从而避免二义性(当然,依旧可以使用const定义常量,但不推荐)。
2.2 语法
constexpr
可以直接修饰C++内置类型的数据。但如果是自定义类型(struct/class
),不能直接constexpr
修饰:
int x = 0; // 变量
constexpr int a = 10; // 得到常量
constexpr int b = a+1; // 得到常量
// 对于结构体
struct T{
int a;
}
constexpr T t{1}; // 得到常量
2.3 修饰函数返回值
使用constexpr
修饰的函数被称为常量表达式
,它可以是:
- 普通函数
- 类成员函数
- 类构造函数
- 模板函数
constexpr
不能随意修饰函数,必须满足以下条件:
-
函数必须有返回值,且返回值是常量表达式。
-
函数在使用前,必须有对应的定义语句,不能只有声明。
constexpr int func(); constexpr int num = func(); // 这一句会报错,因为此时func()函数还没有定义,只有声明 constexpr int func(){ return 10; }
-
函数的函数体中,不能出现非常量表达式之外的语句。以下几条语句除外:
typedef
using
:功能和typedef一致。static_assert
:断言。return
constexpr int func(){ using type = int; constexpr type a = 10; constexpr type b = 20; return a * b; }
2.4 修饰模板函数
由于模板函数中数据类型的不确定性,函数模板实例化后的模板函数是否复合常量表达式函数的标准也不确定。因此,
constexpr
是是否生效取决于模板实例化情况。#include <iostream> using namespace std; struct Person { const char* name; int age; }; // 定义函数模板 template<typename T> constexpr T dispaly(T t) { return t; } int main() { struct Person p { "luffy", 19 }; //普通函数 struct Person ret = dispaly(p); cout << "luffy's name: " << ret.name << ", age: " << ret.age << endl; //常量表达式函数 constexpr int ret1 = dispaly(250); cout << ret1 << endl; constexpr struct Person p1 { "luffy", 19 }; constexpr struct Person p2 = dispaly(p1); cout << "luffy's name: " << p2.name << ", age: " << p2.age << endl; return 0; }
- 19行:由于
p
不是常量,不符合常量表达式函数的标准,因此constexpr
失效。 - 23行:
250
是常量,符合常量表达式函数的标准,因此constexpr
生效,得到的ret1
也是常量。 - 27行:
p1
是常量,符合常量表达式函数的标准,因此constexpr
生效,得到的p2
也是常量。
2.4 修饰构造函数
如果想直接得到一个常量对象,可以直接使用
constexpr
修饰构造函数,这样可以得到一个常量构造函数,前提是:- 常量构造函数的函数体必须为空,并且采用成员初始化列表来初始化数据成员。
#include <iostream> using namespace std; struct Person { const char* name; int age; constexpr Person(const char* p, int age) :name(p), age(age){} }; int main() { constexpr struct Person p1("luffy", 19); cout << "luffy's name: " << p1.name << ", age: " << p1.age << endl; return 0; }