C++返回值是类名和返回值是引用的区别及说明

这篇文章主要介绍了C++返回值是类名和返回值是引用的区别及说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教

返回值是类名和返回值是引用的区别

返回非引用类型

函数的返回值用于初始化在调用函数时创建的临时对象(temporary object),如果返回类型不是引用,在调用函数的地方会将函数返回值复制给临时对象。

在求解表达式的时候,如果需要一个地方存储其运算结果,编译器会创建一个没命名的对象,这就是临时对象。C++程序员通常用temporary这个术语来代替temporary object。

用函数返回值初始化临时对象与用实参初始化形参的方法是一样的。

当函数返回非引用类型时,其返回值既可以是局部对象,也可以是求解表达式的结果。

返回引用类型

当函数返回引用类型时,没有复制返回值,相反,返回的是对象本身。

千万不要返回局部对象的引用!千万不要返回指向局部对象的指针!

当函数执行完毕时,将释放分配给局部对象的存储空间。此时对局部对象的引用就会指向不确定的内存!返回指向局部对象的指针也是一样的,当函数结束时,局部对象被释放,返回的指针就变成了不再存在的对象的悬垂指针。

返回引用时,要求在函数的参数中,包含有以引用方式或指针方式存在的,需要被返回的参数。

如果返回对象,最后多执行一次拷贝构造函数,如果返回引用,直接返回现存对象

#include  using namespace std; class Timer { public:     Timer();     Timer(int, int, int);     friend Timer  &operator+(Timer&, Timer&);     friend Timer operator-(Timer&, Timer&);     friend ostream& operator<<(ostream &out, Timer &t);     friend istream& operator>>(istream &in, Timer &t); private:     int hour, minute, second; }; Timer::Timer() {     hour = 0;     minute = 0;     second = 0; } Timer::Timer(int hour, int minute, int second) {     this->hour = hour;     this->minute = minute;     this->second = second; } Timer & operator+(Timer& a, Timer &b) {     a.second = a.second + b.second;     a.minute = a.minute + b.minute;     a.hour = a.hour + b.hour;     if (a.second >= 60)     {         a.second = a.second - 60;         a.minute++;     }     if (a.minute >= 60)     {         a.minute = a.minute - 60;         a.hour++;     }     return a; } Timer operator-(Timer &a, Timer &b) {     Timer c;     c.hour = a.hour - b.hour;     c.minute = a.minute - b.minute;     c.second = a.second - b.second;     if (c.second <0)     {         c.second += 60;         c.minute--;     }     if (c.minute <0)     {         c.minute += 60;         c.hour--;     }     return c; } ostream& operator<<(ostream &out, Timer &t) {     out << t.hour << ":" << t.minute << ":" << t.second << endl;     return out; } istream& operator>>(istream&in, Timer &t) {     cout << "Input hours and minutes." << endl;     in >> t.hour >> t.minute >> t.second;     return in; } int main() {     Timer t1, t2, t3, t4;     cin >> t1 >> t2;     cout << "t1=" << t1;     cout << "t2=" << t2;     t3 = t1 + t2;     cout << "t3=t1+t2=" << t3;     t4 = t1 - t2;     cout << "t4=t1-t2=" << t4;     return 0; }

刚开始我将函数声明为:

friend Timer&operator+(Timer&,Timer&); friend Timer&operator-(Timer&,Timer&);

对于<<,>>的重载声明的和长代码一样。但是这样做之后我发现出现异常。

在Time &operator+(Timer&t1,Timer&t2)里我声明了一个Timer的局部变量,而当函数调用结束后,该指针变成了悬挂指针。

所以,一定谨记不要在返回值为引用的函数中返回局部变量。

C++函数返回值和返回引用问题

C++函数的返回过程基本可以分为两个阶段,返回阶段和绑定阶段,根据两个阶段中需要返回的值的类型不同(返回值和引用),和要绑定的值的类型(绑定值和引用)会产生不同的情况。

最基本的规则是先返回,再绑定,返回和绑定的时候,都有可能发生移动或者拷贝构造函数的调用来创建临时对象,并且只会发生一次。更具体的,当返回值的时候,函数返回之前,会调用一次拷贝构造函数或移动构造函数,函数在绑定到值的时候,会发生一次拷贝构造函数或移动构造函数,如果返回时已经调用了构造函数,则绑定时不会再调用,而是直接绑定。返回引用或者绑定到引用,在各自阶段,不会产生构造函数的调用。

