用const
可以定义常量对象,这种对象一经定义,在程序运行期间无法被修改。也因此,常量对象必须初始化。
const int i = 0; // 常量
i = 5; // cannot assign to variable 'i' with
// const-qualified type 'const int'
// 无法对具有const限定类型的变量'i'进行赋值操作
const强调运行时对象的不可更改,也就是说你可以在运行时现定义一个const,而constexpr强调编译时确定结果。const无法像constexpr一样在编译阶段就对使用的地方进行替换,效率上有所不如,因此推荐将所有你认定为常量表达式的存在都用constexpr
进行定义。
注意,常量和变量在c++里不是相对的概念。常量是指用const修饰,从而使其值无法更改的变量。
要定义常量表达式,首先要用const
符修饰,其次要用另一个常量表达式对其进行初始化,可以是字面量,也可以是字面量的运算表达式。
int getsize(){
return 20;
}
const int size = 20; // 常量表达式
const int size_1 = size + 1; // 常量表达式
const int size_2 = getsize(); // 不是常量表达式
可以看到,虽然getsize
函数返回值固定为20,但是size_2
依旧无法被判定为常量表达式,这是因为编译器无法判定getsize
的结果是否满足要求。这个时候,可以使用constexpr
代替const
来定义所有你认定为是常量表达式的符号。
constexpr int getsize(){
return 20;
}
constexpr int size = 20; // 常量表达式
constexpr int size_2 = getsize(); // 常量表达式
需要注意三点:
constexpr
修饰的函数,其返回值才能被用于给其他constexpr
进行初始化。constexpr
定义指针时,仅表明该指针是一个常量表达式,与其所指向对象无关。constexpr
定义,但不符合常量表达式要求的情况,会产生编译时错误。例如:error: type name does not allow constexpr specifier to be specified
。*非常量表达式的const对象是变量,无法像constexpr一样在编译阶段就对使用的地方进行替换,效率上有所不如,因此推荐将所有你认定为常量表达式的存在都用constexpr
进行定义。
下面写一段程序,并对其进行编译,加深理解。
constexpr int getsize(){
return 20;
}
int getsize_2(){
return 20;
}
constexpr int i = getsize_2();
void constexprTest(){
int j = i; // 使用非常量对象初始化
int k = getsize(); // 使用常量表达式初始化
}
得到汇编代码如下:
constexprTest() # @constexprTest()
# %bb.0:
push rbp
mov rbp, rsp
sub rsp, 16
mov dword ptr [rbp - 4], 20 # 直接替换为20
call getsize_2() # 需要调用函数才能确定j的值
mov dword ptr [rbp - 8], eax
add rsp, 16
pop rbp
ret # -- End function
常量一般只在定义它的文件内有效。 要想在一个文件定义,在其他文件声明并使用它,可以使用extern
关键字。
// a.cpp
extern const int size = 20;
// b.h
extern const int size; // 声明,与a.cpp是同一个常量对象
绑定到const对象上的引用,称之为常量引用( r e f e r e n c e reference reference t o to to c o n s t const const)。
const i = 0;
const &j = i; // reference to const
上面的例子里,我们创建了一个绑定到const对象进的引用。而常量引用还可以绑定非常量对象,与const对象相同,该引用无法在程序运行期间对绑定的对象进行更改。
int i = 20;
const int &j = i;
j = 21; // error: cannot assign to variable 'j'
// with const-qualified type 'const int &'
有趣的是,常量引用是可以引用字面量的,而非常量引用做不到这一点。
const int &i = 20; // 正确
int &j = 20; // error: non-const lvalue reference to type
// 'int' cannot bind to a temporary of type 'int'
下面写一段程序,并进行编译,来看下原理。
void constReference(){
const int &i = 20;
}
汇编代码如下:
constReference(): # @constReference()
# %bb.0:
push rbp
mov rbp, rsp
mov dword ptr [rbp - 12], 20 # 临时量对象
lea rax, [rbp - 12]
mov qword ptr [rbp - 8], rax # 对临时量对象进行引用
pop rbp
ret # -- End function
可以看到程序先在栈空间上创建了一个临时量对象(没有命名的对象)20,再获取该临时量对象的地址以对其进行引用。
这是个虽然基础,但是比较经典的知识点。但是我发现网络上对于此二者的解释和cpp primer上是相反的。这是翻译的问题,无关对错,我个人比较倾向于网上的说法。除此之外,我觉得与英文结合理解要清楚一点。
常量指针,即用于指向常量的指针。与无法使用非常量引用去绑定常量一样,要想存储常量对象地址,也必须使用具有所谓常量限定类型const-qulified
的指针。
const int i = 5;
const int *p = &i; // 指针常量
const int
与int
应视作两种不同的类型。因为指针类型与其指向对象类型必须相符,因此在用非常量对象的地址去初始化常量指针时,就会发生错误。
int i = 5;
const int *p = &i; // cannot initialize a variable of type
// 'const int *' with an lvalue of type 'int'
int i = 5;
int *const p = &i;
回想上一篇提过的就近原则,const
限定符与变量名p
最近,因此p
首先是一个常量,再者是一个指向int
类型对象的指针。常量类型使得该指针的值初始化后就无法改变,即无法指向其他对象。
上述二者也可以嵌套使用,例如:
const int i = 5;
const int *const p = &i;
此时,p
是一个指向const int
类型对象的指针常量。
此外,结合这种嵌套场景,cpp primer里面提出了两个名词,在之后会经常用到。