一、关键点
- 说法:常量对象 = const 对象 = const
- const 对象必须初始化
利用一个对象去初始化另外一个对象,则它们是不是 const 都无关紧要:
int i = 42; const int ci = i; int j = ci;尽管ci是整型常量,但ci的常量特征仅仅在执行改变ci的操作时才会发挥作用。
- 可以把引用绑定到 const 对象上,我们称之为对常量的引用
- 对 const (对象)的引用:简称为常量引用,但这只是个简称,因为不存在常量引用,因为引用不是一个对象,所以我们无法让引用本身恒定不变;我们只是不能修改它绑定的对象
- 该引用绑定的可以是常量对象也可以是非常量对象,但不能让一个非常量引用指向一个常量对象
【常量引用的初始值】
我们在引用中提过,“引用只能绑定在对象上,而不能与字面值或某个表达式的计算结果绑定在一起”,即引用的类型必须与其所引用的对象的类型一致。
但在初始化常量引用时,允许用任意表达式作为初始值(即可以为一个常量引用绑定非常量的对象、字面值等),只要该表达式的结果能转换成引用的类型即可。
下面这一段代码是合法的:
int i = 42; const int &r1 = i; //允许将常量引用r1绑定到一个普通int对象上 const int &r2 = 42; //r2是一个常量引用,若是“int &r2 = 42;”则非法 const int &r3 = r1 * 2;
上面的代码块中让r1绑定(非常量)对象 i,这是合法的,只是不允许通过r1修改 i 的值,但 i 是一个非常量,故其值仍允许通过其他途径来改变。
【指针和const】
1. 指向常量的指针
- 不能用于改变其所指对象的值
- 允许令一个指向常量的指针指向一个非常量对象
示例:const double *cptr = &(非)常量对象; //cptr是一个指向double型常量的指针,cptr本身不是常量
备注:所谓指向常量的指针或引用,不过是指针或引用“自以为是”罢了,它们觉得自己指向了常量(可能指向了非常量对象),所以自觉地不去改变所指对象的值。
2. 常量指针
- 必须初始化,且初始化完成后,它的值(即存放在指针中的那个地址)就不能改变了
- 可以通过常量指针修改其所指对象的值,只要其所指对象不是常量
示例:int *const ptr = &i; //ptr是一个常量指针,它指向一个 int 对象
示例:const int *const ptr2 = &r; //ptr2是一个常量指针,它指向一个 int 型常量
备注:区分指针究竟是常量指针还是指向常量的指针,就是将声明语句从右往左读,越靠近指针的修饰符影响越大。
二、顶层const & 底层const
- 顶层const:指针本身是一个常量,对于其他类型也是如此
- 底层const:指针所指的对象是一个常量
int i = 0; int *const p1 = &i; //顶层const const int ci = 42; //顶层const const int *p2 = &ci; //底层const const int *const p3 = p2; //靠左的是底层const,靠右的是顶层const const int &r = ci; //底层const
上面的代码块中表示:p1、ci是顶层const,p2、r是底层const,而p3既是顶层const也是底层const。
执行对象的拷贝时,常量是顶层const不受影响,因为拷贝并不会改变被拷贝对象的值,所以拷入和拷出的对象是否是常量并没有什么影响。
i = ci; //正确 p2 = 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),但仅就这次赋值而言不会有什么影响。
【补充】
一般的,
- 顶层const:可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。
- 底层const:与指针和引用等复合类型的基本类型部分有关。
特殊的,
- 指针类型既可以是顶层const 也可以是底层const 。
三、顶层const与函数调用
- 实参初始化形参时会忽略掉顶层const(即形参的顶层const被忽略掉了)
通俗地说,顶层const不影响传入函数的对象,一个拥有顶层const的形参无法和另外一个没有顶层const的形参区分开来。
于是,我们不能同时定义如下函数:
void func(const int x) // 顶层const { cout << x << endl; } /* void func(int x) { cout << x << endl; } */
解释:因为顶层const将被忽略,我们传入两个func函数的实参可以完全一样,这样编译器将不知调用哪个函数。
四、底层const与函数重载
如果形参是某种类型的指针或引用,则通过区分其指向的是常量对象还是非常量对象可以实现函数重载,此时的const是底层的
void func(int &c) { cout << "no const" << endl; } void func(const int &c) { cout << "const" << endl; } int main() { int a = 10; const int b = 10; func(a); // 打印“no const” func(b); // 打印“const” return 0; }
2018-3-22更新
【直观的 const 实例】
#include <iostream> using namespace std; void testConst1() { int a = 10; const int b = 20; const int &x1 = a; // 让常量引用指向非常量对象,但是不允许通过x1修改a的值(a的值可通过其他途径改变) const int &x2 = b; // 让常量对象指向常量对象 // int &r = b; // 报错:[Error] invalid initialization of reference of type 'int&' from expression of type 'const int' cout << " 测试一 x1 = " << x1 << " x2 = " << x2 << endl; a = 30; // b = 30; // 报错:[Error] assignment of read-only variable 'b' cout << "x1 = " << x1 << " x2 = " << x2 << endl; } void testConst2() { int a = 10; double b = 3.14; const int &x1 = a; int &r1 = a; const int &x2 = 10; // int &r2 = 10; // 报错:[Error] invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int' const int &x3 = x2 * 2; // int &r3 = x1 * 2; // 报错:[Error] invalid initialization of non-const reference of type 'int&' from an rvalue of type 'int' const int &x4 = b; // 虽然可行,但违背了引用的初衷,因为此处会生成一个临时量,而x4恰是绑定到该临时量上了 const int &x5 = 3.14; cout << " 测试二 x1 = " << x1 << " x2 = " << x2 << " x3 = "<< x3 << " x4 = " << x4 << endl; } /* 对常量的引用和指向常量的指针都没有规定其所指的对象必须是一个常量 * 前者表示:不能通过该引用改变对象的值 * 后者表示:不能通过该指针改变对象的值 */ void testConst3() { const int a = 10; int b = 5; // int *p = &a; // 报错:[Error] invalid conversion from 'const int*' to 'int*' [-fpermissive] const int *p1 = &a; // p1是指向常量的指针 const int *p2 = &b; cout << " 测试三 *p1 = " << *p1 << " *p2 = " << *p2 << endl; // *p1 = 20; // 报错:[Error] assignment of read-only location '* p1' // *p2 = 20; // 报错:[Error] assignment of read-only location '* p2' p1 = p2; cout << "*p1 = " << *p1 << " *p2 = " << *p2 << endl; } void testConst4() { const int a = 10; int b = 5, c = 2; // int *const p1 = &a; // 报错:[Error] invalid conversion from 'const int*' to 'int*' [-fpermissive] int *const p2 = &b; // p2是常量指针 int *const p3 = &c; const int *const p4 = &a; // p4是指向常量对象的常量指针 const int *const p5 = &b; cout << " 测试四 *p2 = " << *p2 << endl; *p2 = 20; // p2 = p3; // 报错:[Error] assignment of read-only variable 'p2' // *p5 = 20; // 报错:[Error] assignment of read-only location '*(const int*)p5' cout << "*p2 = " << *p2 << endl; } int main() { testConst1(); testConst2(); testConst3(); testConst4(); return 0; }
运行结果: