C++的顶层const和底层const的理解

按照c++ primer里面的描述习惯,认为对象为“具有某种数据类型的内存空间”。本文使用IDE为VS2017 社区版。

const与引用:对常量的引用(reference to const)

	const int ci = 0;//常量int对象
	int &a = ci;//报错

在这里插入图片描述
第二个提示说得很清楚,将 “int &” 类型的引用绑定到 “const int” 类型的初始值设定项时,限定符被丢弃,这是因为引用的类型必须与其所引用对象的类型一致。
结论:非常量引用不能绑定到常量上(第二个提示),无法将“const int”类型转换为“int &”(转换方向从右往左,即第一个提示)。

	int i = 0;//非常量int对象
	const int ci = 0;//常量int对象

	const int &a = i;//指向常量的引用(一般称为常量引用),绑定到非常量
	const int &b = ci;//指向常量的引用,绑定到常量

在这里插入图片描述
虽然上面提到,引用的类型必须与其所引用对象的类型一致,但也有两种例外情况(本文将讲第一种例外情况):只要等号右边的表达式的结果,能转换为引用的类型。尤其,允许为一个常量引用绑定非常量的对象、字面值,甚至一般表达式。
结论:
1)如上两个引用都是常量引用(reference to const,对常量的引用)。
2)如上代码const int &a = i,可以把int类型转换为const int &类型,即可以把常量引用绑定到非常量对象上,但基本类型得一样。
3)如上代码const int &b = ci,把常量引用绑定到常量对象,这是正常操作,类型一样其中没有转换过程。
4)如上四个量,只有i可以进行赋值操作,因为只有它是非常量,赋值后,它的引用a的值也随着改变。换句话说,不能通过常量引用a来赋值,但由于a绑定的是非常量i,所以i可以进行赋值操作。

	double dval = 3.14;
	int $vi = dval;
	const int $vii = dval;
	std::cout << vi << std::endl;
	std::cout << vii << std::endl;

在这里插入图片描述
初始化引用时,如果等号两边基本类型不一样,就可能会丢失数据。
int $vi = dval,如提示信息所示,会创建一个临时量,从“double”转换到“int”。
const int $vii = dval,如提示信息所示,会创建一个临时量,从“double”转换到“int”,再转换到“const int”。
所以判定这两个标识符都是未声明的。

	int a = 1;
	double &b = a;//报错
	const double &c = a;//不报错

在这里插入图片描述
从int转double不会丢失数据,但一样这里会产生临时量,临时量是一个prvalue右值,不能绑定给non-const reference,但可以绑定给const reference,同时其生命周期延长至这个const reference的生命周期。

const与指针:指向常量的指针(pointer to const)

	const int ci = 0;//常量int对象
	int *a = &ci;//报错

在这里插入图片描述
结论:与引用一样,非常量指针不能指向常量对象。

	int i = 1;//非常量int对象
	const int ci = 5;//常量int对象

	const int *a = &i;//指向常量的指针,指向了非常量
	const int *b = &ci;//指向常量的指针,指向了常量
	i = 10;

在这里插入图片描述
指针的类别必须与其所指对象的类型一致,但有两种例外情况(本文将讲第一种):指向常量的指针,指向了非常量对象。
结论:
1)如上两个指针都是指向常量的指针(pointer to const)。
2)如上代码const int *a = &i,可以把指向常量的指针,指向非常量对象。
3)如上代码const int *b = &ci,可以把指向常量的指针,指向常量对象,这是正常操作。
4)如上四个量,只有i可以进行赋值操作,因为只有它是非常量。换句话说,不能通过指向常量的指针a来赋值,但由于a指向的是非常量i,所以i可以进行赋值操作。

常量引用,和指向常量的指针一样,虽然字面上都是说的指向常量,但没有规定其所指对象必须是一个常量。只是因为它们认为自己指向的是常量,所以不能通过常量引用或指向常量的指针,来改变指向的对象的值。但如果指向的对象是非常量,那么这个非常量本身进行赋值操作就是正常操作。

const与指针:常量指针(const pointer)

