注:参考 C++ Primer(第 5版)书籍,本文是精简后的知识点
目录
const 限定符
用 const 关键字对变量的类型加以限定,那么它的值将不能被改变。
const int bufSize = 512; // 输入缓存区大小
bufSize = 512; // 错误:试图向 bufSize 对象写值
(1)初始化和 const
与非 const 类型所能参与的操作相比,const 类型的对象能完成其中大部分,但也不是所有的操作都适合。主要的限制就是只能在const类型的对象上执行不改变其内容的操作。
但是在不改变 const 对象的操作中还有一种是 初始化,如果利用一个对象去初始化另外一个对象,则它们是不是 const 都无关紧要:
inti=42;
const int ci =i; //正确: i的值被拷贝给了ci
intj=ci; // 正确: ci的值被拷贝给了i
尽管 ci 是整型常量,但无论如何 ci 中的值还是一个整型数。c 的常量特征仅仅在执行改变 ci 的操作时才会发挥作用。当用 ci 去初始化 时,根本无须在意 c 是不是一个常量。拷贝一个对象的值并不会改变它,一旦拷贝完成,新的对象就和原来的对象没什么关系了。
(2)默认状态下,const 对象仅在文件内有效
如果程序包含多个文件,则使用了 const 对象的文件都必须得能够访问到它的初始值才行。那么必须在每个用到变量的文件中都有对它的定义,为了避免对同一变量重复定义,默认情况下,const 对象被设定为仅在文件内有效。等同于在不同文件中分别定义了独立的变量。
但有时候需要这样的 const 变量,它的初始值不是一个常量表达式,但又确实必要在文件间共享。解决的办法是,对于const 变量不管是声明还是定义都添加extern 关键字,这样只需定义一次就可以了:
// file 1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
//filel.h头文件
extern const int bufSize;//与file l.cc中定义的bufSize是同一个
如上述程序所示,file 1.cc 定义并初始化了 bufsize。因为这条语句包含了初始值,所以它(显然)是一次定义。然而,因为 bufsize 是一个常量,必须用 extern 加以限定使其被其他文件使用。
file l.h头文件中的声明也由 extern 做了限定,其作用是指明 bufSize 并非本文件所独有,它的定义将在别处出现。
1. const 的引用
可以把引用绑定到 const 对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(reference to const)。 与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &c1 = ci; //正确:引用及其对应的对象都是常量
c1 = 42; // 错误:c1 是对常量的引用
int &c2 = ci; // 错误:试图让一个非常量引用指向一个常量对象
注意:常量引用就是对 const 引用
严格来说,并不存在常量引用的。因为引用不是一个对象,所以没法让引用恒定不变。
1.1. 初始化和对 const 的引用
一般来说,引用的类型必须与其所引用的对象的类型一致,但是又有例外情况: 在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式。
理解这种例外情况就要清楚当一个常量引用被绑定到另外一种类型上时到底发生了什么:
double dval=3.14;
const int &ri =dval;
此处 ri引用了一个int 型的数。对 ri 的操作应该是整数运算,但 dval 却是一个双精度浮点数而非整数。因此为了确保让 ri 绑定一个整数,编译器把上述代码变成了如下形式:
const int temp = dval; // 由双精度浮点数生成一个临时的整型常量
const int &ri = temp; // 让ri绑定这个临时量
在这种情况下,ri 绑定了一个临时量 (temporary)对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。临时量对象简称为临时量。
1.2. 对const的引用可能引用一个并非const 的对象
注意:常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。
因为对象也可能是个非常量,所以允许通过其他途径改变它的值:
int i = 42;
int &r1 = i; // 引用r1绑定对象i
const int &r2 =i; //r2也绑定对象i,但是不允许通过 r2 修改i的值
r1=0; // r1并非常量,i的值修改为0
r2=0; //错误:r2是一个常量引用
r2 绑定(非常量)整数是合法的行为。然而,不允许通过 r2 修改的值。尽管如此 i 的值仍然允许通过其他途径修改,既可以直接给赋值,也可以通过像 r1 一样绑定到i的其他引用来修改。
2. 指针和 const
和常量引用类似,指向常量的指针(pointer to const)也叫做常量指针(const pointer)。不能用于修改指向对象的值。要想存放常量对象的地址,就得使用常量指针。
常量指针必须初始化,一旦初始化完成,则它的值(存放在指针种的那个地址)就不会再改变了。本质上是,不变的是指针本身的值,而不是指向的那个值:
int errNumb= 0;
int *const curErr = &errNumb; // curErr将一直指向 errNumb
const double pi=3.14159;
const double *const pip = π // pip是一个指向常量对象的常量指针
*pip = 2.34; //错误:pip所指的对象和所存储的那个地址都不能改变
*curErr = 0; //正确:把curErr所指向对象errNumb的值改为0
3. 顶层 const
指针本身是一个对象,又可以指向另一个对象。因此,指针本身是不是常量和指针所指向的是不是一个常量就是两个相互独立的问题。用名词顶层表示指针本身是常量,名词底层表示指针所指向的对象是常量。
顶层 const 可以表示任意的对象是常量,这一点对任意数据类型都适用。底层 const 则与指针和引用等复合类型的基本类型部分有关。
比较特殊的是,指针类型既可以是顶层 const ,又可以是底层 const。
int i=0;
int *const pl = &i; // 不能改变pl的值,这是一个顶层const
const int ci = 42; // 不能改变 ci的值,这是一个顶层const
const int *p2 = &ci; // 允许改变p2的值,这是一个底层 const
const int *const p3= p2; // 靠右的const是顶层const,靠左的是底层
const int &r = ci; // 用于声明引用的 const 都是底层
执行对象的拷贝操作时,常量是顶层 const 还是底层 const 区别明显。其中,顶层 const 不受什么影响;
i = ci; // 正确:拷贝ci 的值,ci是一个顶层const,对此操作没有影响
p2 = p3; // 正确:p2 和 p3 指向的对象类型相同,p3 顶层const的部分不影响
另一方面,底层 const 的限制却不能忽视。当执行对象的拷贝操作时,拷入和拷出的对象必须具有相同的底层 const 资格,或者两个对象的数据类型必须能够转换。一般来说,非常量可以转换成常量,反之则不行:
int*p = p3; //错误:p3包含底层const 的定义,而p没有
p2 = p3; //正确:P2和p3都是底层const
p2 = &i; //正确:int*能转换成 const int*
int &r = ci; // 错误:普通的 int&不能绑定到 int 常量上
const int &r2 = i; //正确:const int& 可以绑定到一个普通int 上
4. constexptr 和常量表达式
4.1. 常量表达式(const expression):
指值不会改变并且在编译过程就能得到计算结果的表达式。显然,字面值属于常量表达式,用常量表达式初始化的 const 对象也是常量表达式。
const int max files = 20; // max files 是常量表达式
constint limit=max files +1; // limit是常量表达式
int staff size =27; // staffsize不是常量表达
const int sz =get size(); // sz不是常量表达式,
4.2. constexpr 变量:
C++ 11 新标准规定,允许将变量声明为constexpr 类型以便由编译器来验证变量是否是一个变量表达式。
constexpr int mf = 20; // 20是常量表达式
constexpr int limit =mf + 1; // mf+1是常量表达式
constexpr int sz = size(); // 只有当 size是一个 constexpr函数时
// 才是一条正确的声明语句
一般来说,如果变量是一个变量表达式,那就把它声明诚constexpr类型。
4.3. 字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明 constexpr 时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为 “字面值类型”(literaltype)。
到目前为止接触过的数据类型中,算术类型、引用和指针都属于字面值类型。自定义类 Sales item、IO 库、string 类型则不属于字面值类型,也就不能被定义成constexpr。
4.4. 指针和 constexpr
必须明确一点,在constexpr 声明中如果定义了一个指针,限定符constexpr 仅对指针有效,与指针所指的对象无关:
const int *p =nullptr; // P是一个指向整型常量的指针
constexpr int *q = nullptr; // q是一个指向整数的常量指针
p 和 q 的类型相差甚远,p 是一个指向常量的指针,而g 是一个常量指针,其中的关键在于 constexpr 把它所定义的对象置为了顶层 const 。
5. 总结:
以上就是const 限定符使用的所有内容和相关注意事项:
- 需要了解清楚指针和引用使用const 限定符的方式和区别;
- 指针是一个特殊的数据类型,它既可以是顶层const ,也可以是底层const;
- 同时也需要清楚 const 修饰的是指针还是指针所指向的对象的值。
希望大家多多支持,码字不易,若有错误请指出,共同学习!
欢迎关注🔎点赞👍收藏⭐️留言📝