const限定符
当我们不想在程序中改变某个值时,可以使用const
关键字对变量的类型加以限定。
const int bufSize = 512; //输入缓冲区大小
这样就把buffSize
定义成了一个常亮,任何试图对buffSize
进行赋值的行为都会依法错误。故const
对象一旦创建后其值就无法改变,所以const
对象必须初始化。初始值可以是任意复杂的表达式:
const int i = get_size(); //正确,运行时初始化
const int j = 42; //正确,编译时初始化
const int k; //错误,k时一个未经初始化的变量
初始化和const
对象的类型决定了其上的操作。与非const
类型相比,const
类型能完整大部分,主要区别就在于不能执行改变其内容的操作。
默认状态下,const对象仅在文件内有效
使用extern
关键字可用于在多个文件之间共享const对象。
const的引用
可以将引用绑定到const
对象上,称之为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用作修改它所定义的对象:
const int ci = 1024;
const int &r1 = c1; //正确,引用及对应的对象都是常量
r1 = 42; //错误,r1是对常量的引用
int &r2 = ci; //错误,试图让一个非常量引用指向一个常量对象
注:一般将“对const的引用”称作“常量引用”,但严格来说不存在常量引用。因为引用并非对象,无法让引用本身恒定不变。但事实上C++不支持改变引用所绑定的对象,所以从这层面来说所有引用都算常量。
初始化和对const的引用
引用类型必须与引用对象类型一致,但有两个例外。一是在初始化常量引用时允许用任意表达式作为初始值,只要表达式的结果能转化为引用的类型即可。允许一个常量引用绑定非常量的对象、字面值,甚至是一个一般表达式:
int i = 42; //正确
const int &r1 = i; //正确
const int &r2 = 42; //正确
const int &r3 = r1 * 2; //正确
int &r4 = r1 * 2; //错误
让我们把一个常量引用绑定到另一种类型时会发生如下变化:
double dval = 3.14;
const int &r1 = dval;
此处r1
引用了一个int
类型的值,但dval
却是一个双精度浮点数。因此为了确保r1
绑定一个整数,编译器对上述代码进行了调整:
const int temp = dval;
const int &r1 = temp;
r1
绑定了一个临时量(temporary)对象。当r1
不是常量时,C++将此行为归为非法。
对const的引用可能引用一个并非const的对象
常量引用仅对引用可参与的操作做出了限定,对于引用对象本身是不是一个常量未做限定。因此引用的对象也可能是一个非常量,故可通过其他途径改变它的值。
int i = 42;
int &r1 = i;
const int &r2 = i;
r1 = 0; //i==r1==0;
r2 = 0; //错误
指针与const
与引用相同,可也用指针指向常量。类似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值。想要存放常量对象的地址,只能使用指向常量的指针。
const double pi = 3.14; //定义常量pi
double *ptr = π //错误,ptr是一个普通指针
const double *cptr = π //正确,cptr可以至指向一个常量
*cptr = 42; //错误,不能给*cptr赋值
前文提到,指针的类型必须与其所指对象一致,其例外之一就是允许一个指向常量的指针指向一个非常量对象。所谓指向常量的指针仅仅是不能通过该指针改变对象的值。
double val = 3.14;
cptr = &dval
const指针
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定义为常量。常量指针(const pointer)就必须初始化,而且一旦初始化完成,则它的值(即它存放的地址)不能再改变。把*
放在const
关键字之前用于说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值 。
int i = 0; //
int *const ptr = &i; //ptr将一直指向i
const double pi = 3.14; //
const double *const piptr = π //piptr是一个指向常量对象的常量指针
指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全取决于其所指对象的类型。
const double pi = 3.14;
const double *const piptr = π
*piptr = 3.0; //错误,piptrt是一个指向常量的指针
int i = 0;
int *const ptr = &i;
ptr = 0; //正确
顶层const
指针本事是一个对象,它又可以指向另外一个对象。因此指针本身不是常量以及指针所指对象是不是一个常量时两个独立的问题。用名词顶层const(top-level const)表示指针本身是一个常量,用名词底层const(low-level const)表示指针所指对象是一个常量。
更一般的,顶层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
const int &r = ci; //用于声明引用的const都是底层const
当执行拷贝操作时,常量是顶层const还是底层const区别明显。其中,顶层const不受什么印象。
constexpr和常量表达式
常量表达式(const expression)是指不会改变并且在编译过程中就能够得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。后面会提到,C++语言中有几种情况下是要用到常量表达式的。
一个对象是不是常量表达式由它的数据类型和初始值共同决定,例如:
const int max_files = 20; //是常量表达式
const int limit = max_files +1; //是常量表达式
int staff_size = 72; //不是常量表达式
constexpr 变量
C++11标准规定,允许将变量申明为constexpr
类型以便由编译器来验证变量值是否是一个常量表达式,声明为constexpr
的变量一定是一个常量,而且必须使用常量表达式初始化。
字面值类型
常量表达式需要在编译阶段就得到计算,故声明constexpr
时用到的类型必须有所限制。因为这些类型一般比较简答,值也显而易见,就把他们成为“字面值常量”(literal type)。
一个constexpr指针的初始值必须是nullptr
或0
,或者是储存于某个固定地址中的对象。
指针与constexpr
在constexpr申明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr;
constexpr int *q = nullptr;
p
与q
的类型相差甚远,p
是一个指向常量的指针,而q
是一个常量指针,其中的关键在于constexpr
把它所定义的对象设置为了顶层const
。
与其他常量指针类型相似,constexpr指针
既可以指向常量,也可以指向一个非常量。