一 关于const
在创建变量时,有时候我们希望它不能改变,为了满足这一要求我们可以用关键字const对变量的类型加以限定。
1.const类型的对象一旦创建其值就不能改变,所以const对象必须初始化。
const int i = get_size() //正确 运行时初始化
const int j = 42; //正确 编译时初始化
const int k; //错误 k是一个未经初始化的常量
int m = 3;
const int n = m; //正确 m的值赋值给了n
int p = n; //正确 n的值赋值给了p
在不改变const对象操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是const都不重要。
2.const的引用(实际上并不存在常量引用,因为引用不是一个对象)
常量对象可以引用非常量和常量对象,如下:
int a = 2; //非常量对象
const int b = 3; //常量对象
const int &c = a //正确 对非常量对象的引用
const int &d = b //正确 对常量对象的引用
但是非常量对象不能引用常量对象
const int a = 10; //常量对象
int b = a; //错误 非常量不能引用常量
一般来说引用类型与其所引用对象的类型一致,有两种情况例外。第一种是初始化常量引用可以用任意表达式作为初始值。允许为一个常量引用绑定非常量的对象、字面值、甚至是一个表达式。
int i = 42;
const int &r1 = i;
const int &r2 = 42;
const int &r3 = r1 * 2;
但下面是错的。
int &r4 = r1 * 2; //r4是一个普通的非常量引用
在赋值过程中发生了如下过程
double dval = 3.14;
const int &ri = dval;
此处ri引用了一个int型整数,但dval是个双精度浮点数而非整数,编译器把上述代码变成了如下形式:
const int temp = dval; //由双精度浮点数生成一个临时的整型常量
const int &ri = temp; //让ri绑定这个临时量
3.指针和const
指向常量的指针不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14;
double p = 3.15;
double *ptr = π //错误 ptr是一个普通指针
const double *cptr = π //正确
const double *cpt = &p //正确
*cptr = 42; //错误
和常量引用一样,指向常量的指针并没有要求所指向的那个对象是个常量对象,但是不能通过指向常量的指针修改所指向的对象的值。但是不能排除非常量对象通过其他方式修改。如:
p = 3.16 //初始值是3.15
4.const指针
指针是对象而引用不是,允许把指针本身定义为常量。常量指针必须初始化,一旦初始化完成,其值就不能改变。
int errNum = 0;
int * const curErr = &errNum; //curErr将一直指向errNum
const double pi = 3.14;
const double *const pip = π //pip指向一个常量对象的常量指针。
把*放在const关键字之前用以加以说明指针是一个常量,即不变的是指针本身而非指向的那个值。要理解这些声明的含义最行之有效的方法就是从右往左阅读。离curErr最近的是const,表明curErr本身就是常量对象,对象的类型由其余部分确定。声明符中下一个符号是*,意识是curErr是一个常量指针。
指针本身是一个常量,并不意味着不可以修改所指向对象的值。主要是看所指向对象是不是常量,如果是,则不能修改。反之亦然。此例中pi是一个常量,所以不能通过常量指针修改。指向常量的常量指针也不能修改。如:
double pi = 3.14;
const double * const ptr = π
*ptr = 3.15; //错误
尽管pip指向非常量对象pi,但由于其声明是指向常量的指针,所以不能修改pi的值。
5.顶层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指向的是一个普通的整数。
6 总结
const 常量只能赋值给const常量
非const常量既可以赋值给常量也可以赋值给非常量
常量一经初始化就不能修改
参考:《C++ Primer》