引用的含义
引用表示的给一个变量起一个别名,对引用的操作等同于对变量本身的操作。
引用的用法
因为函数的参数传递只存在 两种方式:
1.值传递
在调用函数时,会替形参在为函数分配的栈区中开辟内存空间并将实参的值拷贝一份给形参,在函数内部对形参的操作并不会影响函数外部实参的值。
2.地址传递
本质上也是值传递,只不过传递的值是地址,所以形参拷贝的值也是地址,虽然形参和实参的地址数据存放的地方不一样但它们是指向同一块内存空间的。所以当函数内部形参指向的内存空间的值发生改变后,实参的值也会相应发生改变。
以前在C语言阶段如果我们想要在函数内部修改函数外部传进来的参数也就是实参的值,必须通过地址传递的方式。
但是在c++中我们多了另一种选择,那就是引用。
1.引用做参数
#include<iostream>
using namespace std;
void swap(int &m,int &n)//使用引用
{
int temp;
temp = m;
m = n;
n = temp;
}
int main()
{
int a = 10;
int b = 20;
cout << a << " " << b << endl;
swap(a, b);
cout << a << " " << b<< endl;
system("pause");
return 0;
}
从运行结果可以看出:
使用引用作为函数参数,成功在函数内部改变了函数外部的实参的值。
引用做返回值
#include<iostream>
using namespace std;
int &fun(int &n)
{
return n;
}
int main()
{
int a = 10;
cout << a << endl;
cout << fun(a) << endl;
fun(a) += 10;
cout << a << endl;
system("pause");
return 0;
}
可以看出:
当我做fun(a) += 10;操作时a的值也加了10,因为此时fun(a)返回的是a的引用,对fun(a)操作也就相当于直接对a进行操作。
指针和引用传参的区别
#include<iostream>
using namespace std;
//引用传参
void quote(int &m, int &n)
{
//打印形参变量的存放地址
cout << &m <<" "<<&n<< endl;
}
//指针传参
void pointe(int *m,int *n)
{
//打印形参变量的存放地址
cout << &m << " " << &n << endl;
}
int main()
{
int a = 10;
int b = 20;
//*********************指针***************************
cout << "指针传参:" << endl;
int *p = &a;
int *q = &b;
cout << &p << " " << &q << endl; //打印实参存放地址
pointe(p, q); //打印形参存放地址
//*********************引用***************************
cout << "引用传参:" << endl;
cout << &a << " " << &b << endl; //打印实参存放地址
quote(a, b); //打印形参存放地址
system("pause");
return 0;
}
从结果可以看出:
就像上文提到的一样:指针传参,会为形参在栈区开辟一段空间用来存放实参的值(只不过值是地址)。实参和形参是指向同一块内存空间的两个变量;
而引用传递的形参和实参的存放地址是一样的也就是说形参就相当于实参,形参和实参之间的关系就是一个变量的两个名字。
常量引用
从上面我们已经知道,一个变量的引用其实就是给这个变量起了一个别名,编译器并不会单独为其分配空间,那么问题来了,我们都知道常量属于右值,是不能进行取地址操作的,如果我声明一个常量的引用,然后对这个引用做取地址操作会发生什么呢?
#include<iostream>
using namespace std;
int main()
{
const int &a = 10;
cout << a << " " << &a << endl;;
system("pause");
return 0;
}
可以看出:
我们成功地取出了引用的地址,也就是说我们做区地址操作的这个引用并不是存在于常量区。我们可以从反汇编层面看一下是为什么。
int main()
{
00D75FB0 push ebp
00D75FB1 mov ebp,esp
00D75FB3 sub esp,0DCh
00D75FB9 push ebx
00D75FBA push esi
00D75FBB push edi
00D75FBC lea edi,[ebp-0DCh]
00D75FC2 mov ecx,37h
00D75FC7 mov eax,0CCCCCCCCh
00D75FCC rep stos dword ptr es:[edi]
00D75FCE mov eax,dword ptr [__security_cookie (0D7B004h)]
00D75FD3 xor eax,ebp
00D75FD5 mov dword ptr [ebp-4],eax
const int &a = 10;
00D75FD8 mov dword ptr [ebp-18h],0Ah
00D75FDF lea eax,[ebp-18h]
00D75FE2 mov dword ptr [a],eax
cout << a << " " << &a << endl;;
00D75FE5 mov esi,esp
注意以下三行汇编指令
00D75FD8 mov dword ptr [ebp-18h],0Ah
00D75FDF lea eax,[ebp-18h]
00D75FE2 mov dword ptr [a],eax
首先在栈上找到ebp-18h偏移的内存双字,然后把十六进制的10存进去,之后再把ebp-18h偏移的内存双字放进eax寄存器,最后把a指向eax中的地址(指向ebp-18h偏移的内存)。
这些操作之后就像相当于:当我们声明一个常量的引用时,会首先在栈区搞个临时变量再把常量的值存在临时变量中,然后再对该临时变量进行引用操作,这也就解释了为什么常量无法取地址而常量的引用却可以取地址。