理解const
const怎么理解?C和C++中const区别是什么?
1.C++里面:const修饰的变量不能够再作为左值, cosnt变量初始化后,值不能被修改。
C++里面的const变量必须初始化,否则编译器报错。
int a = 10; //普通变量a作为一个左值
a = 20; //可以把20赋给a代表的内存区域,a代表的内存时可以修改的
const int b = 20;//变量前面加const,它不能作为左值了
b = 30;//错误:不能修改
const int a = 10;//a是常量了
int arr[a] = { 0 };//a可以当做常量来用作数组初始化的固定长度了,但在C中不行。
2:C代码里面,const变量可以不被初始化,编译器不会报错,但会警告。
C里面const修饰的量不叫常量,叫做常变量。它不能作为常量或者常量表达式来使用的。下面代码报错:
const int a = 10;
int arr[a] = { 0 };
//错误:C里面const修饰的变量是常变量,不能作为常量使用。在C++中可以。
看看如下代码:
const int a = 20;
//a = 10;
//错误:const修饰a后,a不能作为左值使用了,但是a哪里的内存时可以赋值的,通过指针手段。
int* p = (int*)& a;
*p = 30;
printf("a = %d \n *p = %d \n *(&a)=%d\n", a, *p, *(&a)); //30 30 30
//输出a也是30,a变量被const修饰,不能作为左值,但是a代表的内存被更改了,所以a的值也变了
//上面三个表达式都是代表a的内存,输出的都是a哪里的内存的值。
C++中和C中const的区别,坑1:
int main() {
const int a = 20;//C++中const修饰的变量必须初始化
int arr[a] = { 0 };//const修饰的变量a可以当做常量使用了
//所以上面实际是:int arr[20] = {0};
//[C++中所有出现const变量名字的地方都被常量的初始值替换。]
int* p = (int*)& a;
*p = 30; //这样确实把a代表的那块内存地址的值改了。
printf("a = %d \n *p = %d \n *(&a)=%d\n", a, *p, *(&a)); //20 30 20
/* *与&互反抵消,相当于还是a
这里打印的二个20是因为,这里跟a没关系:C++中所有出现const变量名字的地方都被
常量的初始值替换。[这里20是因为编译阶段就已经用常量值把出现常量名的地方替换了],
所以这也是为什么C++const修饰的变量必须初始化了,不初始化,那后面拿谁替换呢?。 */
/*主要原因就是:[C和C++编译器中对待const的编译方式不同]
C中const就是当做一个变量来编译生成指令,那么就是变量怎么做的,const修饰的常变量就怎么做;
C++中所有出现const常量名字的地方都被常量的初始值替换。
*/
return 0;
}
C++中const的坑2:
int b = 20;
const int a = b; //现在用变量b做常量a的初始值发生什么?
//现在a已经不叫常量了,叫常变量了;因为初始值不是一个立即数,而是一个变量。
//int arr[a] = { 0 }; 错误:现在不能用a来定义数组了,a是常变量了,不是常量。
int* p = (int*)(&a);
*p = 30;
printf("%d %d %d \n", a, *p, a);
//也不会进行替换了,a是常变量了,跟C语言哪里打印一样了。
// 30 30 30
/*总结:1.C++中const修饰的变量a给的初始值如果是立即数,那么这个变量a就是常量;
以后都会用常量值去替换出现常量名的地方;
2.C++中const修饰的变量a给的初始值如果是一个变量b,那么这个变量a叫做常变量了,不是常量了。
除了常变量不能用作左值了,和普通变量的编译方式一样*/
重要:引用和指针的区别?
我们经常听到一句话,引用是更安全的指针。
1.引用必须初始化,指针可以不初始化。下面从汇编代码深度看看引用和指针的区别;
我们从汇编代码看看区别:从汇编代码看定义指针和定义引用,解引用指针和使用引用是一模一样的。 汇编代码不区分指针和引用,都是通过地址的操作完成;可以说对于引用的处理,底层都是通过指针的方式处理的; 定义引用变量实际是定义指针变量,引用是更安全的指针;当给引用变量赋值,编译器会自动做解引用操作,我们 不是给引用赋值,是给引用的那块内存赋值;
int a = 10;
mov dword ptr [a],0Ah
int* p = &a;
lea eax,[a]
mov dword ptr [p],eax
int& b = a;
lea eax,[a]
mov dword ptr [b],eax
*p = 20;
mov eax,dword ptr [p]
mov dword ptr [eax],14h
b = 30;
mov eax,dword ptr [b]
mov dword ptr [eax],1Eh
2.引用只有一级引用,没有多级引用;指针可以有一级指针,也可以有多级指针。
3.定义引用变量和定义指针变量,他们的汇编指令是相同的;通过引用修改所引用内存的值和通过指针解引用修改指针指向的内存的值,其底层汇编指令也是一样的。
int arr[5] = { 0 };
int* p = arr;
//定义一个引用指向数组
int(&r)[5] = arr;
cout << sizeof(arr) << endl; //int是4字节*5 = 20;(int在32和64位下都是4字节)
cout << sizeof(p) << endl; //p是指针在32位是4字节,在64位是8字节;
//p是指针,尽管他指向数组,实际上它指向数组的第一个元素的地址,也就是数组首地址。
cout << sizeof(r) << endl; //见到引用变量就是原名arr的别名,所以是20
右值引用?
int a = 10; //a是左值,它有内存,有名字,值可以修改
int & b = a; //a是左值,可以初始化左值引用
int & c = 20 //错误:20是右值,没内存,没名字,不能初始化左值引用;
const int & c= 20 ;//把右值给常量引用实际上可以;编译器实际上做了代码转换如下:
int temp = 20;
const int &c = temp ;
//C++11提供了右值引用
int &&c = 20 ; 20这个右值初始化右值引用c是可以的,正确;
我们看下这行代码的底层汇编:
int&& c = 20;
mov dword ptr [ebp-18h],14h
lea eax,[ebp-18h]
mov dword ptr [c],eax
汇编代码:把右值20放到栈上的一块临时变量上,然后把临时变量的地址放到寄存器,
然后把寄存器地址放到底层引用后面的指针变量上。
注:对于右值引用变量本身它是左值;右值引用必须引用右值,因为右值引用引用的是右值生成的那个临时量。
因为右值引用本身是左值,所以只能通过左值引用来引用右值引用。
int&& c = 20; //右值引用c,实际引用的是20右值生成的临时变量;右值引用只能引用右值;
//右值引用本身是左值,所以可以通过左值引用来引用右值引用。
int& d = c;
看看下面代码:
int a = 10;
int * p = &a;
const int * &q = p ; //错误
错误认识: 左边是const int * (引用不参与类型),右边是int* ,那么int*转const int*没问题啊;实际错误
我们还原成指针看看就是:cosnt int **q = &p ; 这是int** 转const int **是错误的。
看看下面代码错误的是:BCD
A:
int a = 10;
int * p = &a; //int*转int*正确
int * &q = p; //int*转int*正确,引用不参与运算;
//也可以这样判断:int**q= & p ; 左边int**,右边int**正确
B:
int a = 10;
int * const p = &a;//左边是int* ,右边是int*转换正确;const右边没有*,所以不参与类型;const只是修饰p为常量。
int * &q = p; //错误:不能把常量的地址泄露给一个普通的引用和普通的指针;p本身是个常量,赋值给q,那么q可以修改影响p了,显然不行;
C:
int a = 10;
const int *p = &a;
int * & q = p; //错误
转成指针形式判断:int** p = &p; 左边int ** ,右边const int ** ,转换错误
D:
int a = 10;
int * p = &a;
const int *&q = p; //错误
转换成指针形式判断:const int ** = &p ; 左边是const int **,右边是int **,转换错误