• C++函数中返回引用和返回值的区别


    转载地址:https://www.cnblogs.com/JMLiu/p/7928425.html

    一、主要讨论下面两个函数的区别:

    int& at()
    {
        return m_data_;
    }
    int at()
    {
        return m_data_;
    }

    上面两个函数,第一个返回值是int的引用int&,第二个返回值是int,二者的区别是什么呢?

    我们先用一个语句 const int& a = mymay.at(); 来分别调用一次上面两个函数,然后看汇编语言的结果。

    反汇编结果:

    复制代码
     1 #int& at()
     2 #{
     3 #    return m_data_;
     4 #}
     5 
     6 00BB6830  push        ebp  
     7 00BB6831  mov         ebp,esp  
     8 00BB6833  sub         esp,0CCh  
     9 00BB6839  push        ebx  
    10 00BB683A  push        esi  
    11 00BB683B  push        edi  
    12 00BB683C  push        ecx  
    13 00BB683D  lea         edi,[ebp-0CCh]  
    14 00BB6843  mov         ecx,33h  
    15 00BB6848  mov         eax,0CCCCCCCCh  
    16 00BB684D  rep stos    dword ptr es:[edi]  
    17 00BB684F  pop         ecx  
    18 00BB6850  mov         dword ptr [this],ecx  
    19         m_data_++;
    20 00BB6853  mov         eax,dword ptr [this]  
    21 00BB6856  mov         ecx,dword ptr [eax]  
    22 00BB6858  add         ecx,1  
    23 00BB685B  mov         edx,dword ptr [this]  
    24 00BB685E  mov         dword ptr [edx],ecx  
    25         return m_data_;
    26 #取地址this中的值5879712(m_data_的地址)到寄存器eax中,此时寄存器eax存的是m_data_的地址
    27 00BB6860  mov         eax,dword ptr [this]  
    28     }
    29 00BB6863  pop         edi  
    30 00BB6864  pop         esi  
    31 00BB6865  pop         ebx  
    32 00BB6866  mov         esp,ebp  
    33 00BB6868  pop         ebp  
    34 00BB6869  ret  
    35 
    36 
    37 
    38 
    39  
    40     const int& a = mymay.at();    
    41 00176AA2  lea         ecx,[mymay]  
    42 00176AA5  call        MyMat::at (0171546h)  
    43 #此时寄存器eax中的值为m_data_的地址5879712,直接将地址5879712存入地址a中。
    44 00176AAA  mov         dword ptr [a],eax  
    45     cout << a << endl;
    复制代码
    复制代码
     1 #int at()
     2 #{
     3 #    return m_data_;
     4 #}
     5 
     6 
     7 012B6830  push        ebp  
     8 012B6831  mov         ebp,esp  
     9 012B6833  sub         esp,0CCh  
    10 012B6839  push        ebx  
    11 012B683A  push        esi  
    12 012B683B  push        edi  
    13 012B683C  push        ecx  
    14 012B683D  lea         edi,[ebp-0CCh]  
    15 012B6843  mov         ecx,33h  
    16 012B6848  mov         eax,0CCCCCCCCh  
    17 012B684D  rep stos    dword ptr es:[edi]  
    18 012B684F  pop         ecx  
    19 012B6850  mov         dword ptr [this],ecx  
    20         return m_data_;
    21 #和上面一样,也是先取出m_data_的地址
    22 012B6853  mov         eax,dword ptr [this]
    23 #和上面不一样,不是直接将m_data_的地址放入寄存器eax中,而是取地址5879712中的值(m_data_=3)放入寄存器eax中,此时寄存器eax存的是m_data_的值(3)
    24 012B6856  mov         eax,dword ptr [eax]  
    25     }
    26 012B6858  pop         edi  
    27 012B6859  pop         esi  
    28 012B685A  pop         ebx  
    29 012B685B  mov         esp,ebp  
    30 012B685D  pop         ebp  
    31 012B685E  ret  
    32 
    33 
    34 
    35 
    36   
    37     const int& a = mymay.at();    
    38 008E6AA2  lea         ecx,[mymay]  
    39 008E6AA5  call        MyMat::at (08E154Bh) 
    40 #此时eax的值为3,将3存入地址ebp-24h中,
    41 008E6AAA  mov         dword ptr [ebp-24h],eax 
    42 #将eax的值变成ebp-24h 
    43 008E6AAD  lea         eax,[ebp-24h]  
    44 #将地址ebp-24h写到地址为a中,此时a代表的地址是ebp-24h
    45 008E6AB0  mov         dword ptr [a],eax  
    46     cout << a << endl;
    复制代码

    所以结论就是:

    1、返回值为引用型(int& )的时候,返回的是地址,因为这里用的是 int& a=mymay.at(); ,所以a和m_data_指的是同一块地址(由寄存器eax传回的5879712)。

    2、返回值不是引用型(int)的时候,返回的是一个数值。这个时候就很有意思了,编译器是先将这个数值放入一个内存中(上面例子中,该内存地址为ebp-24h),再把这个地址付给a,此时的a代表的地址是ebp-24h,和m_data_代表的地址不一样(m_data_代表的地址是5879712)。

    3、综上两点可以看出,当返回的值不是引用型时,编译器会专门给返回值分配出一块内存的(例子中为ebp-24h)。

    二、说明一下函数返回时,如果不是返回一个变量的引用,则一定会生成一个临时变量。

    看下面的函数,返回的是t而不是&t,所以一定会有临时变量产生。

    1 T function1(){
    2     T t(0);
    3     return t;
    4 }
    5 T x=function1();

    这里的过程是:
    1.创建命名对象t
    2.拷贝构造一个无名的临时对象,并返回这个临时对象
    3.由临时对象拷贝构造对象x
    4.T x=function1();这句语句结束时,析构临时对象
    这里一共生成了3个对象,一个命名对象t,一个临时对象作为返回值,一个命名对象x。

    下面的函数稍微复杂一定,它没有先定义一个中间变量t,看起来似乎是直接返回了一个临时变量。但实际上,如果不经过c++的优化,那么它并没有提高效率,因为它还是创建了3个对象。

    1 T function2(){
    2      return T(0);
    3 }
    4 T x=function2();

    这里的过程是:
    1.创建一个无名对象
    2.由无名对象拷贝构造一个无名的临时对象
    3.析构无名对象,返回临时对象
    4.由临时对象拷贝构造对象x
    5.T x=function2()语句结束时,析构临时对象。
    这里一共生成了3个对象,其中有2个对象都是马上被析构掉的,不能被后面的代码使用。既然是这样,那么就会有优化的余地,可以尝试着不要前面的两个临时变量。c++确实会做这样的优化,优化后的c++会避免匿名对象和临时对象这两个对象的生成,而直接生成x,这样就减少了两次对象生成-回收的消耗,提高了程序性能。

    其实function1()这段代码也是会经过优化的,但因为临时对象t是一个命名对象,所以一定会被创建。存储返回值的临时对象是多余的,会被优化掉而不生成。
    但是,程序员不应该依赖这种优化,因为c++不保证这种优化一定会做。

    新战场:https://blog.csdn.net/Stephen___Qin
  • 相关阅读:
    HRESULT:0x80070057 (E_INVALIDARG)的异常的解决方案
    c# 取两个时间的间隔
    [转]C#算法
    智能仓库管理系统方案(四)
    分页存储过程
    ASP.NET2.0_多语言本地化应用程序(转)
    C#绘图双缓冲技术总结(转)
    C#.net同步异步SOCKET通讯和多线程总结(转)
    WIN2003 sp2中Delphi 7中的Project菜单中Options菜单打不开
    C#关于日期月天数和一年有多少周及某年某周时间段的计算
  • 原文地址:https://www.cnblogs.com/Stephen-Qin/p/12234733.html
Copyright © 2020-2023  润新知