目录
一、 引用的概念
二、引用的特性
1. 引用在定义时必须初始化
2. 一个变量可以有多个引用
3. 引用一旦引用一个实体,再不能引用其他实体
三、使用场景
1.做参数
2.做返回值
3.使用引用的两个好处
3.1 减少拷贝提高效率
3.2可以修改返回值
四、常引用
五、引用和指针的区别
一、 引用的概念
引用其实就相当于是一个人的外号或者小名,比如宋江的外号叫及时雨,林俊杰的叫JJ,我的小名叫狗蛋(谁愿意要这样的小名啊喂!!!)。
引用本质上不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。
[类型] + [&] + [引用变量名(对象名)] = [引用实体] ;
int main()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
下图一我们可以看到 a 和 ra 的地址是一样的,这也进一步说明了引用不会创建新变量,而只是一个别名。图二表示引用类型必须和引用实体是同种类型的,否则就会报错,编译不过。
二、引用的特性 1. 引用在定义时必须初始化 2. 一个变量可以有多个引用
int main()
{
int a = 10;
int& ra = a;
int& rra = a;//可以有多个引用
printf("%p %p %p\n", &a, &ra, &rra);
}
3. 引用一旦引用一个实体,再不能引用其他实体
下图这种就属于赋值了,如果像指针一样用,前面加个 & (引用符号)会直接报错,它和指针的用法是略有不同的。
三、使用场景 1.做参数
下面有三种方式,传值,传址和引用,这样看引用是不是很方便呢?没有对比就没有伤害呀。
//这里是传值调用,参数是形参,
//形参是实参的一份临时拷贝,改变形参不会改变外面的实参
void Swap(int left, int right)
{
int temp = left;
left = right;
right = temp;
}
//这里是传址调用,也就是传地址,指针指向原来的实参
//解引用后就找到实参的内容,就可以对实参进行修改
void Swap(int* left, int* right)
{
int temp = *left;
*left = *right;
*right = temp;
}
//这里是引用,这里的参数是实参的别名,
//可以直接使用进行修改
void Swap(int& left, int& right)
{
int temp = left;
left = right;
right = temp;
}
2.做返回值
做返回值时使用不当特别容易出错,这里会涉及一点函数栈帧的细节,且听我慢慢道来。
如下图二,在main函数里调用Count函数时,会开辟一个新的名为Count的函数栈帧,n是在Count栈帧里创建的临时变量,存在栈帧中,返回的时侯不是把n的地址返回给 ret ,而是把值传给一个叫寄存器的东西。因为函数调用完毕后会把栈帧销毁,而存在栈帧中的临时变量也随之被销毁(这里的销毁指把内存还给 *** 作系统,只是没有了使用权,而不是真正的销毁掉了)。这是n的值被放在寄存器中返回给ret,ret接受到了值就可以进行其他的 *** 作了。
下图一为什么会加一个 static 修饰呢?如果没有加 static 修饰n的话,那么返回时会把 n 销毁掉,寄存器也有地址,由于返回类型是 int 的引用,会把寄存器当成 n 的别名返回给 ret ,因为 n 的空间已经还给 *** 作系统了,但是通过别名可以访问到 n 的空间,这时打印 ret 的值是随机的。用 static 修饰的 n 的空间不会被销毁,所以可以用引用做这里的返回值。
#include
using namespace std;
int& Count()
{
static int n = 0;
n++;
return n;
}
int main()
{
int ret = Count();//这里返回的是n的别名
cout << ret << endl;
return 0;
}
#include
using namespace std;
int Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int ret = Count();
cout << ret << endl;
return 0;
}
注:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以使用 引用返回,如果已经还给系统了,则必须使用传值返回。
- 下图我们可以看到ret ,n ,x的地址是一样的,而 ret 调用第二次就变成了随机值,调用 Func 函数后 ret 的值变成了 Func 函数里 x 的值了。如果在实际项目中使用后果很严重。
- 由此可以得出:出了函数作用域,返回变量不存在了,不能使用引用返回,因为引用返回的结果是未定义的。出了函数作用域,返回变量存在,才能使用引用返回。
3.使用引用的两个好处 3.1 减少拷贝提高效率
下图可以看到引用返回的效率还是比较高的。(单位是毫秒)
3.2可以修改返回值下图可以看到我们通过 ret 直接对 n 的值进行了修改。
四、常引用
- 一般引用做参数都是用 const 引用
//指针和引用赋值,权限可以缩小,但是不能放大
void Func(const int& num)//既可以接收普通值,也可以接收const修饰的值
{
}
int main()
{
int a = 0;
//权限平移
int& ra = a;
const int b = 10;
//我引用你,把权限放大了,是不行的
int& rb = b;//会报错
//我引用你,把权限缩小了,可行
const int& rra = a;
//我的权限缩小不代表你的权限也缩小了
rra++;//会报错
a++;//可以用
Func(rra);
Func(ra);
Func(b);
return 0;
}
五、引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
下图可以看到,引用和指针的汇编指令是一样的。
引用和指针的不同点:
1. 引用在概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全
本章是我对引用的一些看法和了解,有不对的地方请大家批评指正。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)