关于C++中const限定符的学习

const

参考书目:《C++ primer》第五版

为何定义const变量?有什么好处?
当我们在特定的场景下使用一个变量的时候,我们希望该变量的值仅仅在初始化能设置其值,且后续操作都不能更改它的值,这时我们就需要使用const限定符。它的好处是一旦初始化,其对象无法进行修改。可以在程序中防止一些误修改操作的发生。

初始化

const int size = 20;		//输入一个长度大小

这样就定义了一个int类型的常量。任何试图对size赋值的操作都会引发错误

size = 30;  				//错误,试图对size再赋值

由于const对象在创建后就不能更改其值,显然必须在定义的时候对其进行初始化。该初始化的值可以是常数或任意复杂的表达式:

const int a = get_size();	//正确
const int b = 40;			//正确
const int k;				//错误,未进行初始化

const的主要限制就是只能在const类型的对象上执行不改变其内容的操作,比如const int能够和普通的int一样参与运算操作。
在不改变const对象的操作中还有一种是初始化,如果利用一个对象去初始化另外一个对象,则他们是不是const都无关紧要。

int i = 40;
const int j = i;			//正确
int k = j;					//正确

虽然j是一个int常量,但是j中的值还是一个整数。j的常量特征仅仅在执行改变j的操作的时候才会体现出来。当用j初始化k时,只需要等号右边是一个同类型的量即可,无需关注它是一个常量还是变量。正如我们拷贝一个值,我们并没有改变它,拷贝完成后,新的对象和原对象没有任何关系。
这里需要注意,默认情况下,const仅在某一个文件内有效。当多个文件中出现了同名的const变量时,等同于在不同的文件中分别定义了独立的常量。想要在多个文件中都使用一个文件定义的const常量,操作和其他(非常量)对象一样,只需要在初始化的文件和需要使用的外部文件都添加extern关键字进行声明。

extern const int size = 20; 		//初始化一个可被引用的常量size
extern const int size;				//引用外部常量

const的引用

和正常的引用一样,const也可以进行引用来绑定到其他对象上,称为对常量的引用。然而它特殊的地方在于,对常量的引用不能用来修改它绑定的对象。

const int a = 10;
const int &b = a;					//正确,引用及其对应的的对象都是常量
b = 20;								//错误,b为对常量的引用,无法修改其绑定的对象
int &c = a;							//错误,试图让一个非常量引用指向一个常量对象

由于a为常量,因此不能对其进行赋值的操作,上述通过引用去改变a的值也是不允许的。若c的初始化正确,那么可以通过c修改其引用的对象a的值,这显然违背了const的初衷。
Tips:实际上并不存在“常量引用”这个说法。因为引用并非一个对象,我们无法让引用本身保持不变。事实上,由于C++并不允许随意更改引用绑定的对象,从这层意义上理解所有的引用都可以认为是常量。引用的对象是常量还是非常量可以决定其所能参与的操作,却无论如何都不会影响到引用和对象的绑定关系本身。简单来说,在引用关系确定后,两者可以认为是同一个对象,这个对象的值能否修改要看其定义是常量还是非常量。

初始化和对const的引用

在C++中,引用的类型必须与其所引用对象的类型一致,但有两种情况除外。初始化常量引用时允许用任意表达式作为初始值(只要表达式最后的结果为对应的类型)。

int a = 10;
const int &b = a;					//正确
const int &c = 10;					//正确,c为常量引用(右边为一个数)
const int &d = b * 2;				//正确,d为常量引用(右边为表达式)
int &e = b * 2;						//错误,e为非常量引用(不可通过e引用来修改常量)

有时候可能我们会粗心写成了下面这样:

double a = 3.14;
const int &b = a;

然而更惊讶的是,居然没有报错?原来编译器在执行常量引用的时候,自动将a强制转换成了int类型,然后让b绑定了这个临时量。注意,此时b绑定的对象不是a,而是临时量。如果去掉该const,变量b也会绑定临时量吗?在编译器上运行发现提示错误了。原因很简单,变量的引用,其目的是使用b来更改a,这时b绑定了一个临时量,如何修改对象a的值?因此C++将这种操作认为是非法操作。

对const的引用可能引用一个并非const的对象

前面已经说过,常量引用不可以修改指向的对象,但是如果指向对象的类型并非const呢?那我们可以修改对象的值吗?

int a = 10;
int &b = a;													//引用b绑定对象a
const int &c = a;											//c绑定对象a,但是不能通过c改变a的值
b = 0;															//允许
c = 0;															//错误,c为常量引用,不可以通过c修改对象a的值

