有时候我们需要用一个变量存储一个不变的值,例如缓冲区的大小、数组的元素个数等等。好处是我们可以根据需求很容易对其进行调整,并且也能防止程序不小心修改了这个值,此时可以使用关键字const对变量的类型加以限定。
初始化
const对象一旦创建后,其值就无法修改,因此必须初始化。初始值可以是任何复杂的表达式,包括常量和变量。
1 | int i = 10; // i是一个已初始化的变量 |
输出结果如下:
1 | 6 |
文件作用域
当以编译时初始化的方式定义一个const对象时,如下
1 | const int bufSize = 512; |
编译器将在编译过程中把用到该变量的地方都替换成对应的值。为了执行替换,编译器必须知道变量的初始值,如果程序包含多个文件,则每个用了const对象的文件必须能够访问到它的初始值。因此,就必须在每个用到变量的文件中都有对它的定义,为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量,其实等同于在不同文件中分别定义了独立的变量。
main.cc中声明了func()函数,其定义在a.cc中,初始化bufSize = 512,在main函数中打印它的值,并调用func()。
1 | // main.cc |
a.cc中定义了func()函数,其输出定义在本文件中的const变量bufSize。
1 | // a.cc |
命令行编译并执行:
1 | rda@pa ~/Downloads> g++ a.cc main.cc -g -o main |
可见,定义在不同文件中的同名const对象是不同的。
若需要在文件之间共享const变量,则对于其声明和定义都添加extern关键字,这样只需定义一次就可以了。
为main.cc中的bufSize添加extern关键字。
1 | // main.cc |
为a.cc中的bufSize添加extern关键字,并舍去其初始值。
1 | // a.cc |
再次编译并执行:
1 | rda@pa ~/Downloads> g++ a.cc main.cc -g -o main && ./main |
此时bufSize在两个文件之间共享。
const的引用
可以把引用绑定到const对象上,称之为对常量的引用。对常量的引用无法修改它所绑定的对象:
1 | const int ci = 1024; |
一般来说,引用的类型必须与其所引用的对象的类型一致,但当初始化常量引用时允许使用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。允许一个常量引用绑定到非常量的对象、字面值甚至是一般表达式:
1 | int i = 42; |
当一个常量引用绑定到另一种类型上时,如下:
1 | double dval = 3.14; |
编译器把上述代码变成如下格式:
1 | const int temp =dval; // temp的值为3 |
这种情况下,ri绑定到了一个临时量(temporary)对象,所谓的临时对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。
指针和const
可以令指针指向常量或非常量。指向常量的指针不能用于改变其所指的对象的值,想要存放常量对象的地址,只能使用指向常量的指针。
1 | const double pi = 3.14; |
指针的类型必须与其所指对象的类型一致,但是有两个例外,第一个例外是,允许一个指向常量的指针指向一个非常量对象。
1 | double dval = 3.14; |
所谓指向常量的指针或引用,仅仅是自身无法修改所指向的对象或所绑定的对象,而没有规定那个对象的值无法修改。
指针是对象,而引用不是,因此允许把指针本身定义为常量,即常量指针,意味着不变的是指针本身的值而非指向的地址中存储的值。和其他常量一样,常量指针必须初始化,且一旦初始化完成,则它的值(存放在指针中的地址)不能再改变。
1 | int errNumb = 0; |
顶层const和底层const
对于指针来说,其本身是一个对象,且指向另一个对象(存储该对象的地址),因此指针本身是不是一个常量以及其指向的是不是一个常量对象是独立的。名词顶层const表示指针本身是个常量,而底层const表示指针所指向的对象是一个常量。更为一般的,顶层const可以表示任意的对象是一个常量。
1 | int i = 0; |
拷贝时,两个对象必须具有相同的底层const资格,或者两个对象的数据类型可以转换,一般来说,非常量可以转换成常量,反之则不行。
1 | int *p = p3; // 错误,p3和p的底层const不同且常量无法转换为非常量 |
重载函数中的const
实参初始化形参时会忽略顶层const,当形参含有顶层const时,传递给它常量对象或非常量对象都是可以的,因此如下重载函数会发生错误。
1 |
|
但底层const可以用来区分不同的重载函数,此时,常量对象调用底层const对应版本的函数。
1 | const int i = 3; |
输出结果为:
1 | call const fcn, i = 3 |
注意,如下情况编译不会报错,但运行时会有错误。
1 |
|
无法得知调用的是哪个版本的重载函数。