指针是对象,因此可以把指针本身定为常量。常量指针必须初始化,初始化完成后,它的值(指针存的地址)就不能改变了。

	int a = 0;//非常量
	int *const ai = &a;//常量指针,指向了非常量

	const double pi = 3.14;//常量
	const double *const api = &pi;//常量指针,指向了常量

	int * const * aii = &ai;//常量指针的二级指针
	const double * const * aapi = &api;//常量指针的二级指针

如果只是对常量指针解引用,那么解引用后得到指针指向的对象,根据指向对象为常量或者非常量,来决定常量指针解引用后可不可以赋值操作:

	*ai = 1;//可运行
	*api = 3.0;//报错,表达式必须是可修改的左值

对常量指针赋值,直接报错:

	ai = 0;//报错
	api = 0;//报错

在这里插入图片描述
最后两句,对两个常量指针取二级指针,二级指针的类型就是在一级指针的类型后面加个*。
对aii或者aapi解引用,其实就相当与去掉最后一个*号。
读法是从右往走读,const修饰const左边那个星号,若const左边没有星号,那么就是指的最底层的对象。
在这里插入图片描述
在这里插入图片描述
对aii解引用的过程如上。

在这里插入图片描述
在这里插入图片描述
对aapi解引用的过程如上。

顶层const和底层const(top-level const and low-level const)

对于一般对象来说,其实只有顶层const。而对于指针这种,本身是一个对象,又指向一个对象。所以,指针本身是不是常量,和指针指向对象是不是常量,是两个独立的问题。
用顶层top-level const表示指针本身是一个常量,用底层low-level const表示指针指向对象是一个常量。

引用

用于声明引用的const都是底层const。因为引用本身不是对象,所以不可能有顶层const。

	int i = 0;//非常量int对象
	const int ci = 0;//常量int对象

	const int &a = i;//指向常量的引用(一般称为常量引用),绑定到非常量
	const int &b = ci;//指向常量的引用,绑定到常量

在这里插入图片描述

指针

*const代表的是顶层const,指针存的地址不能改变。
const int代表的是底层const,指针指向一个常量,常量自然不能改变。

	int i = 0;
	int *const p1 = &i;
	//不能改变p1指针存的地址,顶层const
	const int ci = 42;
	//常量不能改变,也算是顶层const
	const int *p2 = &ci;
	//p2存的地址可以改变,但p2解引用后得到const int,不能改变,底层const
	const int *const p3 = p2;
	//分析p3类型,*const说明是顶层const,const int说明是底层const
	p2 = p3;

在执行对象的拷贝操作时,顶层const不会受影响:
1)const int *const p3 = p2中,p2没有顶层const,p3有顶层const。
2)p2 = p3中,执行前,p2没有顶层const,p3有顶层const,执行后,也是一样。
在这里插入图片描述
不管有么有执行p2 = p3,各个对象的类型一直都如上图所示,没有变过。

在执行对象的拷贝操作时,两个对象必须具有相同的底层const资格,或者能够转换(一般来说,非常量可以转换成常量,反之不行):
1)int *p = p2; int *p = p3;这两句都会报错,不管p2 p3是不是有顶层const,在拷贝时,都会只看成const int *,但是由于从“const int *”转换到“int *”是不可以的(常量不可以转换为非常量),所以报错。
在这里插入图片描述
2)p2 = p3可运行,p2 p3都是底层const,所以符合“两个对象必须具有相同的底层const资格”。虽然p3还是个常量指针。
3)p2 = &i可运行,&i后得到一个普通指针int *,可以从“int *”转换到“const int *”,符合“非常量可以转换成常量”。

小练习

来自C++ primer的2.4.3节练习。

	int i = 0;
	const int v2 = 0; int v1 = v2;
	int *p1 = &v1, &r1 = v1;
	const int *p2 = &v2, *const p3 = &i, &r2 = v2;

它们的类型如下,请先自行判断类型再与下表对照。
在这里插入图片描述

constexpr

constexpr即为const expression常量表达式。用于声明constexpr的类型一般为字面值类型literal type。指针、引用也属于字面值类型,但指针和引用定义成constexpr时,它们的初始值必须受到限制。
一个constexpr指针的初始值必须是一个存储在固定地址的对象,或者是nullptr或0。
一般来说,函数体内定义的变量不是存放在固定地址中的。但函数也允许定义一类有效范围超过函数自身的变量,这类变量也有固定地址。一个constexpr指针只能指向这些变量。

