一、基本描述
定义变量时的限定符,表示变量值不能改变。
const int bufSize = 512; bufSize = 512; // 错误:试图向const对象写值
由于const一旦创建就不可更改,所以const对象必须初始化(否则定义一个默认值且不可修改的变量没有任何意义)。
const int i = get_size(); // 正确 const int j = 42; // 正确 const int k; // 错误:未初始化
使用值传递初始化时,被初始化的对象是否为const与初始化对象是否为const无关。也即,const对象与非const对象可以互为初始化。
int i = 0; int j = i; // 正确,非const初始化非const const int k = i; // 正确,非const初始化const const int x = 0; int y = x; // 正确,const初始化非const const int z = x; // 正确,const初始化const
二、const初始化引用时的例外
1、C++规定引用类型必须与被引用对象一致:
int i = 2; double &j = i; // 错误:引用类型与对象类型不一致
2、C++还规定引用必须绑定到左值:
注:左值和右值的辨别方法是,能取地址的是左值。
int &i = 2; // 错误:不允许用右值初始化 int &j = a * 2 // 错误:不允许用表达式初始化,实际上表达式(a*2)是右值
3、但是用const初始化引用时会有例外:
const引用类型与对象类型不一致(但可以转化):
int i = 2; const double &j = i; // 正确:j是常量引用
const引用绑定到一个非左值上(类型一致或可以转化):
const int &i = 2; // 正确:i是常量引用 const int &j = a * 2 // 正确:j是常量引用
原因在于,const引用将会额外创建一个临时变量,并绑定上去。
C++支持这种做法的目的在于,既然不能通过const引用修改对象值,那么额外创建一个常量和直接绑定对象并没有什么区别,所以干脆让const引用支持这种非常规做法。
三、顶层const和底层const
通常在指针/引用与const符同时使用时会用到这个概念。修饰指针本身的const称为顶层const,修饰指针所指向对象的const称为底层const。底层const与顶层const是两个互相独立的修饰符,互不影响。
1、const与指针
指针本身是一个独立的对象,它又可以指向另一个对象。所以指针和const同时使用时,有两种情况:
int i = 0; int *const j = &i; // 指针j指向i,const修饰指针j本身,所以j的地址值不允许修改,但可以通过j修改i的值 const int *k = &i; // 指针k指向i,const修饰k指向的i,所以k的地址值可以修改,但不可以通过k修改i的值
2、const与对象
所以使用引用时,就只需考虑是否为底层const:
int i = 0; const int &j = i; // j为绑定到i的const引用,不允许使用j来修改i
3、其它
(1)可以将底层const的指针(或引用)指向(或绑定)到非const对象,但不允许非底层const的指针(或引用)指向(或绑定)到const对象。 (即:const对象不允许通过任何方式(指针/引用)被修改。)
(2)修饰值本身的const均为顶层const:
const int i = 0; // 顶层const;
4、const与函数
(1)值传递的const形参:
void fcn(const int i) { /* ... */ }
这个函数中,变量i为值传递形参,根据值传递的初始化规则,形参i是否为const与传入的实参是否为const是完全无关的。这里的const仅表示i在函数体中不允许修改。如下的调用均为合法调用:
int x = 0; fcn(x); const int y = 0; fcn(y);
因为值传递的const形参在调用上与非const形参没有区别(大概是指,无论形参是否为const,实参都不会被修改。),所以仅仅使用const无法区分参数类别,所以无法实现函数重载,如下的重载是错误的:
void fcn1(const int i) { /* ... */ } void fcn1(int i) { /* ... */ } // 错误:重复定义函数,不能实现重载
(2)const指针/引用的形参
对于顶层const的指针,与上一小节一样,其const性质与实参无关,顶层const仅表示指针/引用本身在函数体中不允许修改。所以我们只需要讨论底层const的指针/引用。
void fcn2(const int &x) { /* ... */ } // 接受const或非const的int引用,但是不允许通过x修改传入的对象 void fcn2(const int *y) { /* ... */ } // 接受const或非const的int指针,但是不允许通过y修改传入的对象
如上两个函数都定义了底层const的形式参数,它们可以接受const或非const对象,但是不能在函数体内修改这些对象。所以如下的调用都是合法的:
int i = 0; fcn2(i); // 正确:调用第一个函数 fcn2(&i); // 正确:调用第二个函数 const int j = 0; fcn2(j); // 正确:调用第一个函数 fcn2(&j); // 正确:调用第二个函数
由于底层const描述实参性质(不允许在调用函数内部被修改),可以在调用时区分const,所以使用底层const的指针/引用可以实现函数重载:
void fcn3(int &x) { /* ... */ } void fcn3(const int &x) { /* ... */ } // 新函数,作用于const的引用
所以可以分别调用两个函数:
int i = 0; fcn3(i); // 正确:调用第一个函数 const int j = 0; fcn3(j); // 正确:调用第二个函数
注意,当传递非常量对象时,编译器会优先调用非常量版本的函数。
(3)总结
- 顶层const的形式参数不能实现函数重载,但底层const形参可以
- 当函数不修改参数值时,尽可能将形式参数定义为(底层)const参数。一方面,(底层)const参数可以保护参数对象;另一方面,因为(底层)const参数可以接受常量与非常量对象,但非(底层)const参数只能接受非常量对象。
https://zhuanlan.zhihu.com/p/37514756
浙公网安备 33010602011771号