C++引用——类型+&+对象

在文章的开始可以先看这样一段C++代码:

int main(void)
{
	int a = 0;
	int& ra = a;
	int n = 0;
	int* pn = &n;
	return 0;
}

这段代码转换为汇编代码是这样的(由于没有在CSDN的代码选项里面找到汇编,我直接复制重要部分的汇编代码吧)

    int a = 0;
00007FF7E6EF224D  mov         dword ptr [a],0  
    int& ra = a;
00007FF7E6EF2254  lea         rax,[a]  
00007FF7E6EF2258  mov         qword ptr [ra],rax  
    int n = 0;
00007FF7E6EF225C  mov         dword ptr [n],0  
    int* pn = &n;
00007FF7E6EF2263  lea         rax,[n]  
00007FF7E6EF2267  mov         qword ptr [pn],rax  

可以看到,定义引用和定义指针的汇编代码并无区别。所以,我们可以把引用看作指针去理解,但是它又与指针有着许多的区别:

1.初始化

指针不初始化,代码可以运行,虽然会被编译器口头警告;但引用不一样,如果引用不进行初始化,代码是直接无法运行,并且引用的初始化赋值只能是一个对象,无法用NULL或者是nullptr对其初始化。

2.赋值

定义一个可读可写的指针,这个指针和变量一样是可以重新被赋值的 。 引用是否也可以被重新赋值呢?

#include<iostream>
using std::cout;
int main(void)
{
	int p = 9;
	int& b = p;
    int a = 10;
	b = a;
	//int& b = a;
	cout << b;
	return 0;
}

可以看到,b = a;这步操作只是把a的值赋给了b,而下面的int& b = a;这一操作是不被编译器所允许的——重复初始化。 从这里得出的结论是——引用无法用以上操作重新赋值。事实也确实如此,在C++语法中,引用无法重新被赋值。

3.多级引用?

指针有多级指针,那么引用是否也有呢?🤔

答案是没有,int ** p = &b 不会报错,但是int && d = b会。

4.大小

众所周知,指针大小是固定的(在同一平台)。那么引用呢?本质上也是指针,是否也是固定的呢?

#include<iostream>
using namespace std;
int main(void)
{
	char c = 0;
	int a = 0;
	char& rc = c;
	int& ra = a;
	cout << sizeof(ra) << endl;
	cout << sizeof(rc) << endl;
	return 0;
}

可以看到,输出的值随着类型的变化而变化。

5.计算

引用本质上作为指针,对它进行计算操作会怎么样呢?

#include<iostream>
using namespace std;
int main(void)
{

	int a = 0;
	int& ra = a;
	cout << ++ra << endl;
	return 0;
}

可以看到,是对其引用对象进行了计算操作。

到此为止,相信你对引用已经有了一个大概的认识,接下来就是我认为较难理解的部分细节。

1.权限

int main(void)
{
	const int a = 0;
	int& ra = a;
	return 0;
}

这段代码在VS中会报错:qualifiers dropped in binding reference of type "int &" to initializer of type "const int"

嗯......大概意思是:用int&类型引用const int 类型缺少修饰词

确实,将代码int& ra = a;改为const int&ra;就不会报错。

所以当权限不一样时就不行?继续看如下代码:

int main(void)
{
	int a = 0;
	const int& ra = a;
	return 0;
}

虽然权限不一样,但这样却不会报错。所以可以理解为:引用既定对象时,无法扩大权限,但可缩小。

再看:

int main(void)
{
	int a = 1;
	float b = a;
	float& rb = b;
	float& ra = a;
	return 0;
}

在VS上跑一下,编译器直接指出   a reference of type "float &" (not const-qualified) cannot be initialized with a value of type "int" ——需要const修饰(报错代码为:float& ra = a;)

果不其然,加上修饰词const就会报错了(当然,隐式转换会有警告,但无关紧要)

这又是为什么呢?

