一、const 的初始化
const 可以限定变量的类型,创建一种类型的常量,创建后便不能够被改变。
因为 const 对象创建后不能够改变其值,因此 const 对象在创建时必须初始化:
const int i = 42; //正确,编译时初始化
const int k; //错误,未初始化常量
const int ci = i; //正确,将i的值拷贝给ci
默认状态下,const 对象仅在文件内有效。
要想只在一个文件定义 const 对象,而在多个文件中声明并使用它。
解决办法是,对于 const 变量不管是声明还是定义都添加 extern 关键字,这样就只需要定义一次就行了:
//file_1.cpp定义并初始化一个常量,该常量便能够在其他文件访问
extern const int bufSize = 512;
//file_1.h头文件
extern const int bufSize; //与file_1.cpp文件中定义的 bufSize 是同一个
file_1.h头文件中的声明也由 extern 限定,作用是指明 bufSize 并非本文件独有,它的定义在其他问中间出现。
二、const 的引用
引用既可以绑定到 const 对象,也可以绑定到非 cons t对象,从代码开始分析:
int i = 512;
const int ci = 1024;
const int &r1 = ci; //正确,引用还有其绑定对象都是 cosnt
r1 = 2048; //错误,r1 是对 const 的引用
const int& r2 = i; //正确,允许将 const int& 绑定到普通 int 对象上
const int& r3 = 42; //正确
const int& r4 = r2 * 2; //正确
int &r5 = ci; //错误,让非 const 引用绑定到 const 对象上
从上面的代码中可以看出,允许将一个对 const 的引用绑定带非 const 的对象,字面值,甚至是表达式。
但是,在 const 引用绑定到非 cosnt 的对象时,不能够通过该引用去修改其绑定的对象。
三、const的指针
与引用类似,指针也可以指向 const 对象或非 const 对象。指向常量的指针不能够用于改变其所指向的对象。还是从代码开始分析:
const double pi = 3.14; //pi是个常量,它的值不能改
double* ptr = π //错误,pi 是 const 对象,要用 ptr 只是个普通指针
const double* cptr = π //正确,const指针 指向 const 对象
*cptr = 3.14159; //错误,不能通过 cptr 修改其指向的对象
cosnt double* const cpi = π //既不能修改其指向的对象,也不能修改指针 cpi 的指向
从上面的代码可以看到,指针是一个对象,常量指针声明后必须初始化,并且初始化后,不能再修改它的值。并且对于 const 对象,要用常量指针才能够指向它。
在这里 const 既能修饰其指针本身指向,也能修饰其指向的对象。修饰的是谁,就要根据cons t与*的左右来区分了。
四、顶层 const
如上面所述,指针本来是对象,它又可以指向另一个对象。所以这里用**顶层 const 和底层 const **去区分其指针本身还是所指对象,谁是常量的问题。顶层 const 表示指针本身是个常量,而底层 const 表示指针所指向的对象是 const 。
顶层 const 可以表示任意的对象是常量,对任何数据类型都是用。底层 const 则对指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层 const 也可以是底层 const 。
int i = 0;
int* const p1 = &i; //不能修改p1的值,顶层const
const int ci = 42; //不能修改ci的值,顶层const
const int* p2 = &i; //不能通过p2修改ci的值,可以改变p2的值,底层const
int* p3 = p2; //错误,p2是底层const,而p3不是const指针
从上面的代码可以看到,在执行对象的拷贝操作时,顶层 const 不受什么影响;而底层 const ,却有些不可忽视的限制,拷贝和拷出的对象必须有相同的底层 const 资格,或对象的数据类型能够转换,非 const 可以转换成const(p2 = &i),但反之却不行(p3 = p2)。
五、constexpr和常量表达式
常量表达式是指值不会改变并且在编译过程就能够得到的表达式。显然,字面值属于常量表达式,用常量表达式初始化的 const 对象也是常量表达式。C++语言中有几种情况下也是要用到常量表达式的。
const int max_files = 20; //常量表达式
const int limit = max_files + 1; //常量表达式
int staff_size = 27; //非常量表达式
虽然 27 是常量,但是其数据类型只是普通 int 而非 const int,所以它不是常量表达式。
在一个复杂系统中,很难(几乎肯定不能)分辨一个初始值到底是不是常量表达式,虽然可以定义一个 const 变量并把它的初始值设为我们认为的某个常量表达式,但在实际使用时,尽管要求如此,但常常发现初始值并非常量表达式的情况。
C++新标准规定,允许将变量声明为 constexpr 类型以便由编译器来验证变量的初始值是否是一个常量表达式。声明为 constexpr 的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int mf = 20; //正确,常量表达式
constexpr int limit = mf + 1; //正确,常量表达式
constexpr int sz = size(); //不一定,只有当size是constexpr函数时,才正确
尽管不能使用普通函数作为 constexpr 变量的初始值,但是新标准允许一种特殊的 constexpr 函数,这种函数应该足够简单以使得编译时就可以计算其结果。
强调一点,在 constexpr 声明中如果定义了一个指针,限定符 constexpr 仅对指针本身有效,与指针所指的对象无关:
const int* p = nullptr; // p 是一个指向 int 常量的指针
constexpr int* q = nullptr; // q 是一个指向 int 变量的常量指针