C++ Primer const 笔记

2.4 const限定符
用const关键字定义的变量,其值不能被改变,即const不能被赋值。也因此,const定义的变量必须在定义时初始化。
引用的类型必须与其所引用对象的类型一致。

默认状态下,const对象仅在文件内有效
编译器将在编译过程中把用到const变量的地方都替换成对应的值。
为了执行上述替换,编译器必须知道变量的初始值。如果程序包含多个文件,则每个用了const对象的文件都必须的能访问到它的初始值才行。要做到这一点,就必须在每一个用到变量的文件中都有对它的定义。为了支持这一用法,同时避免对同一变量的重复定义,默认情况下,const对象被设定为仅在文件内有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。
某些时候有这样一种const变量,它的初始值不是一个常量表达式,但又确实有必要在文件间共享。即只在一个文件中定义const,而在其他多个文件中声明并使用它。
解决的办法是,对于const变量不管是声明还是定义都添加extern关键字,这样只需定义一次就可以了:

// file_1.cc定义并初始化了一个常量,该常量能被其他文件访问
extern const int bufSize = fcn();
// file_1.h头文件
extern const int bufSize;   // 与file_1.cc中定义的bufSize是同一个

2.4.1 const的引用
如果我们想把一个引用绑定到一个常量上,那么在定义该引用的时候,引用名前需要加上const关键字来修饰,这种定义时前面加了const修饰的引用称为对常量的引用(reference to const)。与普通引用不同的是,对常量的引用不能被用于修改它所绑定的对象:

const int ci = 1024;
const int &r1 = ci;   // 正确:引用及其对应的对象都是常量
r1 = 42;   // 错误:r1是对常量的引用
int &r2 = ci;   // 错误:试图让一个非常量引用指向一个常量对象

因为不允许直接为ci赋值,当然也就不能通过引用去改变ci。因此,对r2的初始化是错误的。假设该初始化合法,则可以通过r2来改变它引用的对象的值,这显然是不正确的。
即,对const变量的引用也应该是const的。

初始化和对const的引用
2.3.1节提到,引用类型必须与其所引用对象的类型一致,但是有两个例外。第一种例外情况就是在初始化常量引用时允许用任意表达式作为初始值,只要该表达式的结果能转换成引用的类型即可。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至是个一般表达式

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 dval = 3.14;
const int &ri = dval;

此处 ri 引用了一个int型的数。对 ri 的操作应该是整数运算,但val却是一个双精度浮点数而非整数。因此为了确保让ri绑定一个整数,编译器把上述代码变成了如下形式(编译器自己做一步强转):

const int temp = dval;   // 由双精度浮点数生成一个临时的整形常量
const int &ri = temp;   // 让 ri 绑定这个临时量

参考链接:https://blog.csdn.net/Colsum/article/details/79095462

对const的引用可能引用一个并非const的对象
常量引用仅对引用可参与的操作做出了限定,对于引用的对象本身是不是一个常量未作限定。因为对象也可能是个非常量,所以允许通过其他途径改变它的值:

int i = 42;
int &rl = i;   // 引用ri绑定对象i
const int &r2 = i;   // r2也绑定对象i,但是不允许通过r2修改i的值
r1 = 0;   // r1并非常量,i的值修改为0
r2 = 0;   // 错误:r2是一个常量引用

2.4.2 指针和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的指针来指,但const的指针也可以指向非const变量。

const指针
指针是对象而引用不是,因此就像其他对象类型一样,允许把指针本身定为常量。常量指针(const pointer)必须初始化,而且一旦初始化完成,则它的值(也就是存放在指针中的那个地址)就不能再改变了。把*放在const关键字之前用以说明指针是一个常量,这样的书写形式隐含着一层意味,即不变的是指针本身的值而非指向的那个值:

int errNumb = 0;
int *const curErr = &errNumb;  // curErr将一直指向errNumb
const double pi = 3.14159;
const double *const pip = π   // pip是一个指向常量对象的常量指针

