C++引用

一、引用概念

        引用即为某个已存在的变量取别名,引用变量与被引用变量公用一块内存空间,比如西红柿、番茄都是同一物品的不同命名。通过在数据类型后、变量名前添加“&”符号来定义引用类型。

int ival = 1024;
int &rename_ival = ival;//rename_ival为变量ival的引用(rename_ival为ival的别名)

二、引用特性

1、引用必须初始化

        引用实质为“取别名”,故在取别名前必须已有名字,那么其实也要求了被引用的必须为一个对象。

2、一旦引用一个对象将无法引用其他对象

        一般在初始化变量时,初始值会被拷贝到新建对象,而在定义引用时则是将初始值与引用进行绑定,故引用一旦引用一个对象将无法引用其他对象,对引用的赋值实质为对被引用对象的赋值,以下代码即可证明。

#include <iostream>
int main()
{
    int ival = 1024;
    int& rename_ival = ival;
    int new_ival = 2048;
    rename_ival = new_ival;
    std::cout << "ival = " << ival << std::endl;
    std::cout << "rename_ival = " << rename_ival << std::endl;
    return 0;
}

  3.一个变量可有多个引用

         一个变量可以有多个引用,可通俗理解为一个人可以有多个昵称。

三、const引用

         const引用即将引用与const对象绑定。由于const引用是为const对象取别名,故无法通过修改const引用修改被引用的const对象。

//使用场景1
const int ival = 1024;
const int &reival1 = ival;//正确
int &reival2 = ival;//错误:将非常量引用指向常量对象,将被引用对象的只读特性改变为可读可写,是权限的放大,不被允许
int ival3 = 2048;
const int &ival4 = ival3;//const引用可引用非常量对象,允许权限的缩小。
//使用场景2
#include <iostream>
int main()
{
	double ival1 = 1024.1024;
	//int& ival2 = ival1;错误
    const int& ival2 = ival1;
	std::cout << ival2 << std::endl;
	return 0;
}

         针对使用场景2,可以发现变量ival3引用了一个int类型的变量,但ival2却为double类型的变量,发生了隐式类型转换。在此情况下使用非const引用会提示错误。

        为了确保让ival3绑定一个整形对象,编译器将相关代码做了如下变换:

const int temp = ival1;
const int &ival2 = temp;

        在此情况下,ival2实际引用了一个临时变量,该临时变量暂存了表达式的结果。因为临时变量具有常性,所以必须使用const引用。

四、使用场景

1、做参数

#include <iostream>
void Swap(int& left, int& right)
{
    int temp = left;
    left = right;
    right = temp;
}

int main()
{
    int left = 1024;
    int right = 2048;
    Swap(left, right);
    std::cout << "left = " << left << std::endl;
    std::cout << "right = " << right << std::endl;
    return 0;
}

        如上代码所示使用引用作为形参,在函数被调用时实质就是传递了实参。

2、做返回值

         在理解引用返回前,先看一下传值返回,代码如下:

#include <iostream>
int Add(int num1, int num2)
{
    int sum = num1 + num2;
    return sum;
}

int  main()
{
    int ret = Add(1, 2); //int& ret = Add(1, 2);
    return 0;
}

        通过上述代码可能会比较直接的理解为在main函数中调用了Add函数,两数求和后将返回值sum赋值给ret。但事实并非如此,实际上传值返回并未直接把sum的值赋值给ret,而是先将其值拷贝存放进了临时变量,通过临时变量再赋值给ret。将ret设置为引用类型后,不难证明却有临时变量的存在。临时变量若较大则会存放于调用层函数的栈帧中,若临时变量较小(一般在4/8byte内)则会存放在寄存器中。

         那么再来看看传引用返回,与传值返回不同的是返回了sum对象的引用,虽然与传值返回一样产生了临时变量,但此时临时变量只是返回了一个名称,而并不是一块具体的空间。通过如下代码同样可以验证。

#include <iostream>
int& Add(int num1, int num2)
{
    int sum = num1 + num2;
    return sum;
}

int  main()
{
    int& ret = Add(1, 2); 
    return 0;
}

           然而,这上述代码却存在一个很明显的问题,即当Add函数调用完毕后,函数栈帧将会销毁,那么sum的取值就会存在非法访问,取值有可能是3也有可能会是一串随机值,这取决于sum空间中的内容是否有改动,倘若在ret所在行的后一行添加一串代码,那么ret的值将会被修改为随机值。

         于是,对于引用返回便有了如下的使用规则:若返回对象在函数调用结束后还会继续存在则可以使用引用返回,如静态变量,反之则不宜使用。

