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