对C++引用的初步认识

C++的引用是变量的别名,是同一块内存空间。

#include <iostream>
using namespace std;
int main()
{
	int a = 10;
	int& ra = a;
	cout << &a << endl;
	cout << &ra << endl;
	return 0;
}

 逻辑上,引用是别名,不占用内存空间;底层物理实现上,用的还是指针,还是占用内存空间的。

int main()
{
	int a = 10;

	int& ra = a;
	ra = 20;

	int* pa = &a;
	*pa = 30;
	return 0;
}

以上代码对应的汇编如下:

76: 	int a = 10;
00901FEF C7 45 F4 0A 00 00 00 mov         dword ptr [a],0Ah  
    77: 
    78: 	int& ra = a;
00901FF6 8D 45 F4             lea         eax,[a]  
00901FF9 89 45 E8             mov         dword ptr [ra],eax  
    79: 	ra = 20;
00901FFC 8B 45 E8             mov         eax,dword ptr [ra]  
00901FFF C7 00 14 00 00 00    mov         dword ptr [eax],14h  
    80: 
    81: 	int* pa = &a;
00902005 8D 45 F4             lea         eax,[a]  
00902008 89 45 DC             mov         dword ptr [pa],eax  
    82: 	*pa = 30;
0090200B 8B 45 DC             mov         eax,dword ptr [pa]  
0090200E C7 00 1E 00 00 00    mov         dword ptr [eax],1Eh

一个变量可以有多个引用,即多个别名。

引用声明的时候必须初始化。        

引用一旦建立,就不能更改,与其他语言的引用在这点不同,C++在这点上就不能摆脱指针。

	int a = 10;
	int& ra = a;
	int b = 20;
	ra = b;
    cout << a << endl;

此处是把b的值赋值给ra,而不是让ra变成b的引用。

 引用的价值:

1. 作为函数的输出型参数

形参是实参的别名,虽然形参引用在形式上好像没有初始化,但实际上形参在函数栈帧建立时被创建并且接受了实参,还是初始化了的。

2. 引用做返回值

//传值返回
int Count()
{
	int n = 0;
	n++;
	return n;
}

//引用返回
int& Count()
{
	int n = 0;
	n++;
	return n;
}

在传值返回情况下,n不会直接返回,而是通过一个临时变量,类型为int,当n比较小时,一般用存在寄存器里,当n比较大的时候,存在上层栈帧里。

为什么要用临时变量?因为n可能是局部变量,返回时n已经销毁,n原来占用的内存还在,但已经没有使用权,里面的值可能还在,也可能已经变成随机值,具体取决于编译器如何处理。

传值返回会多一层拷贝,即临时变量的拷贝。

在引用返回的情况下,可以认为也生成了临时变量,类型是int&,int&临时变量就是n,所以是直接返回了n,但这里的n是局部变量,n已经销毁,这时如果访问n就相当于野指针的访问。

	int ret = Count();
	cout << ret << endl;
	cout << ret << endl;


    int& ret = Count();
	cout << ret << endl;
	cout << ret << endl;

上面两段代码的区别在于:

第一段代码是Count返回了局部变量的引用,这个引用的值被赋值给ret,这个赋值只发生了一次,即对销毁了的n访问了一次,所以两个cout可能全是1也可能全是随机值。

第二段代码给Count返回的局部变量的引用又起了新的别名--ret,即ret也是n的引用,所以对这个已经销毁的内存n连续访问了两次,两次的结果就可能不同。

不要用引用返回局部变量。

出了函数作用域,变量还存在,才能用引用返回。

引用作为返回值意义:

1. 提高效率,减少拷贝

2. 修改返回值

void Swap(int a, int b);
void Swap(int& a, int& b);

是否构成重载?

要看编译器函数名修饰规则,但不管是否构成重载,调用的时候都会出错,因为不能明确调用哪一个函数。所以语法虽然可以构成重载,但调用有歧义。

const引用

	int a = 1;
	int& ra = a;

	const int b = 2;
	int& rb = b;//err

指针和引用赋值中,权限可以平移或者缩小,但是不能放大。

此处的权限指的是引用的权限,原变量的权限不受影响。

一般引用做参数都用const。

	int a = 0;
	const int b = 2;
	a = b;

此处不涉及权限的问题,因为a得到的是b的拷贝,a的变化不会影响b。

	const int b = 2;
	int& a = b;

而此处就会涉及权限,因为a是b的引用,a的变化会影响b,从b到a权限变大,所以编译不能通过。

所以如果函数参数用const引用的话,就不会出现权限问题,即各种带const的实参也都可以传进来;如果确实需要形参来改变实参,那就不要用const引用,用一般的引用即可。

const int& size = 10;

常量可以有引用。

	double d = 3.14;
	int i = d;
	int ai = (int)d;
    
    cout<<(int)d<<endl;

类型转换(包括强制类型转换,截断,提升等)都会产生临时变量,这里打印出来的就是临时变量。临时变量具有常性,也就是不能被修改。所以

	double d = 3.14;
	int& rd = d;//err

这里rd试图成为d的临时变量的引用,而临时变量是常量,所以这里会出错。但是,改为:

	double d = 3.14;
	const int& rd = d;

就可以了,rd就是临时变量3的常量引用,所以rd的值就是3。

int Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	int& ret = Count();//err

	return 0;
}

上面的代码也会报错,原理是一样的,Count返回的是n的拷贝,是个临时变量,具有常性,所以不能用int&的ret接收,而需要用const int&的ret来接受,如下:

int Count()
{
	int n = 0;
	n++;
	return n;
}
int main()
{
	const int& ret = Count();

	return 0;
}

引用和指针的不同点:

1. 引用概念上定义一个变量的别名,指针存储一个变量地址。

2. 引用在定义时必须初始化,指针没有要求。

3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何 一个同类型实体。
4. 没有NULL引用,但有NULL指针 5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32 位平台下占4个字节)。
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小。

7. 有多级指针,但是没有多级引用

8. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
9. 引用比指针使用起来相对更安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值