const指向变量:
有时我们希望定义这样一种变量,它的值不能被改变。为了满足这一要求,可以用关键字const对变量的类型加以限制。
const int bufSize = 512;
bufsize = 512; // 错误:试图向const对象写值
- 因为const对象一旦创建后其值就不能再改变,所以const对象必须初始化
const int i; // 错误:i是一个未经初始化的常量
const类型所能参与的操作原则:只能在const类型的对象上执行不改变其内容的操作。
- 例如,利用一个对象去初始化另一个对象,此时,都未改变const对象的值。
int i = 42;
const int ci = i; // 正确:i的值被拷贝给了ci
int j = ci; // 正确:ci的值拷贝给了j
默认状态下,const对象仅在文件内有效
当以编译时初始化的方式定义一个const对象时,编译器将在编译过程中把用到该变量的地方都替换成对应的值。为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须能够访问到它的初始值才行。要做到这一点,必须在每个用到变量的文件中都有对它的定义。为了支持这一点,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。
- 某些时候有种const变量,它的初始值不是一个常量表达式,但又要求在文件间共享。我们想让这类const对象像其它非常量对象一样,只在一个文件定义const,而在其它多个文件中声明并使用它。解决办法是对于const变量,不管是声明还是定义,都添加extern关键字,这样只需要定义一次即可。
// 在dingyi.cpp内定义const对象
int fn()
{
return 6;
}
extern const int buf = fn();
// 在Hconst.h内声明
extern const int buf;
// const.cpp
#include<iostream>
#include"Hconst.h"
using namespace std;
int main()
{
cout << buf <<endl;
}
输出结果:
const的引用
可以把引用绑定到const对象上,就像绑定到其它对象上一样,我们称之为对常量的引用。对常量的引用不能被用作修改它所绑定的对象。
const int ci = 1024;
const int &r1 = ci; // 正确:引用及其对应的对象都是常量
r1 = 42; // 错误:r1是对常量的引用
int &r2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
总结:不能给常量的引用赋值,试图改变常量的引用值是错误的;非常量引用不能指向常量对象,因为不允许直接为ci(常量)赋值,当然也不允许通过引用去改变ci(常量)。
const引用的初始化
引用的类型必须与其所引用对象的类型一致,例外情况是,在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值、甚至是一个表达式。
int i = 42;
const int &ri = i; //允许将const int & 绑定到一个普通int对象上
const int &r2 = 42; //正确: r1是一个常量引用
const int &r3 = ri*2; //正确: r3是一个常量引用
int &r4 = r1*2; //错误:r4是一个普通的非常量引用
为什么const的引用就可以用任意表达式作为其初始值,但是普通的左值引用就必须要保证引用与之绑定的对象严格匹配,且引用绑定的对象不能是字面值或者某个表达式的结果呢?
double dval = 3.14;
const int &ri = dval;
此处ri引用了一个int型的数,对ri的操作应该是整数运算,但dval却是一个双精度浮点数而非整数。为了确保让ri绑定一个整数,编译器把上述代码变成了如下代码:
double dval = 3.14;
const int &temp = dval; //由双精度浮点数生成一个临时的整型常量
const int &ri = temp; // 让ri绑定这个临时量
这种情况下ri绑定了一个临时量对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。
当ri不是常量时,就允许对ri赋值,这样就会改变ri所引用对象的值。此时绑定的对象是临时量而非dval。既然让ri引用dval,就肯定想通过ri改变dval的值,但此时就产生了矛盾。
所以,左值引用所绑定的对象必须和引用类型完全相同;但常量引用就不受影响,因为常量引用的值不能被改变。
对const的引用可能引用一个并非const的对象
const的引用仅对引用本身参与的操作做出了限制,对于引用所绑定的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其它途径改变它的值。
int main()
{
int i = 42;
int &r1 = i; // 引用r1绑定i
const int &r2 = i; // r2也绑定对象i,但不允许通过r2修改i的值
cout << r2 << endl; // 此时r2即为i的初始值42
r1 = 0;
cout << r2 << endl; // 通过引用r1改变i的值,此时常量引用的值被间接改为0
}
输出结果:
指向常量的指针
即,令指针指向常量,指向常量的指针不能用于改变所指对象的值。
我们知道,指针的类型必须与其所指对象的类型一致,但允许令一个指向常量的指针指向一个非常量对象
const double pi = 3.14; // pi是常量,它的值不能被改变
double *ptr = π // 错误:ptr是一个普通指针
const double *cptr = π // 正确: cptr可以指向一个双精度常量
*cptr = 1024; // 错误:不能给*cptr赋值
double dval = 3.14;
cptr = &dval; // 正确,但是不能通过cptr改变dval的值
总结:指向常量的指针,它的指向可以是常量对象,也可以是非常量对象,如果指向是非常量对象,可以改变常量指针的指向,但是不能改变常量指针所指对象的值,但是那个对象的值可以通过其它途径改变。
const指针(常量指针)
指针是对象而引用不是,因此就像其它对象类型一样,允许把指针本身定位常量。常量指针必须初始化,而且一旦初始化,它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用来说明指针是一个常量。
总结:也就是指针的值不变,值所指向的对象可以变。刚才讲的指向常量的指针,指的是指针变量不是常量,可以随意赋值,但是它指向的值不能随意赋值改变。
int errNumb = 0;
int *const curErr = &errNumb; // 指针为常量不可改变,但指针所指的对象可以改变
const double pi = 3.14;
const double *const pip = π // 指针是常量不可改变,指针所指的对象也是常量,也不可改变
顶层const
指针本身是个对象,它又可以指向另一个对象。因此,指针本身是不是常量和指针所指的是不是常量就是两个相互独立的问题。
用顶层const表示指针本身是个常量。用底层const表示指针所指的对象是一个常量。
更一般的,顶层const可以表示任意的对象是常量,对任何数据类型都适用。底层const则与指针和引用等复合类型的基本类型部分有关。
比较特殊的是,指针类型既可以是顶层const也可以是底层const。
constexpr和常量表达式
常量表达式指值不会改变并且在编译过程中就能得到计算结果的表达式。用常量表达式初始化的const对象也是常量表达式。一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定,例如:
// 是常量表达式
const int max_files = 20;
const int limit = max_files + 1;
// 不是常量表达式
int staff_size = 27;
const int sz = get_size(); // 尽管sz本身是一个常量,但它的具体值直到运行时才能获取到,所以不是常量表达式
constexpr变量
在复杂系统中,很难分辨一个初始值是不是常量表达式,当然可以定义一个const变量并把它的初始值设定为我们认为的某个常量表达式,但是实际使用中,却发现初始值并非常量表达式。
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
// 是
constexpr int mf = 20;
constexpr int limit = mf+1;
// 不是
constexpr int sz = size(); // 只有当该函数是constexpr函数时才正确
指针和constexpr
constexpr指针的初始值必须是nullptr,但它们的初始值必须是nullptr或0,或者是存储于某个固定地址中的对象。看例子:
const int *p = nullptr; // p是一个指向整形常量的指针
constexpr int *q = nullptr; // q是一个指向整形的常量指针
与const指针类似,constexpr指针既可以指向常量也可以指向一个非常量:
int j = 0;
constexpr int i = 42;
constexpr const int *p = &i; //p是常量指针,指向整形常量i
constexpr int *pi = &j; //pi是常量指针,指向整数j
const函数形参
当形参是const时,实参对形参的初始化原则遵循const变量的初始化。
但要注意,当函数的形参为const的int型和int型形参时,对形参的初始化没有区别,所以像这样的函数就重复定义了:
void fnc(const int i){...}
void fnc(int i){...}
尽量使用常量引用
在使用引用类型的形参时,如果要求实参不被改变,尽量把形参设置为常量的引用,防止把实参的值改变了。还有一个原因,使用引用而非常量引用极大的限制实参的类型,比如常量对象、字面值或者需要类型转换的实参。
const成员函数
形式上:类在声明时,成员函数声明的形参表后面跟了const。像这样:
bool same_isbn(const Sales_item &rhs) const
{
return isbn == rhs.isbn;
}
作用:const 改变了隐含的 this 形参的类型。在调用 const成员函数时,隐含的 this 形参将是一个指向调用该函数的对象的 const 类名* 类型的指针。
用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。因此,const成员函数只能读取而不能修改调用它们的对象的数据成员。
const 对象、指向 const 对象的指针或引用只能用于调用其 const 成员函数,如果尝试用它们来调用非 const 成员函数, 则是错误的。
*参考《C++Primer》总结的知识点