五、引用和指针

1、相同点

#include <iostream>
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;
}

int main()
{
    int left = 1024;
    int right = 2048;
    Swap(&left,&right);
    Swap(left, right);
    std::cout << "left = " << left << std::endl;
    std::cout << "right = " << right << std::endl;
    return 0;

       引用和指针从底层来看,并没有什么区别,引用在底层实现上与指针相同。引用只是c++语法糖,可以将其看作编译器自动完成取地址、解引用的常量指针。

void Swap(int& left, int& right)//引用:汇编指令
{
00E119B0  push        ebp  
00E119B1  mov         ebp,esp  
00E119B3  sub         esp,0CCh  
00E119B9  push        ebx  
00E119BA  push        esi  
00E119BB  push        edi  
00E119BC  lea         edi,[ebp-0Ch]  
00E119BF  mov         ecx,3  
00E119C4  mov         eax,0CCCCCCCCh  
00E119C9  rep stos    dword ptr es:[edi]  
00E119CB  mov         ecx,offset _19C4B5D4_list@cpp (0E1F066h)  
00E119D0  call        @__CheckForDebuggerJustMyCode@4 (0E11384h)  
    int temp = left;
00E119D5  mov         eax,dword ptr [left]  
00E119D8  mov         ecx,dword ptr [eax]  
00E119DA  mov         dword ptr [temp],ecx  
    left = right;
00E119DD  mov         eax,dword ptr [left]  
00E119E0  mov         ecx,dword ptr [right]  
00E119E3  mov         edx,dword ptr [ecx]  
00E119E5  mov         dword ptr [eax],edx  
    right = temp;
00E119E7  mov         eax,dword ptr [right]  
00E119EA  mov         ecx,dword ptr [temp]  
00E119ED  mov         dword ptr [eax],ecx  
}
void Swap(int* left, int* right)//指针:汇编指令
{
00BB19B0  push        ebp  
00BB19B1  mov         ebp,esp  
00BB19B3  sub         esp,0CCh  
00BB19B9  push        ebx  
00BB19BA  push        esi  
00BB19BB  push        edi  
00BB19BC  lea         edi,[ebp-0Ch]  
00BB19BF  mov         ecx,3  
00BB19C4  mov         eax,0CCCCCCCCh  
00BB19C9  rep stos    dword ptr es:[edi]  
00BB19CB  mov         ecx,offset _19C4B5D4_list@cpp (0BBF066h)  
00BB19D0  call        @__CheckForDebuggerJustMyCode@4 (0BB1384h)  
    int temp = *left;
00BB19D5  mov         eax,dword ptr [left]  
00BB19D8  mov         ecx,dword ptr [eax]  
00BB19DA  mov         dword ptr [temp],ecx  
    *left = *right;
00BB19DD  mov         eax,dword ptr [left]  
00BB19E0  mov         ecx,dword ptr [right]  
00BB19E3  mov         edx,dword ptr [ecx]  
00BB19E5  mov         dword ptr [eax],edx  
    *right = temp;
00BB19E7  mov         eax,dword ptr [right]  
00BB19EA  mov         ecx,dword ptr [temp]  
00BB19ED  mov         dword ptr [eax],ecx  
}

2、不同点    

1、在语法概念上,引用只是定义了对象的别名,并未新空间;指针存储对象的地址。

2、引用必须初始化,而指针并无要求。

3、有空指针,无空引用。

4、引用与被引用对象绑定,一个对象可被引用多次,但一个引用不可引用多个对象;指针可指向不同的对象。

5、自增自减意义不同,引用自增自减即对被引用对象进行修改;指针则指指针运算,表明指针的偏移。

6、引用的使用比指针安全。

7、引用没有顶层const修饰,如 int& const。因为引用本身就与被引用对象绑定不可变,顶层const就失去了其意义;指针既有顶层const又有底层const修饰。

8、访问实体方式不同,指针需要显式解引用,引用则由编译器自动完成取地址、解引用。

9、在sizeof中含义不同,引用结果为被引用对象类型的大小,而sizeof指针的大小则为4或8byte。


EOF

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZHOUZH_093

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值