1.引用概念
类型名& 引用变量名(对象名)=引用实体;
引用不是新定义一个变量,而是给已经存在的变量取一个别名,编译器不会为引用变量开辟空间,他和他的引用共用同一块内存
举例
注意:引用类型必须和引用实体是同种和类型的
void Test()
{
int a = 10;
int& ra = a;
}
2.引用特点
- 引用在定义时必须初始化
- 一个变量可以有多个引用
- 引用一旦引用了一个实体,变不能引用其他实体
举例
void Test()
{
int a = 10;
int b = 20;
int& ra;
int& ra = a;
int& rra = a;
int& ra = b;
3.常引用
void Test()
{
const int a = 10;
const int& ra = a;
const int& b = 10;
double d = 12.34;
const int& rd = d;
}
4.引用应用场景
1.做参数
void Swap(int *a, int *b)
{
int temp = *a;
*a = *b;
*b = temp;
}
int main()
{
int a = 10;
int b = 20;
Swap(&a, &b);
return 0;
}
void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a, b);
return 0;
}
2.使代码更加简洁化
struct A
{
int a;
int b;
struct B
{
int c;
};
B stuB;
};
int main()
{
A stuA;
stuA.stuB.c = 10;
stuA.stuB.c = 20;
stuA.stuB.c = 30;
int& rc = stuA.stuB.c;
rc = 40;
rc = 50;
}
3.做返回值
int& Add(int left, int right)
{
int ret = left + right;
return ret;
}
int main()
{
int& result = Add(1, 2);
Add(3, 4);
Add(5, 6);
return 0;
}
应用3的代码看起来是不是有点奇怪,result的值为什么会变呢?
1.首先,我们利用监视窗口发现result 和 ret的地址相同
- 其实result引用的实际是Add函数体中的ret局部变量,当Add函数运行结束后,该函数中的ret局部变量的空间就被回收了
- result实际引用的就是一块非法空间
2.想要彻底搞清楚,需要从函数调用背后来梳理
esp和ebp是两个寄存器,来标记栈帧的栈顶和栈底
3.Add函数运行结束后,他并没有清理栈帧中所留下的垃圾数据,result是ret的别名,因此可以看到空间中的垃圾数据 所以我们需要注意:
📍一定不能返回函数栈上的空间---->典型:局部变量
因为函数结束后,函数体内部的局部变量就被销毁了,如果外部以引用的方式来接收函数的返回值,外部引用实际引用的就是一块非法的内存空间
📍那我们可以返回什么样的变量实体呢?
只要返回的实体、变量不受函数结束而销毁就可以,例如:全局变量、局部静态变量、引用类型的变量
举例
看如下代码:
#include <iostream>
using namespace std;
int& Add(int a, int b)
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
cout << ret << endl;
system("pause");
return 0;
}
我们先将Add(3, 4)注释掉, 结果是3 Add(3, 4)不注释的结果是7
5.传值、传引用效率比较
以值作为参数或者返回值类型,再传值和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或返回变量的一份临时拷贝,因此用值作为参数或者返回值类型,效率使非常低的,尤其是当参数或者返回值类型非常大时,效率更低
5.1参数举例
#include <time.h>
struct A
{
int a[10000];
};
void Test1(A a){}
void Test2(A& a) {}
void Test()
{
A a;
size_t begin1 = clock(0;
for(size_t i = 0; i < 10000; ++ i)
Test1(a);
size_t end1 = clock();
size_t begin2 = clock();
for(size_t i = 0; i < 10000; ++ i)
Test2(a);
size_t end2 = clock();
cout << "Test1(A)-time:" << end1 - begin1 << endl;
cout << "Test2(A&)-time:" << end2 - begin2 << endl;
}
结果
5.2返回值举例
#include <time.h>
struct A
{
int a[10000];
};
A a;
void Test1(){return a;}
void Test2() {return a;}
void Test()
{
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
Test1();
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
Test2();
size_t end2 = clock();
cout << "Test1-time:" << end1 - begin1 << endl;
cout << "Test2-time:" << end2 - begin2 << endl;
}
结果
6.指针和引用的共性
在语法概念上,引用就是一个别名,没有独立空间,但是其实引用的底层实现,就是按照指针的方式实现的从底层来看,引用就是指针,引用实际也是有空间的,因为引用就是指针,他里面存储的是引用实体的地址
传引用的汇编代码
传地址的汇编代码
📍不是说编译器不会给引用类型的变量开辟空间嘛?
其实概念上这么说的原因是为了我们更好的理解引用,但是引用的底层实现技术就是指针
📍那为什么还需要引用呢?
那就要谈谈指针和引用在概念,特性,和应用上的区别了
补充:
指针不是因为操作系统是64位的,就是8字节,而是因为按照64位的方式来编译,才是8字节
7.指针和引用的不同
1.引用在定义时必须初始化,但是指针没有要求 2.引用在初始化引用一个实体后,不能再引用其他实体了,而指针可以在任何时候指向任何一个同类型实体 3.没有NULL引用,有NULL指针 4.在sizeof中含义不同,引用的结果是引用类型的大小,但是指针始终是地址空间所占字节的个数(4/8字节) 5.引用自加即引用的实体加一,指针自加即指针向后偏移一个类型大小 6.有多级指针,没有多级引用 7.访问实体的方式不同,指针需要显式解引用,引用编译器自己处理 8.引用比指针使用起来相对安全
|