下面举例阐述各种可能的情况(例子中很多情况我们是不该这样写的,本文只讨论如果这样做了会怎样)。

关于右值引用和移动构造函数的解释,后面会写篇东西做个总结。这里仅需要一条规则,那就是在需要调用移动构造函数的情况,如果类没有定义移动构造函数,则调用它的拷贝构造函数,后面对未定义移动构造函数的情况,不再赘述。

为叙述方便,定义绑定值和返回值:

绑定值就是函数赋给的变量,返回值就是函数的返回值,最终的调用形式类似:

返回值 fun() {     .....     return 返回值; } 绑定值 = func();

定义一个类:

class myClass { public:     //构造函数     myClass()     {         cout << "construct" << endl;     }     //拷贝构造函数     myClass(const myClass& rhs)     {         cout << "copy construct" << endl;     }     //移动构造函数     myClass(myClass&& rhs)     {         cout << "move consturct" << endl;     }     //析构函数     ~myClass()     {         cout << "desctruct" << endl;     } };

1.绑定值类型为值类型

返回值类型为值类型,返回的是局部变量:

在函数返回阶段,调用类的移动构造函数创建返回值,并绑定到绑定值上。移动构造函数的调用发生在函数返回阶段。

例子:

myClass fun() {     myClass mc;     return mc; }   int main() {     myClass result = fun(); }

输出:

construct
move consturct
desctruct
desctruct

2.绑定值类型为值类型

返回值类型为值类型,返回的是局部变量的引用:

首先,这不是一个好的做法,企图返回一个局部变量的引用结果通常并不会令人满意。

函数调用结束后,函数帧被回收(实际上只是设置了栈顶的地址),函数栈中的临时变量不再有意义。

但是这么做通常也不会造成什么副作用,因为在函数返回阶段,会调用拷贝构造函数(注意,即使定义了移动构造函数也不会调用),将生成的新的临时对象,绑定到绑定值上。

例子:

myClass fun() {     myClass mc;     myClass& mcRef = mc;         //如果是想返回这个局部变量的引用,并且在函数外部对其进行修改,那必然要失望了。     return mcRef; }   int main() {     myClass result = fun(); }

输出:

construct
copy construct
desctruct
desctruct

3.绑定值类型为值类型

返回值类型为值类型,返回的是全局变量的引用:

和2中的情形类似,但是由于全局变量的生命周期超过当前函数,所以即使函数返回,变量仍然存活。在函数返回阶段,仍然会调用拷贝构造函数,将产生得临时对象绑定到绑定值上,却依然不能返回全局对象。

例子:

通常,返回作为参数而传递进来的引用的情况更加常见,此时的参数对于函数来说,和全局变量类似,即函数返回,变量的生命周期也不会结束。

myClass fun(myClass& param) {         //最终只能得到param的拷贝     return param; }   int main() {     myClass mc;     myClass result = fun(mc); }

输出:

construct
copy construct
desctruct
desctruct

4.绑定值类型为值类型

返回值类型为引用类型,返回的是局部变量或局部变量的引用:

和情况2情形类似的是,希望以引用的方式返回局部变量或者局部变量的引用,通常也不能取得令人满意的结果;但与情况2的情形不同的是,情况2基本不会产生什么副作用,情况2中在函数返回阶段对对象进行拷贝,此时对象完整;但是在本情况中,就会产生严重的副作用。

由于返回值类型为引用类型,在函数返回阶段,并不会调用拷贝构造函数,而在绑定阶段,绑定值是值类型,才会产生拷贝构造函数的调用,而此时函数已经返回,拷贝函数拷贝的是函数中的临时变量,此时已经析构了,再进行拷贝,只会得到一个被析构对象的拷贝,通常,这会产生严重的错误。

简单的说,这种情况下,会先析构临时对象,在拷贝这个临时对象。

例子:

myClass& fun() {     myClass mc;     //或者myClass mcRef = mc; return mcRef     return mc; }   int main() {     myClass result = fun(); }

输出:

construct
desctruct
copy construct
desctruct

5.绑定值类型为值类型

返回值类型为引用类型,返回的是全局变量的引用:

基本的情况和2是类似的,即并不能真的的到全局变量的引用,但是也不会产生什么副作用(除非调用者就是希望或者这个全局变量,并进行修改)。由于全局变量在函数结束之后并不会被析构,所以对它调用拷贝构造函数是安全的。和情况4类似,拷贝函数的调用也不是发生在函数返回阶段,而是发生在绑定阶段。

例子:

同样使用参数为引用类型的函数举例:

myClass& fun(myClass& param) {     return param; }   int main() {     myClass mc;     myClass result = fun(mc); }

输出:

construct
copy construct
desctruct
desctruct

上述5中情况是将返回值绑定到值类型的情形,所有情形中,均会出现拷贝或移动构造函数的调用,最终绑定之后,都不可能得到原来的对象,企图通过这种方式来修改原对象,必然会失败。

如果绑定值类型为引用类型,返回值类型为值类型,返回的是局部变量,局部变量的引用或者全局变量会怎么样呢?

这种情况编译器会报错,由于函数返回的是值类型,必然会产生移动构造函数或者拷贝构造函数,产生一个临时对象,而临时对象是一个右值,我们常说的引用,其实是左值引用,而一个右值对象是不能够绑定到一个左值引用的,所以编译器会报错。

我们可以将函数的返回值版绑定到右值引用变量(使用myClass&&),我们下面讨论返回右值引用的情况。但是,有时候返回一个右值引用并且使用它,可能并不能产生想要的结果(如本文最后例子)。

6.绑定值类型为右值引用

返回值类型为值类型,返回的是局部变量或局部变量的引用或全局变量的引用:

函数返回阶段,由于是返回值类型,所以会调用移动构造函数或者拷贝构造函数,创建一个不具名的临时变量(其实它就是一个右值),可以把它绑定到一个右值引用上。

但是对它的修改是没有什么意义的,本来函数创建的这个不具名临时变量,会在函数返回之后被析构,由于我们增加了一个指向它的引用,所以这个临时变量的生命周期被延长了,直到指向它的引用离开作用域,它才会析构。

例子:

myClass fun() {     myClass mc;     return mc; } int main() {     myClass&& result = func(); }

输出:

construct
move consturct
desctruct
desctruct 

myClass fun() {     myClass mc;     myClass& mcRef = mc;     return mcRef; } int main() {     myClass&& result = fun(); }

输出:

construct
copy construct
desctruct
desctruct

7.绑定值类型为为引用类型

返回值类型为引用类型,返回局部变量或局部变量的引用:

由于函数返回阶段和绑定阶段都是引用类型,所以不会产生任何拷贝或移动构造函数的调用,最终绑定值类就是函数的局部变量,但是由于函数返回后,局部变量已经被析构,在保存这个局部变量的引用没有意义,往往会引起一些诡异的错误。

例子:

myClass& fun() {     myClass mc;     //或者myClass& mcRef = mc; return mcRef;     return mc; }   int main() {         //在函数返回之后,我们甚至还能使用这个result,但是仅仅是编译器将原来result的位置继续解释成result变量,         //很有可能很快这个位置会被新的栈帧覆盖,继续使用result,可能会出现“诡异”的问题     myClass& result = fun(); }

输出:

construct
desctruct

8.绑定值类型为引用类型

返回值类型为引用类型,返回全局变量的引用:

通常,当我们这是我们的真实意图,函数在返回阶段和绑定阶段均不存在构造函数的调用,最终绑定值就是需要返回的全局变量,对最终绑定值的任何修改,均会反映到全局变量上。

例子:

依然使用参数为引用类型的函数举例。

myClass& fun(myClass& mc) {     return mc; } int main() {     myClass mc;     myClass& result = fun(mc); }

输出:

construct
desctruct

9.一个思考

右值是不能绑定到左值引用的,但是当把右值付给左值会怎样呢?

如下

int main() {     myClass mc;     myClass result = move(mc); }

答案是会调用移动构造函数创建一个新的对象,并且绑定到绑定值。

输出:

construct
move consturct
desctruct
desctruct

另一个非常类似的例子:

int main() {     myClass mc;     myClass&& mcRightRef = move(mc);     myClass result = mcRightRef; }

和第一个非常类似,但是result的生成是调用拷贝构造函数而非移动构造函数完成的。

输出:

construct
copy construct
desctruct
desctruct

这就涉及到了移动构造函数调用的时机,更多关于右值,右值引用和移动构造函数详细的研究,待续。

以上为个人经验,希望能给大家一个参考,也希望大家多多支持0133技术站。

以上就是C++返回值是类名和返回值是引用的区别及说明的详细内容,更多请关注0133技术站其它相关文章!

赞(0) 打赏
未经允许不得转载:0133技术站首页 » C语言