拙见——a为int类型,如果允许通过ra对变量a进行修改,就是将一个浮点型数据写入整形变量中,很明显:这种操作是绝对不被允许的。所以,加上const修饰就好了,只能从a所在内存读出数据。

2.引用可做参数

非常好理解,就像传地址调用一样,但不同类型定义有一些出入,直接看代码:

void Test_n(int& num)
{

}
void Test_p(int*& ptr)
{

}
void Test_a( int (&arr)[5])
{

}
int main(void)
{
	int n = 0;
	int* p = nullptr;
	int myarr[5] = { 0 };
	Test_n(n);
	Test_p(p);
	Test_a(myarr);
	return 0;
}

3.引用可做返回值

#include<iostream>
using namespace std; 
int& Test(int a)
{
	int c = a + 1;
	return c;
}
 int main(void)
 {
	 int num = 0;
	 int& tmp = Test(num);
	 Test(2);
	 cout << tmp << endl;
	 printf("%d\n",tmp);
	 cout << tmp << endl;
	 return 0;
 }

运行结果如下:

  

 很奇怪,但是可以解释。部分汇编代码如下:

     int num = 0;
00007FF7E5CE1A7B  mov         dword ptr [num],0  
     int& tmp = Test(num);
00007FF7E5CE1A82  mov         ecx,dword ptr [num]  
00007FF7E5CE1A85  call        Test (07FF7E5CE1519h)  
00007FF7E5CE1A8A  mov         qword ptr [tmp],rax  

拓展:在 X64调用约定中,ecx寄存器用于调用第一个整型参数,函数返回值(如果是整形或者是指针)存储在rax寄存器中。

这段代码其实有一个警告: warning C4172: returning address of local variable or temporary: c

——返回值可能是地址或者是临时变量c。

所以,在主函数中用什么类型的变量接收,返回值就是什么类型。这里我们用了一个引用变量来接收,返回的自然就是指针。

我们或许又会有疑问——返回指针输出的不应该是地址吗?并非如此,用引用变量接收后,它就变成了一个纯正的引用变量,编译器会自动处理,不再像指针一样需要自己去进行解引用操作。

如此一来,我们就可以解释了,引用变量可以直接访问到函数所在栈区(为编译器与操作系统协同工作),但是在函数调用结束后,函数所在栈区被销毁(操作权还给操作系统)。

这里可以看出VS的编译器在函数调用结束后并不会清空其中数据,但会随着其他函数的调用导致其中的数据被修改。所以这里也能看出—— printf(其实是所有的C语言库函数)会调用栈区,但cout不会。(这是我们以后探讨的问题了)。

刚刚提到过,返回值分为两种,如果我们用整形变量来接收呢。答案是——确实解决了非法访问的问题,但是为什么不直接用int定义函数呢?

返回值如果为非引用类型,编译器就会创建一个临时变量来持有返回值,引用类型返回值则不会(具体原因和证明就不再赘述,以后再详细展开吧)所以为了追求极致的性能,C++在能用的地方肯定是选择引用类型的返回值。

那有没有解决办法呢?也肯定是有的

1.动态内存分配

#include<iostream>
using namespace std
int& Test(int a) 
{
    int* c = new int(a + 1);
    return *c; 
}

int main() 
{
    int num = 0;
    int& tmp = Test(num);
    cout << tmp << endl;
    delete &tmp;
    return 0;
}

但是开辟内存和释放内存是否又会增加时间成本呢? 

2.用静态变量

#include<iostream>
using namespace std;
 int& Test(int a)
{
	static int c = a + 1;
	return c;
}
 int main(void)
 {
	 int num = 0;
	 int& tmp = Test(num);
	 Test(2);
	 cout << tmp << endl;
	 printf("%d\n",tmp);
	 cout << tmp << endl;
	 return 0;
 }

这样也可以解决,将返回值单独放在静态区,典型的以空间换时间。

综上,就是我目前对引用的全部理解了,欢迎大家补充。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值