const:定义一种变量,它的值不能被改变
参考:《C++ primer》
初始化
const对象一旦被创建,值不能再改变,所以const对象必须初始化!const int i = get_size(); //正确:运行时初始化 const int j = 42; //正确:编译时初始化,编译器会在编译过程中用到该变量的地方都替换成对应的值 const int k; //错误:k是一个未经初始化的常量
如果利用一个对象去初始化另一个对象,则它们是不是const都无关紧要!
int i = 42; const int ci = i; //正确:i的值被拷贝给了ci,ci的常量特性仅仅在执行改变ci的操作时才会发挥作用 int j = ci; //正确:ci的值被拷贝给了j,无需在意ci是不是一个常量,拷贝完成,新的对象就和原来的对象没什么关系了
const对象的有效范围
默认状态下,const对象被设定为仅在文件内有效。
当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。如果想要让const变量在不同文件中被共享,也就是说,只在一个文件中定义const,在其他多个文件中声明并使用它。
解决的方法是:对于不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了。//file_1.cc 定义并初始化看一个常量,该常量能被其他文件访问 extern const int bufSize = fcn(); //必须用extern加以限定,使其被其他文件使用 //file_1.h头文件 extern const int bufSize; //与file_1.cc中定义的bufSize是同一个,用extern致命bufSize并非本文件所独有,它的定义将在别处出现
const的引用
把引用绑定到const对象上,称之为对常量的引用。
与普通引用不同的是,对常量的引用不能用作修改它所绑定的对象:const int ci =1024; const int &r1 = ci; //正确:引用及其对象都是常量 r1 = 42; //错误:r1是对常量的引用 int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
一般来说,引用的类型必须和其所引用的对象的类型一致,但是有例外。
发现一件有意思的事!
int _tmain(int argc, _TCHAR* argv[])
{
int dval = 3.14;
const int &ri = dval;
dval = 5; //正确:可以通过dval来改变ri的值
//ri = 5; //错误:不能通过ri来修改dval的值
cout<<ri; //输出5
cout<<dval; //输出5
return 0;
}
//但是,把dval的类型改为double,两者类型不一致:
int _tmain(int argc, _TCHAR* argv[])
{
double dval = 3.14;
const int &ri = dval;
dval = 5;
cout<<ri; //输出3
cout<<dval; //输出5
return 0;
}
//这是由于在执行const int &ri = dval;时
//编译器内部执行了如下操作
const int temp = dval;
const int &ri = temp; //将ri绑定在了这个临时量上,是一个const类型
常量引用仅仅对“引用”可参与的操作做出了限定,对于被引用的对象本身是不是一个常量并没有做出限定。
但是,对于一个常量对象,只能用常量引用指向
int i = 42;
int &r1 = i;
const int &r2 = i; //r2也绑定对象i,但是不允许通过r2修改i
r1 = 0; //正确,r1并非常量,可以修改
r2 = 0; //错误
- 指针和const
和引用一样,可以令指针指向一个常量或者非常量;但是对于常量对象的地址,只能使用指向常量的指针
const double pi = 3.14; //pi是一个常量,它的值不能改变
double *ptr = π //错误:试图用一个非常量指针指向一个常量
const double *cptr = π //正确:cptr是一个常量指针,可以指向常量
*cptr = 42; //错误:不能给(*cptr)赋值
double dval = 3.14;
cptr = &dval; //正确:但是不能通过cptr改变dval的值
- const指针
指针是对象,而引用不是。
所以允许将指针本身定义为常量。
常量指针必须初始化,一旦初始化完成,则存放在指针内的那个地址就不能再改变了。
即不变的是指针本身而不是指向的那个值。
指针本身是常量,并不意味着不能通过指针修改其所指向对象的值,能否这样做完全依赖于所指向对象的类型。
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π //pip是一个指向常量对象的常量指针
顶层const
顶层const(top-level const)表示指针本身是常量,更一般地,可以表示任意的对象是常量,这一点对任何数据类型都适用;
底层const(loe-level const)表示指针所指向的对象是常量,更一般地,与指针和引用等复合类型有关int i = 0; int *const p1 = &i; //top-level const int ci = 42; //top-level const int *p2 = &ci; //low-level const int *const p3 = p2; //靠右的const是top-level,靠左的const是low-level const int &r = ci; //low-level //执行对象的拷贝操作 //top-level不受影响,执行拷贝并不会改变被拷贝对象的值 i = ci; //正确 p2 = p3;//正确:p2和p3指向同类型对象,所以p3的top-level不影响 //low-level有限制,拷入和拷出的对象必须具有相同的底层const资格,或者两个对象的数据类型必须可以互相转换 int *p = p3; //错误:p3包含low-level的含义,而p没有,万一通过p来修改p3就不行了 p2 = p3; //正确 p2 = &i; //正确:int*可以转换成const int*,即会把i的地址临时存成const int* temp后拷贝给p2 int &r = ci; //错误:不能将const int转成int const int &r2 = i; //正确:int可以转成const int
constexpr和常量表达式
常量表达式是指在编译过程中就能得到计算结果的表达式。const int sz = get_size(); //不是常量表达式,sz本身虽然使常量,但具体值要到运行时才能获得
constexpr变量
C++新标准规定。
一般来说,如果认定一个变量时一个常量表达式,那就把它声明为constexpr变量。constexpr类型由编译器来验证变量的值是否是一个常量表达式。
新标准还允许定义一种特殊的constexpr函数。这种函数应该足够简单到编译时就能得到结果,这样就能用constexpr函数来初始化constexpr变量。
必须明确的一点是,在constexpr声明中如果定义了一个指针,那么限定符仅仅对指针有效,与指针指向的对象无关,因为constexpr将它所定义的对象置为top-level
const int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = null; //q是一个指向整型的常量指针
constexpr const int *p = &i; //p是一个指向一个整型常量的常量指针
constexpr int *p1 = &j; //p是一个指向整型的常量指针
Summary:
名称 | 定义 | 示例 |
---|---|---|
常量引用 | 仅限定了该“引用”所参与的操作,means 不能通过常量引用来改变它所引用的对象 | const int &ri = temp; //temp是不是const变量都无关 |
指向常量的指针 | 仅限定了该“指针”所参与的操作,means不能通过常量指针来改变它所指向的地址的值 | const double *cptr = &temp; //temp是不是const变量都无关 |
const指针 | 将指针本身定义为常量,必须初始化,一旦初始化后,指针内存放的地址不能再改变 | int *const curErr = &errNumb; //curErr将一直指向errNumb |
想要弄清楚这些声明的含义最有效的方法是从右往左阅读。
指针本身是不是常量和指针指向的是不是常量,是两个独立的问题。
2.4节需要好好研读。