要想弄清楚这些声明的含义,最行之有效的办法是从右向左阅读。此例中,离curErr最近的符号是const,意味着curErr本身是一个常量对象,对象的类型由声明符的其余部分确定。声明符中的下一个符号是*,意思是curErr是一个常量指针。最后,该声明语句的基本数据类型部分确定了常量指针指向的是一个int对象。与之相似,我们也能推断出,pip是一个常量指针,它指向的对象是一个双精度浮点型常量。
指针本身是一个常量并不意味着不能通过指针修改其所指对象的值,能否这样做完全一来于所指对象的类型。例如,pip是一个指向常量的常量指针,则不论是pip所指的对象值还是pip自己存储的那个地址都不能改变。相反的,curErr指向的是一个一般的非常量整数,那么就完全可以用curErr去修改errNumb的值:

*pip = 2.72;   // 错误:pip是一个指向常量的指针

if(*curErr)
{
	errorHandler();
	*curErr = 0;   // 正确:把curErr所指的对象的值重置

2.4.3 顶层const
如前所述,指针本身是一个对象,它又可以指向另外一个对象。因此,指针本身是不是常量以及指针所指的是不是一个常量就是两个相互独立的问题。用名词顶层const(top-level const)表示指针本身是个常量,而用名词底层const(low-level const)表示指针所指的对象是一个常量。
更一般的,顶层const可以表示任意的对象是常量,这一点对任何数据类型都适用,如算术类型、类、指针等。底层const则与指针和引用等复合类型的基本类型部分有关。比较特殊的是,指针类型既可以是顶层cosnt也可以是底层const,这一点和其他类型相比区别明显:

int i = 0;
int *const p1 = &i;   // 不能改变p1的值,这是一个顶层const
const int ci = 42;   // 不能改变ci的值,这是一个顶层const
const int *p2 = &ci;   // 允许改变p2的值,这是一个底层const
const int *const p3 = p2;   // 靠右的cosnt是顶层const,靠左的是底层const
const int &r = ci;   // 用于声明引用的const都是底层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都是底层cosnt
p2 = &i;   // 正确:int*能转换成const int*
int &i = ci;   // 错误:普通的int&不能绑定到int常量上
const int &r2 = i;   // 正确:const int&可以绑定到一个普通int上

p3既是顶层const也是底层const,拷贝p3时可以不在乎它是一个顶层const,但是必须清楚它指向的对象得是一个常量。因此,不能用p3去初始化p,因为p指向的是一个普通的(非常量)整数。另一方面,p3的值可以赋给p2,是因为这两个指针都是底层const,尽管p3同时也是一个常量指针(顶层const),仅就这次赋值而言不会有什么影响。

2.4.4 constexpr变量
注:一般来说,如果你认定变量是一个常量表达式,那就把它声明成constexpr类型。

字面值类型
常量表达式的值需要在编译时就得到计算,因此对声明constexpr时用到的类型必须有所限制。因为这些类型一般比较简单,值也显而易见、容易得到,就把它们称为“字面值类型”(literal type)。
到目前为止接触过的数据类型中,算术类型、引用和指针都属于字面值类型。自定义类Sales_item、IO库、string类型则不属于字面值类型,也就不能被定义成cosntexpr。其他一些字面值类型将在7.5.6节和19.3节介绍。
尽管指针和引用都能定义成constexpr,但它们的初始值却受到严格限制。一个constexpr指针的初始值必须是nullptr或者0,或者是存储于某个固定地址中的对象。
6.1.1节将要提到,函数体内定义的变量一般来说并非存放在固定地址中,因此constexpr指针不能指向这样的变量。相反的,定义于所有函数体之外的对象其地址固定不变,能用来初始化constexpr指针。同样是在6.1.1节中还将提到,允许函数定义一类有效范围超出函数本身的变量,这类变量和定义在函数体之外的变量一样也有固定地址。因此,constexpr引用能绑定到这样的变量上,constexpr指针也能指向这样的变量。

指针和constexpr
必须明确一点,在constexpr声明中如果定义了一个指针,限定符constexpr仅对指针有效,与指针所指对象无关:

const int *p = nullptr;   // p是一个指向整型常量的指针
constexpr int *q = nullptr;   // q是一个指向整数的常量指针

p和q的类型相差甚远,p是一个指向常量的指针,而q是一个常量指针,其中的关键在于constexpr把它所有定义的对象置为了顶层const(参见2.4.3节,57页)。

与其他常量指针类似,consteptr指针既可以指向常量也可以指向一个非常量:

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
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值