目录
const限定符
关键字const对变量的类型加以限定,可以随时警惕程序改变这个值;
const int bufSize = 512;
这样bufSize就定义成了一个常量,任何试图改变bufSize赋值的行为都将引发错误。
bufSize = 122; //错误:试图向const对象写值
因为const对象一旦创建后其值就不再改变,所以const必须初始化。初始值可以是任意复杂的表达式:
const int I = get_size(); //正确:运行时初始化
const int j = 42; //正确:编译时初始化
const int k; //错误:k是一个未经初始化的常量
初始化和 const
对象的类型决定了其上的操作。
与非const类型所能参与的操作相比,const类型的对象能完成其中大部分。主要限制就是只能在const类型的对象上执行不改变其内容的操作,const int和普通的int一样都能参与算术运算,也都能转换成一个布尔值,等等。
在不改变const对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则它们是不是const都无关紧要:
int i = 42;
const int ci = i; //正确:i的值被拷贝给了ci
int j = ci; //正确:ci的值被拷贝给了j
ci的常量特征仅仅在执行改变ci的操作时才会发挥作用。(const 的限制只会在执行时发生作用) , 在用ci去初始化j时,根本不用在意ci是不是一个常量。
默认状态下,const对象仅在文件内有效
当以编译时初始化的方式定义一个const对象时,就如对bufSize的定义一样,编译器将在编译过程中把用到该变量的地方都替换成对应的值。
为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用到了const对象的文件都必须得能访问到它的初始值才行,这样就必须在每个用到了它的文件中都有对它的定义。
为了支持这一用法,同时避免对同一个变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const 变量时,其实等同于在不同文件中分别定义了独立的变量。
某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。也就是说,只在一个文件中定义 const,而在其他多个文件中声明并使用它。解决方法就是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:
//file_l.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fun();
//file_l.h头文件
extern const int bufSize; //与file_l.cc中定义的bufSize是同一个
file_l.cc定义并初始化了bufSize。因为这条语句包含了初始值,所以是一次定义。然而,因为bufSize是一个常量,必须用extern加以限定使其被其他文件使用。
注意⚠️:如果想在多个文件之间共享const对象,必须在变量的定义之前添加extern关键字。
const的引用
可以把引用绑定到const对象上,就像绑定到其他对象上一样,我们称之为对常量的引用(refenence to const),与普通引用不同的是,对常量的引用不能被用作修改它所绑定的对象:
const int ci = 1024;
const int &rl = ci; //正确:引用及其对应的对象都是常量
rl = 42; //错误:rl是对常量的引用
int &r2 = ci; //错误:试图让一个非常量引用指向一个常量对象
因为不允许直接为ci赋值,当然也就不能通过引用去改变ci。
术语:常量引用是对const的引用
C++程序员们经常把词组“对const的引用”简称为“常量引用”。
引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系本身。
初始化和对const的引用
之前提到,引用的类型必须与其所引用对象的类型一致,但是有两个例外。第一个例外情况就是在初始化常量引用时允许用任意表达式作为初始值(只要该表达式的结果能转换成引用类型即可)。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式:
int I = 42;
const int &r1 = I; //允许将const int&绑定到一个普通的int对象上
const int &r2 = 42; //正确:r1是一个常量引用
const int &r3 = r1 * 2; //正确:r3是一个常量引用
int &r4 = r1 * 2; //正确:r4是一个普通的非常量引用
注意:常量引用可以绑定变量,但是变量不允许绑定常量!!
其实在上述的情况中,编译器做了如下调整:
double deal = 3.14;
const int &r1 = dval;
|
|
\/
const int temp = dval; //由双精度浮点数生成一个临时的整型常量
const int &r1 = temp; //让r1绑定这个临时量
在这种情况下,r1绑定了一个临时量(temporary)对象。所谓临时量对象就是当编译器需要一个空间来暂存表达式的求值结果时临时创建的一个未命名的对象。C++程序员们常常把临时量对象简称为临时量。
对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可以绑定(非常量)整数i,但是,不允许通过r2修改i的值。
但是i的值仍然允许通过其他途径修改:
- 可以直接给i赋值;
- 可以通过像r1一样绑定到i的其他引用来修改。
指针和const
与引用一样,也可以令指针指向常量或非常量。
类似于常量引用,指向常量的指针(pointer to const)不能用于改变其所指对象的值。要想存放常量对象的地址,只能使用指向常量的指针:
const double pi = 3.14; //pi是个常量,它的值不能改变
double *ptr = π //错误:ptr是一个普通指针
const double *cptr = π //cptr可以指向一个双精度常量
*cptr = 42; //错误:不能给*cptr赋值
指针的类型必须与其所指对象的类型一致,但是有两个例外。
第一个例外情况是允许一个指向常量的指针指向一个非常量对象:
double dval = 3.14; //dval是一个双精度浮点数,它的值可以改变
cptr = &dval; //正确:但是不能通过cptr改变dval的值
和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。(在解引用时,起到常量的作用)
所谓向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象的值不能通过其他途径改变。(常量指针不用一定要指向常量)
const指针
常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值:
int errNumb = 0;
int *const curErr = &errNumb; //curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π //pip是一个指向常量对象的常量指针
关于常量指针和指针常量的区别:
指针常量:可以改变指向,但不能修改指向的某变量的值 。
常量指针:不可以改变指向:
- 指向非常量的常量指针(当a为常量时),会报错;
- 指向常量的常量指针(当a为非常量时),可改变a的值;
- 指向常量的常量指针,不论是指针所指的对象值还是指针自己存的那个地址都不能改变。
顶层const
指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。
用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level-const)表示指针所指的对象是一个常量。
int I = 0;
int *const p1 = &I; //不能改变p1的值,这是一个顶层cosnt
const int ci = 42; //不能改变ci的值,这是一个顶层const
const int *p2 = &ci; //允许改变p2的值,这是一个底层const
const int *const p3 = p2; //靠右的const是顶层const,靠左的是底层const
const int &r = ci; //用于声明引用的const都是底层const
constexpr和常量表达式
常量表达式(const expression)是指值不会改变并且在编译过程就能得到计算结果的表达式。
字面值属于常量表达式,用常量表达式初始化的const对象也是常量表达式。
一个对象(或表达式)是不是常量表达式由它的数据类型和初始值共同决定:
const int max_files = 20; //max_files是常量表达式
const int limit = max_files + 1; //limit是常量表达式
int staff_size = 27; //staff_size不是常量表达式
const int sz = get_size(); //sz不是常量表达式
尽管最后一条语句中的sz本身是一个常量,但它的具体值直到运行时才能获取到,所以也不是常量表达式。
constexpr变量
C++11新标准规定,允许将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量表达式。声明为constexpr的变量一定是一个常量,而且必须用常量表达式初始化:
constexpr int mf = 20; //20是常量表达式
constexpr int limit = mf + 1; //mf + 1是常量表达式
cosntexpr int sz = size(); //只有当size是一个constexpr函数时,才是一条正确的声明语句
字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型(literal type)”。
到目前为止接触的数据类型时,算术类型、引用和指针都属于字面值类型。
自定义类Sales_item、IO库、string类型则不属于字面值类型,也就不能被定义成constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。(constexpr类指针用法)
函数体内定义的变量一般来说并非存放在固定地址中, 因此constexpr指针不能指向这样的变量。
后面还会提到,允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。因此constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。
指针和constexpr
在constexpr声明中如果定义了一个指针,限定符cosntexpr仅对指针有效,与指针所指的对象无关:
const int *p = nullptr; //p是一个指向整型常量的指针
constexpr int *q = nullptr; //q是一个指向整数的常量指针
p和q的类型相差甚远,p是一个指向常量的指针,而q是一个常量指针,其中的关键在于cosntexpr把它所定义的对象置为了顶层const。
与其他常量指针类似,cosntexpr指针既可以指向常量也可以指向一个非常量:
constexpr int *np = nullptr //np是一个指向整数的常量指针,其值为空
int j = 0;
constexpr int I = 42; //I的类型是整型常量
//I和j都必须定义在函数体之外
constexpr const int *p = &I; //p是常量指针,指向整型常量I
constexpr int *p1 = &j; //p1是常量指针,指向整数j
码字不易,来个赞👍再走吧!!