主要的限制就只能在 const 类型的对象上执行不改变其内容的操作
在不改变 const 对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是 const 都无关紧要。
int i = 42;
const int ci =i; // 正确:i 的值被拷贝给了 ci
int j = ci; // 正确:ci的值被拷贝给了 j
// 当 ci 去初始化 是时,根本无须在意 ci 是不是一个常量。拷贝一个对象的值并不会改变它,一旦拷贝完成,新的对象就和原来的对象没有什么关系了。
当以编译时初始化的方式定义一个 const 对象时,就如对 bufSize 的定义一样:
const int bufSize = 512; // 输入缓冲区大小
编译器将在编译过种中把用到该变量的地方都替换成对应的值。也就是说编译器会找到代码中所用到 bufSize 的地方 ,然后用 512 替换。
为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用到了 const 对象的文件都必须得能访问到它的初始值才行。要做到这一点,就必须在每一个用变量的文件中都有对它的定义。为了支持这一用法同时避免对同一变量的重复定义,默认情况下,const 对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实赞同时在不同文件中分别定义了的变量。
解决的办法是,对于 const 变量不管是声明还是定义都添加 extern 关键字,这样只需定义一次就可以了:
// file_1.cc 定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn());
// file_1.h 头文件
extern const int bufSize; // 与 file_1.cc 中定义的 bufSize 是同一个
Note: 如果想在多个文件之间共享 const 对象,必须在变量定义之前添加 extern 关键字
总结:
当以编译时初始化的方式定义一个 const 对象时,编译器将在编译过程中把用到该变量的地方都替换成对应的值。
默认情况下,const 对象被设定为仅在文件内有效。当多个文件中出现了同名的 const 变量时,其实等同于在不同文件中分别定义了独立的变量。
如果想在多个文件之间共享 const 对象,必须在变量定义之前添加 extern 关键字。
建议的解决办法是,如果想在多个文件之间共享 const 对象,对于 const 变量不管是声明还是定义都添加 extern 关键字,这样只需定义一次就可以了。如果不共享,就不用加 extern。
可以把引用绑定到 const 对象上, 就像绑定到其他对象上一样,我们称之为对常量的引用。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &r1 = ci; // 正确:引用及期对应的对象都是常量
r1 = 42; // 错误:r1 是对常量的引用
int & r2 = ci; // 错误:施工图让一个非常量引用指向一个常量对象
int i = 0;
const int ci = i;
std::string::size_type ctr = 0;
//const int &const r2 = &i; // "int *"类型不能初始化"cosnt int&"
const int &const r2 = i;
int i = 24;
const int &r1 =i; // 重点:允许将 const int& 绑定到一个普通 int 对象上
const int &r2 = 42; // 正确:r1 是一个常量引用
const int &r3 = r1 * 2; // 正确:r3 是一个常量引用
int &r4 = r1 *2; // 错误:r4 是一个普通的非常量引用,VS语法错误:非常量引用的初始化值必须为左值
引用的类型必须与其所引用对象的类型一致,但用两个例外:
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果可以转换成引用的类型。
允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式
初始化 const 引用总结:
在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果可以转换成引用的类型。
允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式
非常量引用的初始化值必须为左值
常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值。
int i = 42;
int &r1 = i; // 引用 ri 绑定对象 i
const int &r2 = i; // r2 也绑定对象 i,但是不允许通过 r2 修改 i 的值
r1= 0 ; // r1 并非常量,i 的值修改为 0
r2 = 0; // 错误:r2 是一个常量引用
与引用一样,也可能令指针指向常量或非常量。类似于常量引用,指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针
const double pi = 3.14; // pi 是个常量,它的值不能改变
double *ptr = π // 错误:ptr是一个普通指针
const double *cptr = π // 正确:cptr 可以指向一个双精度常量
*cptr = 42; // 错误:不能给 *cptr 赋值
指针的类型必须与其所指对象的类型一致,但是有两个例外:
第一种例外情况是鸡毛令一个指向常量的指针指向一个非常量对象:
double daval = 3.14; // dval 是一个双精度浮点数,它的值可以改变
cptr = & dval; // 正确:但是不能通过 cptr改变dval的值
和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。所谓指向常量的指针公公要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。
**指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。**常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把放在 const 关键字之前用以说明指针是一个常量这样的书着意味,即不变的是指针本身的值而非指向的那个值
int errNumb = 0;
int *const curErr =&errNumb; // curErr 将一直指向 errNumb
const double pi = 3.14159;
const double *const pip = π // pip 是一个指向常量对象的常量指针
如果前所述,指针本身是一个对象,它又可以指向另一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。
非常重要:
- 用名词 顶层 const(top-level const)表示指针本身是个常量,而用名词 底层 const (low_level const) 表示指针所指的对象是一个常量。
- 顶层 const 可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。
- 底层 const 则与指针和引用等复合类型的基本类型部分有关。
- 比较特殊的是,指针类型既然可以是 顶层 const 也可以是 底层 const ,这一点和其他类型相比区别明显:
int i = 0;
int *const p1 = &i; // 不能改变 p1 的值,这是一个顶层 const
const int ci = 42; // 不能改变 ci 的值,这是一个顶层 const
const int *p2 =&ci; // 允许改变 p2 的值,这是一个底层 const
const int *const p3 =p2; // 靠右的 const 是顶层 const,靠左的是底层 const
const int &r = ci; // 用于声明引用的 const 都是底层 const
当执行对象的拷贝操作时,常量是顶层 const 还是底层 const 区别明显。其中顶层 const 不受什么影响:
i = ci; // 正确:拷贝 ci 的值,ci 是一个顶层 const,对此操作无影响
p2 = p3 ; // 正确:p2 和 p3 指向的对象类型相同,p3顶层 const 的部分不影响
执行拷贝操作并不会改变被拷贝对象的值,因此,拷入和拷出的对象是否是常量都没什么影响。
另一方而,底层 const 的限制去不能忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层 const 资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行:
int *p = p3; // 错误:p3 包含底层 const 的定义,而 p 没有
p2 = p3; // 正确: p2 和 p3 都是底层 const
p2 = &i; // 正确:int* 能转换成 const int*
int &r = ci; // 错误:普通的 int& 不能绑定到 int 常量上
const int &r2 = i; // 正确:const int& 可以绑定到一个普通 int 上
p3 既然是顶层 const 也是底层 const,拷贝 p3 时可以不在乎它是一个顶层 const,但是必须清楚它指向的对象得是一个常量。因此,不能用 p3 去初始化 p,因为 p指向的是一个普通(非常量)整数。另一方面,p3 的值可以赋给 p2,是因为这两个指针都是底层 const,尽管p3同时也是一个常量指针(顶层const),仅就这次赋值而言不会有什么影响。
总结:
int i = 0;
int *const p1 = &i; // 不能改变 p1 的值,这是一个顶层 const
const int ci = 42; // 不能改变 ci 的值,这是一个顶层 const
const int *p2 =&ci; // 允许改变 p2 的值,这是一个底层 const
const int *const p3 =p2; // 靠右的 const 是顶层 const,靠左的是底层 const
const int &r = ci; // 用于声明引用的 const 都是底层 const
用于修饰普通变量或对象的 const,它是顶层 const。
用于声明引用的 const 都是底层 const。
指向常量对象的指针常量,它即是顶层与是底层 const。
例子总结:
int i = 0;
const int ci = 42; // 不能改变 ci 的值,这是一个顶层 const
const int &r = ci; // 用于声明引用的 const 都是底层 const
int *const top_pt1 = &i; // 不能改变 p1 的值,这是一个顶层 const
const int *bot_pt2 = &ci; // 允许改变 p2 的值,这是一个底层 const
const int *const top_bot_p3 = bot_pt2; // 靠右的 const 是顶层 const,靠左的是底层 const
const int *bot_p4 = &i; // 允许改变 p4 的值,这是一个底层 const
const int *const top_bot_p1 = &i; // 不允许改变 ccp1 及其内容的值,这是一个既是顶层与是底层 const
const int *const top_bot_ccp2 = top_bot_p1;
// 将 "const int*&" 类型的引用绑定到 "const int* const"类型的初始值设定项时,
// 限定符(注意:是底层const而不是顶层const,因为const变量的引用,都是底层const)被丢弃
const int *&bot_pt_ref = top_bot_p1; // 错误:当执行对象拷贝时,应该考虑底层的const,这里表示非const指针的引用,引用了const指针
const int *const &top_bot_pt_ref = top_bot_p1;