Tips:我们直接修改a,c的值也会改变(但其指向的对象不变)

指针和const

和引用一样,指针也可以指向常量或者非常量。可以回想一下常量引用的特点,指向常量的指针也不能改变它指向的对象的值。想要存放常量对象的地址,就只能使用指向常量的指针:

const int p = 3;											//p为常量,其值不可变
int *a = &p; 													//错误,普通指针不可以指向常量(a可以改变p的值)
const int *b = &p;											//正确,b可以指向一个int常量
*b = 1;															//错误,不能给*b赋值(常量)

同样,指针的类型必须与其所指对象的类型一致。但是允许一个指向常量的指针指向一个非常量的对象:

int val = 10;
b = &val;														//正确,这里使用上文的b,但不能通过b改变val的值

和常量引用一样,指向常量的指针也没有规定其所指的对象必须是一个常量。指向常量的指针仅仅要求不能通过该指针改变对象的值,而没有规定那个对象能不能通过其他途径改变。

const指针

指针是对象,而引用不是对象,因此指针和其他对象类型一样,允许把指针本身定为常量。常量指针必须初始化,在初始化完成后,它的值(指针中存放的地址)就不能被改变了。把*放在const前说明指针是一个常量,这里的意思是指针本身的值不能改变,而不是指针指向的值不能改变。

int num = 0;
int *const err = #									//err将一直指向num
const double pi = 3.14;
const double *const pip = π					//pip是一个指向常量对象的常量指针

要想更快地理解这些声明的含义,我们可以从右边往左开始解读。在上面的代码第二行err的声明中,离err最近的符号是const,说明err本身是一个常量对象,下一个符号是*,意思是err是一个指针常量,最后类型说明err是一个指向int对象的指针常量。类似地我们可以推断出pip是一个指向double常量的指针常量。
指针本身是一个常量并不意味着不能通过指针修改其指向对象的值,能否更改完全依赖于所指对象的类型。比如pip是一个指向常量的指针常量,那么无论是pip所指的对象的值还是自己存储的地址都不可改变。相反,指针常量err指向的是非常量,那么就可以用err去修改num的值。

**Tips:快速读取和判断指针类型的小技巧**
从左往右 => *和const谁先出现先读谁    
	eg: int *const->指针常量   int const*->常量指针
从左往右 => const左边第一个出现的对象就是const实际限定的内容,没有则看右边
	eg: int *const->const对*做限定,指针(的指向)不能改变   
	    int const*->const对int做限定,指针指向的对象的值不能改变
*pip = 1.1;										//错误,pip为指向常量的指针(常量)
*err = 3;										//正确,num被修改为3
err = pip;										//错误,指针常量不允许在初始化后再赋值
pip = err;										//同上

顶层const

指针是一个对象,它可以指向另外一个对象。因此,指针本身是不是常量,以及指针指向的对象是不是常量,实际上是两个完全独立的问题。C++中用顶层const(声明时在最左边)表示指针本身是个常量(即指针常量),用底层const(声明时在指针符号右边)表示指针指向的对象是一个常量(即常量指针)。
指针类型既可以是顶层const也可以是底层const:

int i = 0;
int *const a1 = &i;										//不能改变a1的值,顶层const
const int c = 10;											//不能改变c的值,顶层const
const int *a2 = &c;										//可以改变a2的值,底层const
const int *const a3 = a2;								//靠右的const是常量指针顶层const,靠左的是指向常量的底层const
const int &r = c;											//用于声明引用的const都是底层const

当我们需要进行对象的拷贝时,常量是顶层const还是底层const的区别就十分明显。顶层const不受任何影响:

i = c;															//正确,c是顶层const,拷贝操作不受影响
a2 = a3;														//正确,a2和a3指向的对象都是同类型的常量(底层const),a3顶层部分不影响

而另一方面,底层const的限制很大。当执行对象进行拷贝操作时,拷入和拷出的对象必须具有相同的底层const资格,后者两个对象的数据类型必须能够转换。一般非常量可以转换成常量,反之不行:

//接上初始化
int *a = a3;										//	错误,a3包含底层const定义,而a没有
a2 =a3;											//	正确,a2和a3都是底层const
a2 = &i;											//	正确,int *可以转换成const int *
int &r = c;											//	错误,普通的int&不能绑定到int常量上
const int &r2 = i;								//	正确,const int&可以绑定到一个普通int上
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值