文章目录
const限定符
const int bufSize = 512; //输入缓冲区大小
这样就把bufSize定义成了一个常量。任何试图为bufSize赋值的行为都将引发错误:
bufSize = 512; //错误:试图向const对象写值
因为const对象一旦创建后其值可以是任意复杂的表达式:
const int i = get_size(); //正确:运行时初始化
const int j = 32; //正确:编译时初始化
const int k; //错误:k是一个未经初始化的常量
初始化和const
与非const类型所能参与的操作相比,const类型的对象能完成其中大部分,但也不是所有的操作都适合,主要的限制就是只能在const类型的对象上执行不改变其内容的操作。例如,const int和普通的int一样都能参与算术运算,也都能转换成一个布尔值,等等。
在不改变const对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要:
int i = 32;
const int ci = i; //正确:i的值被拷贝给了ci
int j = ci; //正确:ci的值被拷贝给了j
尽管ci是整型常量,但无论如何ci中的值还是一个整型数。ci的常量特征仅仅在执行改变ci的操作时才会发挥作用。当用ci去初始化j时,根本无须在意ci是不是一个常量。拷贝一个对象的值并不是改变它,一旦拷贝完成,新的对象就和原来的对象没什么关系了。
默认状态下,const对象仅在文件内有效
TODO
const的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用。与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &r1 = ci; //正确:引用及其对应的对象都是常量
r1 = 32; //错误:r1是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
因为不允许直接为ci赋值,当然也就不能通过引用去改变ci。因此,对r2的初始化是错误的。假设该初始化合法,则可以通过r2来改变它引用对象的值,这显然是不正确的。
初始化和对const的引用
引用的类型必须与其引用对象的类型一致,但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式:
int i = 32;
const int &r1 = i; //允许将const int&绑定到一个普通int对象上
const int &r2 = 32; //正确:r1是一个常量引用
const int &r3 = r1 * 2 //正确:r3是一个常量引用
int &r4 = r1 * 2; //错误:r4是一个普通的非常量引用
要想理解这种例外情况的原因,最简单的办法就是弄清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:
double dval = 3.14;
const int &ri = dval;
此处ri引用了一个int型的数。对ri的操作应该是整数运算,但dval却是一个双精度浮点数而非整数。因此为确保让ri绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; //由双精度浮点数生成一个临时的整型常量
const int &ri = temp; //让ri绑定这个临时量
在这种情况下,ri绑定了一个临时量对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。C++程序员们常常把临时量对象简称为临时量。
当ri不是常量时,如果执行了类似于上面的初始化过程将带来什么样的后果。如果ri不是常量,就允许对ri赋值,这样就会改变ri所引用对象的值。注意,此时绑定的对象是一个临时量而非dval。程序员既然让ri引用dval,就肯定想通过ri改变dval的值,否则干什么要给ri赋值呢?如此看来,既然大家基本上不会想着把引用绑定到临时量上,C++语言也就把这种行为归为非法。
对const的引用可能引用一个并非const的对象
必须认识到,常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值:
int i = 32;
int &ri = i; //引用ri绑定对象i
const int &r2 = i; //r2也绑定对象i,但是不允许通过r2修改i的值
r1 = 0; //r1并非常量,i的值修改为0
r2 = 0; //错误:r2是一个常量引用
r2绑定(非常量)整数i是合法的行为,然而,不允许通过r2修改i的值。尽管如此,i的值仍然允许通过其他途径修改,既可以直接给i赋值,也可以通过像r1一样绑定到i的其他引用来修改。
指针和const
与引用一样,也可以令指针指向常量或非常量。类似于常量引用,指向常量的指针不能用于改变其所指向对象的值。要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14; //pi是个常量,它的值不能改变
double *ptr = π //错误:ptr是一个普通指针
const double *cptr = π //正确:cptr可以指向一个双精度常量
*cptr = 32; //错误:不能给*cptr赋值
指针的类型必须与其所指对象的类型一致,但是有两个例外,第一种例外情况是允许另一个指向常量的指针指向一个非常量对象
const指针
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。常量指针必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,既不变的是指针的值而非指向的那个值:
int errNumb = 0;
int *const curErr = &errNumb;//curErr将一直指向errNumb
const double pi = 3.1415;
const double *const pip = π //pip是一个指向常量对象的常量指针
要想弄清楚这些声明的含义,最行之有效的方法是从右向左阅读。此例中离curErr最近的符号是const,意味着curErr本身是一个常量对象,对象的类型由声明符的其余部分确定。声明符中的下一个符号是*,意思是curErr是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。与之相似,我们也能推断出,pip是一个常量指针,它指向的对象是一个双精度浮点型常量。
指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全依赖于所指对象的类型。例如,pip是一个指向常量的常量指针,则不论是pip所指向的对象值还是pip自己存储的那个地址都不能改变。相反的,curErr指向的是一个一般的常量整数,那么就完全可以用curErr去修改errNumb的值。
*pip = 2.27;//错误:pip是一个指向常量的指针
if (*curErr) {
errorHandler();
*curErr = 0;//正确:把curErr所指的对象的值重置
}