const、ref、pointer、编译相关问题

本来是因为上一篇写的比较短,在下面补了一些相关内容凑字儿,结果越写越多,还是单独开个帖吧。


1.pointer相关

常指针:int * const p表示指向的量是常量(pointer to const);
指针常量:const int* p表示指针本身是常量(const pointer);
const int * const p从右往左读,const p表示p是个常量, const int* 表示p是个指针,且指针指向的对象是const int类型的,故指针本身和指向的量都是常量。


2.const变量相关

const修饰一个变量,从存储上分以下情况讨论:
1 当修饰一个局部变量时,该变量从存储上来说仍是栈中一个普通变量。
2 当修饰一个全局变量时,该变量被存储在静态常量区,是一个只读量。

于是,const修饰的局部变量从理论上还是可以改的。

const int a = 5;
int* b = (int*)& a;//显式类型转换,也可用const_cast
*b = 4; 
int c = a;
std::cout<< a<< ' '<< c<< std::endl;//4, 5

以上代码就可以修改a对应的内存的值,输出为4, 5;
const量在编译期在所有常量表达式中都被替换为了最开始初始化的值,这一点和宏定义很像,区别就是宏定义发生在预处理期且没有类型的概念。

以上代码(除输出行)编译后的汇编文件如下:

0x5561affda7f2: c7 45 dc 05 00 00 00        movl   $0x5, -0x24(%rbp)
0x5561affda7f9: 48 8d 45 dc                 leaq   -0x24(%rbp), %rax
0x5561affda7fd: 48 89 45 f0                 movq   %rax, -0x10(%rbp)
0x5561affda801: 48 8b 45 f0                 movq   -0x10(%rbp), %rax
0x5561affda805: c7 00 04 00 00 00           movl   $0x4, (%rax)
0x5561affda80b: c7 45 e4 05 00 00 00        movl   $0x5, -0x1c(%rbp)

可以看出,在给c赋值时用的是立即数5,而不是a对应的内存值,且此时a对应的内存值已经被改为4了。


3.类型推导与转换

对于一个reference或pointer来说,自身的const类型被称为top-level,所指向的对象类型被称为low-level
类型转换时,可以忽略top-level,不可忽略low-level;
使用auto和typeid进行类型推导时,会忽略high-level的const和reference
如:

const int a = 5;
const int* const pa = &a;
auto c = pa;//c是const int*类型
int* d = pa;//error,low-level不可违反
int e = a;//隐式转换,单向

这点很好理解,因为auto和typeid的类型推导类似赋值操作,是单向的,不会对原数据进行更改,所以去掉top-level的const很安全,但是新对象如果是指针,是可以绕过自身的值而去改变所指向的数据(这也是对分top和low的一种理解方式),所以low-level的const必须得保留。

隐式类型转换一般发生在赋值上,所以道理同auto,而显式类型转换const_cast可以去掉low-level的const,这种情况与上面2 const变量相关讨论的情况一样。

decltype()更复杂一点,他作的类型推导是会同时包含top和low的信息的,这个函数其实更像是专业的类型推导。而auto仅仅是简化编程,似乎只要编程不出错,推导出的类型不全也无所谓。


(补)4.reference与pointer的区别

提示:以下内容基于gcc解释器,其他解释器可能不同

(看了下MSVC,也是一样的。)
其实从gcc的汇编来看,r跟p没有区别,一模一样。
r和p的汇编代码都是类似下面的形式:

0x5561affda7f9: 48 8d 45 dc                 leaq   -0x24(%rbp), %rax
0x5561affda7fd: 48 89 45 f0                 movq   %rax, -0x10(%rbp)

所做的工作就是将右值的地址赋给左值。

在作函数参数的时候,很多人说传reference是传对象本身,传pointer是传值,那传地址就比传值好。

其实汇编后都一样,传reference的过程就是拷贝了一份对象的地址到调用函数的栈区,传pointer的过程是拷贝了一份pointer的到调用函数的栈区。那他俩拷贝的不就是同样大小的东西吗?

所以他们俩的区别主要就是在编译期的区别,只要语法能过编译,那就一样了。
编译期的区别如下:
1.r的安全性比p好,r可以看作一个top level的const pointer,必须初始化且初始化后不可改变其reference对象,这不比pointer乱指安全很多吗。
2.引用不算个类型,更多像一个描述符,所以不存在&&描述符,不能int&& a = b; 但可以int** pp = (int*)p;
3.sizeof(reference)是对象大小,sizeof(pointer)是本身大小,sizeof是一个在编译期内得到值的函数。
4.有指针数组,但没有引用数组(看到有人加了这点,不过应该没人真这么写吧)。

看到篇文章对const写的很全很好,可以学习一下:
https://zhuanlan.zhihu.com/p/91075706

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值