constexpr设置变量本身为顶层const:相当于在星号后面加个const。

	const int *p = nullptr;
	constexpr int *q = nullptr;
	constexpr const int *k = nullptr;

在这里插入图片描述

auto

auto自行推断类型,忽略顶层const,保留底层const。如果想用auto还保留顶层const,在auto前面加const即可,但只对非复合类型起作用。

	const int i = 42;

	auto j = i;
	const auto j2 = i;

	const auto &k = i;
	auto &k2 = i;

	auto *p = &i;
	const auto *p2 = &i;
	return 0;

在这里插入图片描述
const auto &k = i,使得i成为常量。
k2已经能推断出const int &,所以const auto &k = i前面的const没有起作用。
p已经能推断出const int *,const auto *p2 = &i前面的const看似应该使得指针成为一个常量指针,但并没有起到作用。

  • 27
    点赞
  • 103
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
### 回答1: C++中的顶层const和底层const都是指const修饰的对象或指针。 顶层const指的是const修饰的对象本身是不可修改的,但是可以修改指向该对象的指针。例如: ``` const int a = 10; // a是顶层const int* p = &a; // 错误,不能将const int*转换为int* const int* p = &a; // 正确,p是指向const int的指针,可以指向a *p = 20; // 错误,a是const int类型,不可修改 p = nullptr; // 正确,p本身不是const,可以修改 ``` 底层const指的是const修饰的指针指向的对象是不可修改的,但是指针本身可以修改。例如: ``` int a = 10; int b = 20; int* const p = &a; // p是底层const,指向a,不可修改 *p = 30; // 正确,a的值被修改为30 p = &b; // 错误,p是const指针,不可修改 ``` 需要注意的是,顶层const和底层const可以同时存在,例如: ``` const int* const p = &a; // p是指向const int的const指针,不可修改指向的对象和指针本身 ``` ### 回答2: C++中的const关键字,可以作为类型修饰符,用来修饰声明对象是否可被修改。根据const修饰的位置,分为顶层const和底层const。 顶层const,即const在*号左边。在指针本身不可修改时使用,表示指针本身不可修改,也就是指针所指向的地址可以修改,指向的值也可以修改。例如: ``` const int* p; ``` p是一个指向int类型的const指针,指针本身不可修改,但可以指向其他不同的int类型变量,同时该int类型变量也不可修改。 底层const,即const在*号右边。在指针指向的数据不可修改时使用,表示指针所指向的数据不可修改。例如: ``` int* const p; ``` p是一个指向int类型的const指针,指针本身可修改,但是指向的int变量不可修改。 同时,也可以将顶层const和底层const一起使用,例如: ``` const int* const p; ``` p是一个指向int类型的const指针,该指针本身不可修改,同时也不能修改所指向的int变量。 总的来说,const关键字可以帮助我们保护变量的值不受非法修改,提高代码的可维护性和可读性,而顶层const和底层const则分别用于指针和指针指向的数据不可修改的情况。 ### 回答3: C++中的const关键字有两种应用方式:顶层const和底层const。 顶层const表示的是指针本身是一个常量,即指针所指向的对象不能被修改,但指针本身的指向可以改变。例如,对于int* const p,p是一个指向int的常量指针,表示p所指向的int类型的变量不能被修改,但是p可以指向其他int类型的变量。 底层const表示的是指针所指向的对象是个常量,即指针所指向的对象不能被修改,但是指针本身的指向可以改变。例如,对于const int* p,p是一个指向常量int的指针,表示p所指向的int类型的变量不能被修改,但是p可以指向其他int类型的变量。 由于顶层const和底层const有不同的应用场景,它们的转换也有细微的差别。const int*可以隐式地转换成int*,即底层const可以被忽略,因为我们可以将一个指向常量的指针赋值给一个不是指向常量的指针,但int* const不能隐式地转换成const int*,因为顶层const不能被忽略。如果想要转换成const int*,需要使用强制类型转换。 总之,顶层const和底层const针对的是指针和指针所指向的对象的常量性,它们的使用场合和转换规则也有区别。在C++中熟练使用这两种const是程序员必备的功夫之一。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值