C++的引用
- 左值引用和右值引用
- 引用的实例
1)引用是一种更安全的指针。
2)引用只有一级引用,没有多级;指针可以有一级指针,也可以有多级指针。
int main()
{
int a = 10;
int *p = &a;//(指针)int *p = nullptr;编译链接没有错,但是没有输出结果
int &b = a;//(引用)
*p = 20;
//输出结果:20 20 20
cout << a << " " << *p << " " << b << endl;
b = 30;
//输出结果:30 30 30
cout << a << " " << *p << " " << b << endl;
return 0;
}
3)加断点后,转到反汇编代码,可以看出,从汇编代码的角度上里说,定义指针和定义引用说的意义是一样的,都是通过指针来实现的,都是先将a的地址放入寄存器eax,然后再将eax寄存器里面的地址放到p和b的4字节内存里;通过引用变量修改引用内存的值,和通过指针解引用修改指针指向的内存的值,其底层指令也是一模一样的。
int *p = &a;//int *p = nullptr;编译链接没有错,但是没有输出结果
013E245F lea eax,[a]
013E2462 mov dword ptr [p],eax
int &b = a;
013E2465 lea eax,[a]
013E2468 mov dword ptr [b],eax
但是注意,我们在定义引用变量时,根据定义,
1、必须初始化;
2、初始化时引用变量的右值必须是一个可以取地址的值。
所以以下代码是错误的:
int &c = 20;//20是一个不能取地址的值;
引用实际上相当于给数组arr起了一个别名,所以它的本质还是数组arr,如果对于使用引用不熟练,可以先定义一个指针变量,然后再将指针的取地址,换成要引用的变量,然后再用“ & ”将“ * ”
覆盖就是一个合理的引用了。
int main()
{
int arr[5] = {};
int *p = arr;
//通过指针int (*q)[5] = &arr;
//定义一个引用变量,来引用数组arr
int (&q)[5] = arr;
cout<<sizeof(arr)<<endl;
cout<<sizeof(p)<<endl;
cout<<sizeof(q)<<endl;
return 0;
}
-
左值引用
左值,有内存,有名字,值可以修改,不需要生成临时变量; -
右值引用
1、int &&e = 20;专门用来引用右值类型(没内存,没地址),指令上,可以自动生成临时量,然后再引用临时量,使 c = 40;
2、右值引用变量,本身是一个左值(由内存,有地址),只能用左值引用来引用它;
3、不能用一个右值引用变量来引用一个左值(不需要生成临时量)
int main()
{
//左值引用
int a = 10;
int &b = a;
//int &c = 20;//20是右值,没内存,没名字(错误)
//C++11以后提供了右值引用
int &&c = 20;
c = 30;
int &e = c;//一个右值引用变量,本身是一个左值(由内存,有地址)
/*
右值引用在汇编代码中的实际过程相当于,
int temp = 20;//定义一个临时变量
temp -> b //将这个临时变量赋给b
*/
const int &d = 20;
//d = 30;//d 不能被修改
system("pause");
return 0;
}
面试题:引用和指针的区别?
答:结合上述的1) 2) 3)进行回答。
const 、一级指针、引用的结合应用
左值引用,引用地址,可以改变值,即可以理解为“变量”
右值引用,引用具体的值,没内存,没地址,即可以理解为“常量”
引用不参与类型
关于引用、指针、const ,判断代码是否正确,如果感觉对引用的理解不是那么清楚,那么可以将引用变换成指针:
1、等号右边的 取地址符‘&’移除;
2、等号左边的 引用符 ‘ & ’换成 ‘ * ’。
面试题:
1、写一句代码,在内存的0x0018ff44处写一个4字节的10 ?
int *p = 0x0018ff44;(错)0x0018ff44是一个整数,必须加类型强转
int *p = (int*)0x0018ff44;//true
int *&p = (int*)0x0018ff44;//(error)
int * &&p = (int*)0x0018ff44;//(true)
int * const &p = (int*)0x0018ff44;// (true)
new 和 delete
关于new 和 delete,经常和C语言中的 malloc和free联系理解,大多数时候针对以下两个问题展开,
1、new 和 malloc的区别?
2、delete 和 free的区别?
new和malloc的区别:
1)在动态开辟内存时,因为new是自己指定类型的,所以不用类型强转,而malloc则没有指定类型,所以需要类型强转;
2)new 不仅可以做内存开辟,还可以做内存的初始化操作;malloc只开辟内存,不初始化,初始化需要另写代码。
//malloc
int *p=(int*)malloc(sizeof(int);
//new
int *b = new int (40);//为b开辟空间,且初始化为40
3)malloc开辟内存失败,通过返回值和nullptr作比较;而new开辟内存失败,通过抛出bad_alloc类型的异常来判断的,对于new来说,通过malloc的异常处理方式处理异常无意义,
int main()
{
int *p = (int*)malloc(sizeof(int));
if (p == nullptr)
{
return -1;
}
*p = 20;
free(p);
int *p1 = new int(20);
//if (p1 == nullptr) //对于new来说,通过以下方式处理异常无意义
//{
// return -1;
//}
try
{
int *p1 = new int(20);
}
catch (const bad_alloc &e)//返回异常时需要使用到特殊的头文件,这里就不讲了
{
}
int *p1 = new int(20);
delete p1;
return 0;
}
4)malloc和free,称作C的库函数;new和delete,称作运算符的重载函数operator new / delete。
delete 和 free的区别?
1)free只能释放内存,delete先析构函数,再释放内存(free)。
2)在释放内存时,free就是一个标准的函数调用,只需要传入一个起始地址就可以了,而delete,就是加不加“[]”的问题,释放单个元素不需要,释放数组时需要加上[];
int main()
{
//开辟数组
int *q = (int *)malloc(sizeof(int) * 20);
if (q == nullptr)
{
return -1;
}
free(q);
int *q1 = new int[20];//没有这种初始化的方式:int *q1 = new int[20](40),每个数组的元素初始化为40
int *q1 = new int[20]();//开辟数组后将每一个数组元素初始化为0,
//出错时,和前面的一样,同样是返回异常
delete[]q1;
return 0;
}
除此之外,我们还会经常遇到一个问题,那就是,new 有多少种?根据个人了解,有以下4种,
int main()
{
//new 有多少种?
int *p1 = new int(20);//第一种
int *p2 = new (nothrow) int;//第二种,不抛出异常版
const int *p3 = new const int(40);//第三种,开辟了一个常量内存,所以不能直接传给int *p3,需要加上const;
//第四种,定位new,在学习容器的空间配置器时一定会用,在堆上开辟了内存,但是具体的在哪开辟不知道。
int data = 0;
int *p4 = new (&data) int(50);//这个new操作把我们指定地址"&data"的内存,划分成一个整型内存块并把初值写成50
cout << "data: " << data << endl;
system("pause");
return 0;
}
第一种:内存开辟失败是通过捕获 bad_alloc异常 来判断内存开辟失败的。(抛出异常)
第二种:nothrow 不抛出异常版本的new,其返回值和nullptr判断(同malloc)(不抛出异常)
第三种:在堆上开辟了一个常量的内存,并初始化为40 。既然是常量,就只能把其地址给一个常量的指针来指向。
第四种:在容器的空间配置器上 是一定会用到的。这种new开辟内存,int* p4 = new int(50);是 在堆上开辟了一个内存,并初始化为50 。(但是这块内存在哪不清楚)