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

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

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)。

 

所以结论就是:

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++不保证这种优化一定会做。

C++中,函数返回引用返回值是两种不同的机制,它们在性能、语义使用场景上具有显著区别。 ### 返回值 返回值函数将计算结果返回给调用者的一种常见方式。当函数返回一个值时,会创建一个临时对象,并将函数内部的返回值拷贝到这个临时对象中。调用者随后使用这个临时对象进行后续操作。由于涉及拷贝构造,返回值可能会带来额外的性能开销,尤其是在返回大型对象时[^3]。 例如,一个简单的返回值函数如下: ```cpp int add(int a, int b) { return a + b; } ``` 在调用此函数时,返回的值会被拷贝到调用者的变量中: ```cpp int result = add(3, 4); // 3 + 4 的值被拷贝到 result 中 ``` ### 返回引用 返回引用允许函数返回一个现有变量的引用,而不是其值的拷贝。这种方式避免了不必要的拷贝操作,从而提高了性能。然而,返回引用时必须确保所引用的对象在函数返回后仍然有效,否则将导致悬空引用(dangling reference),进而引发未定义行为。因此,不能返回函数内部局部变量的引用,因为这些变量在函数返回后会被销毁[^4]。 例如,返回引用函数可能如下: ```cpp int& max(int& a, int& b) { return (a > b) ? a : b; } ``` 在调用此函数时,返回的是`a`或`b`的引用,而不是它们的值: ```cpp int x = 10, y = 20; int& ref = max(x, y); // ref 引用的是 x 或 y,具体取决于它们的值 ``` ### 区别总结 1. **性能**:返回引用避免了拷贝构造,适用于返回大型对象或需要直接修改调用者提供的对象。而返回值涉及拷贝,可能会带来额外的性能开销[^2]。 2. **生命周期**:返回引用时必须确保引用的对象在函数返回后仍然有效。而返回值则是函数内部的临时对象,其生命周期通常在调用者使用后结束[^4]。 3. **语义**:返回引用允许调用者直接修改函数内部的数据,而返回值则提供了一个独立的拷贝,调用者对返回值的修改不会影响函数内部的状态[^5]。 ### 示例对比 以下是一个对比返回引用返回值的示例: ```cpp #include <iostream> #include <vector> // 返回值函数 std::vector<int> createVectorByValue() { std::vector<int> vec = {1, 2, 3, 4, 5}; return vec; // 返回 vec 的拷贝 } // 返回引用函数 std::vector<int>& createVectorByReference(std::vector<int>& vec) { return vec; // 返回传入 vec 的引用 } int main() { // 使用返回值函数 std::vector<int> valueVec = createVectorByValue(); for (int val : valueVec) { std::cout << val << " "; } std::cout << std::endl; // 使用返回引用函数 std::vector<int> vec = {10, 20, 30, 40, 50}; std::vector<int>& refVec = createVectorByReference(vec); for (int val : refVec) { std::cout << val << " "; } std::cout << std::endl; return 0; } ``` ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值