const限定符
1、初始化和const
1.1、因为const对象一旦创建后其值就不能改变,所以const对象必须初始化。
const int k = GetSize(); // 正确,运行时初始化
const int i = 42; // 正确,编译时初始化
const int j; // 错误,j是一个未初始化的常量
1.2、如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要。
int i = 42;
const int ci = i; // 正确,用非const类型对象初始化const类型对象
int j = ci; // 正确,用const类型对象初始化非const类型对象
2、默认状态下const对象仅在文件内有效
默认情况下,const对象被设定为仅在文件内有效。
有些情况下,希望const对象能在多个文件中共享,则定义及声明时都添加extern关键字,此时const对象只定义一次。
// file1.cpp
extern const int BUFF_SIZE = 512;
// file1.h
extern const int BUFF_SIZE; // 与file1.cpp中定义的BUFF_SIZE是同一个
提示:如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
2.1、定义在头文件中的const对象
在头文件中定义const对象,每个包含它的cpp源文件都将此const对象默认为其局部变量,每个包含该头文件的源文件都有一个独立的const对象,其名字和值相同。
//file2.h
const int PATH_LENGTH = 256;
//file2.cpp
#include "file2.h"
if (pathLength < PATH_LENGTH) { // 此处PATH_LENGTH为file2.cpp文件中的局部变量
......
}
//file3.cpp
#include "file2.h"
if (pathLength < PATH_LENGTH) { // 此处PATH_LENGTH为file3.cpp文件中的局部变量
......
}
3、const的引用
3.1、初始化和对const的引用
引用的类型必须与其所引用对象的类型一致,但是也有例外。
1、初始化常量引用时允许用任意表达式作为初始值,只要改表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是一般表达式。
int i = 42;
const int &r1 = i; // 允许将常量引用绑定到非常量的对象
int &r2 = r1; // 错误:不允许非常量引用绑定到常量对象
2、当一个常量引用绑定到另外一种类型上时会发生什么?
double dval = 3.14;
const int &ri = dval;
此处ri引用了一个int类型的数,但dval却是一个double类型的数;因此为了确保ri绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; // 先生成一个临时整型常量
const int &ri = temp; // 再让ri绑定这个临时量
3、当一个非常量引用绑定到另外一种类型上时会发生什么?
double dval = 3.14;
int &ri = dval; // 错误,非常量引用,引用的类型必须与其所引用对象的类型一致
此时ri为非常量引用,就允许对ri赋值,这样就可以改变ri所引用对象的值。但是由于此时ri绑定的是一个临时量,所以并不能改变dval的值,程序员既然让ri引用dval,就肯定想通过ri改变dval的值。C++语言把这种行为归为非法。
3.2、对const的引用可能引用一个并非const的对象
在3.1章节中我们知道,C++允许对const的引用绑定到非const的对象。因此可能出现以下代码:
int i = 10;
int &r1 = i;
const int &r2 = i; // 正确,允许常量引用绑定到非常量对象
r1 = 20; // 正确,非常量引用,允许通过引用修改对象的值
r2 = 30; // 错误,常量引用,不允许通过常量引用修改对象的值
注意:常用引用绑定到非常量对象,不允许通过常量引用修改非常量对象的值。
4、指针和const
4.1、指向常量的指针,通常也叫指针常量,即指针指向的对象是常量。
const int i = 42;
const int *ptr1 = &i; // 指针ptr1不是常量,其指向的对象i是常量
*ptr1 = 20; // 错误,i是常量,不能修改
int *ptr2 = &i; // 错误,非指针常量不能指向常量
4.2、const指针,通常也叫常量指针,即指针本身是常量。
int i = 42;
int *const ptr1 = &i; // 指针ptr1为常量指针,指针本身为常量
*ptr1 = 20; // 正确,指针指向的对象不为常量,可以修改
int j = 30;
ptr1 = &j; // 错误,指针为常量,不能修改指针
5、顶层const
5.1、顶层const
顶层const表示指针本身是个常量,即const指针。
int i = 42;
int *const ptr = &i; // 不能改变ptr的值,这是一个顶层const
另外,顶层const可以表示任意的对象是常量。
const int j = 10; // 不能改变j的值,这是一个顶层const
5.2、底层const
底层const表示指针所指的对象是一个常量。
const int ci = 30;
const int *ptrCi = &ci; // 不允许改变ptrCi所指的对象的值,这是一个底层const
5.3、指针既可以是顶层const又可以是底层const
const int ci = 30;
const int *const ptrCi = &ci; // ptrCi是个常量,且不允许改变ptrCi所指的对象的值,这是一个底层const,也是一个顶层const
5.4、用于声明引用的const都是底层const
const int ci = 42;
const int &ri = ci; // 用于声明引用的const,都是底层const
5.5、拷贝对象忽略顶层const,但不能忽略底层const
const int i = 42; // 这是一个顶层const
int j = i; // 正确:忽略顶层const
const int *const ci = 10; // 既是顶层const,又是底层const
const int *ptr1 = &ci; // 正确:忽略顶层const
int *ptr2 = &ci; // 错误:不能忽略底层const
6、constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程中就能得到计算结果的表达式。
一个对象或表达式是不是常量表达式,由它的数据类型和初始值共同决定。例如:
int buffSize = 30; // buffSize的值会变,不是常量表达式
int GetBuffSize()
{
return buffSize;
}
const int size = GetBuffSize(); // 尽管buffSize的初始值是字面值常量,但buffSize不是常量表达式;所以size不是常量表达式
// 并且GetBuffSize()的值在运行时才能得到,因此size不是常量表达式
6.1、constexpr变量
C++11标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化。
constexpr int i = 20; // 20是常量表达式
constexpr int j = i + 1; // i + 1是常量表达式
constexpr int sz = GetSize(); // 只有当GetSize()是一个constexpr函数时,才是一条正确的声明语句
提示:一般来说,如果你认定变量是一个常量表达式,那就把它声明为constexpr类型。
6.2、字面值类型
常量表达式的值需要在编译时就得到,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们成为“字面值类型”。
算术类型、引用和指针都属于字面值类型。
IO库、string类型不属于字面值类型,也不能被定义成constexpr。
尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。但定义在函数体外的对象,其地址固定不变,能用来初始化constexpr指针。
6.3、指针和constexpr
在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有限,与指针指向的对象无关。
const int *p = nullptr; // p是一个指向整型常量的指针
constexpr int *q = nullptr; // q是一个指向整数的常量指针
注意:constexpr把它所定义的对象置为了顶层const。constexpr指针既可以指向常量也可以指向非常量。