目录
- case1:用返回值方式调用函数
- case2:用函数的返回值初始化引用的方式调用函数
- case3:用返回引用的方式调用函数
- case4:用函数返回的引用作为新引用的初始化值的方式来调用函数
- 用引用实现多态
- 函数中返回引用和返回值的区别
- 主要讨论下面两个函数的区别
- 说明一下函数返回时
特别注意:
1.引用作为函数的返回值时,必须在定义函数时在函数名前将&
2.用引用作函数的返回值的最大的好处是在内存中不产生返回值的副本
//代码来源:RUNOOB#includeusing namespace std; float temp; float fn1(float r){temp=r*r*3.14; return temp; } float &fn2(float r){ //&说明返回的是temp的引用,换句话说就是返回temp本身temp=r*r*3.14; return temp; }int main(){float a=fn1(5.0); //case 1:返回值//float &b=fn1(5.0); //case 2:用函数的返回值作为引用的初始化值 [Error] invalid initialization of non-const reference of type 'float&' from an rvalue of type 'float'//(有些编译器可以成功编译该语句,但会给出一个warning) float c=fn2(5.0); //case 3:返回引用float &d=fn2(5.0); //case 4:用函数返回的引用作为新引用的初始化值cout<
case1:用返回值方式调用函数 如下图:
文章图片
返回全局变量temp的值时,C++会在内存中创建临时变量并将temp的值拷贝给该临时变量。当返回到主函数main后,赋值语句a=fn1(5.0)会把临时变量的值再拷贝给变量a
case2:用函数的返回值初始化引用的方式调用函数 如下图:
文章图片
这种情况下,函数fn1()是以值方式返回到,返回时,首先拷贝temp的值给临时变量。
返回到主函数后,用临时变量来初始化引用变量b,使得b成为该临时变量到的别名。
由于临时变量的作用域短暂(在C++标准中,临时变量或对象的生命周期在一个完整的语句表达式结束后便宣告结束,也就是在语句float &b=fn1(5.0); 之后),所以b面临无效的危险,很有可能以后的值是个无法确定的值。
如果真的希望用函数的返回值来初始化一个引用,应当先创建一个变量,将函数的返回值赋给这个变量,然后再用该变量来初始化引用:
int x=fn1(5.0); int &b=x;
case3:用返回引用的方式调用函数 如下图:
文章图片
这种情况下,函数fn2()的返回值不产生副本,而是直接将变量temp返回给主函数,即主函数的赋值语句中的左值是直接从变量temp中拷贝而来(也就是说c只是变量temp的一个拷贝而非别名) ,这样就避免了临时变量的产生。尤其当变量temp是一个用户自定义的类的对象时,这样还避免了调用类中的拷贝构造函数在内存中创建临时对象的过程,提高了程序的时间和空间的使用效率。
case4:用函数返回的引用作为新引用的初始化值的方式来调用函数 如下图:
文章图片
这种情况下,函数fn2()的返回值不产生副本,而是直接将变量temp返回给主函数。
1.在主函数中,一个引用声明d用该返回值初始化,也就是说此时d成为变量temp的别名。
2.由于temp是全局变量,所以在d的有效期内temp始终保持有效,故这种做法是安全的。
3.不能返回局部变量的引用。如上面的例子,如果temp是局部变量,那么它会在函数返回后被销毁,此时对temp的引用就会成为“无所指”的引用,程序会进入未知状态。
4.不能返回函数内部通过new分配的内存的引用。虽然不存在局部变量的被动销毁问题,但如果被返回的函数的引用只是作为一个临时变量出现,而没有将其赋值给一个实际的变量,那么就可能造成这个引用所指向的空间(有new分配)无法释放的情况(由于没有具体的变量名,故无法用delete手动释放该内存),从而造成内存泄漏。因此应当避免这种情况的发生
5当返回类成员的引用时,最好是const引用。这样可以避免在无意的情况下破坏该类的成员。
6.可以用函数返回的引用作为赋值表达式中的左值
#includeusing namespace std; int value[10]; int error=-1; int &func(int n){if(n>=0&&n<=9)return value[n]; //返回的引用所绑定的变量一定是全局变量,不能是函数中定义的局部变量 elsereturn error; } int main(){func(0)=10; func(4)=12; cout<
文章图片
用引用实现多态 在C++中,引用是除了指针外另一个可以产生多态效果的手段。
也就是说一个基类的引用可以用来绑定其派生类的实例
class Father; //基类(父类)class Son:public Father{.....}//Son是Father的派生类Son son; //son是类Son的一个实例Father &ptr=son; //用派生类的对象初始化基类对象的使用
特别注意:
ptr只能用来访问派生类对象中从基类继承下来的成员。如果基类(类Father)中定义的有虚函数,那么就可以通过在派生类(类Son)中重写这个虚函数来实现类的多态。
函数中返回引用和返回值的区别
主要讨论下面两个函数的区别
int& at(){return m_data_; }
int at(){return m_data_; }
上面两个函数,第一个返回值是int的引用int&,第二个返回值是int,二者的区别是什么呢?
我们先用一个语句 const int& a = mymay.at(); 来分别调用一次上面两个函数,然后看汇编语言的结果。
反汇编结果:
#int& at()#{#return m_data_; #}00BB6830pushebp00BB6831movebp,esp00BB6833subesp,0CCh00BB6839pushebx00BB683Apushesi00BB683Bpushedi00BB683Cpushecx00BB683Dleaedi,[ebp-0CCh]00BB6843movecx,33h00BB6848moveax,0CCCCCCCCh00BB684Drep stosdword ptr es:[edi]00BB684Fpopecx00BB6850movdword ptr [this],ecxm_data_++; 00BB6853moveax,dword ptr [this]00BB6856movecx,dword ptr [eax]00BB6858addecx,100BB685Bmovedx,dword ptr [this]00BB685Emovdword ptr [edx],ecxreturn m_data_; #取地址this中的值5879712(m_data_的地址)到寄存器eax中,此时寄存器eax存的是m_data_的地址00BB6860moveax,dword ptr [this]}00BB6863popedi00BB6864popesi00BB6865popebx00BB6866movesp,ebp00BB6868popebp00BB6869retconst int& a = mymay.at(); 00176AA2leaecx,[mymay]00176AA5callMyMat::at (0171546h)#此时寄存器eax中的值为m_data_的地址5879712,直接将地址5879712存入地址a中。00176AAAmovdword ptr [a],eaxcout << a << endl;
#int at()#{#return m_data_; #}012B6830pushebp012B6831movebp,esp012B6833subesp,0CCh012B6839pushebx012B683Apushesi012B683Bpushedi012B683Cpushecx012B683Dleaedi,[ebp-0CCh]012B6843movecx,33h012B6848moveax,0CCCCCCCCh012B684Drep stosdword ptr es:[edi]012B684Fpopecx012B6850movdword ptr [this],ecxreturn m_data_; #和上面一样,也是先取出m_data_的地址012B6853moveax,dword ptr [this]#和上面不一样,不是直接将m_data_的地址放入寄存器eax中,而是取地址5879712中的值(m_data_=3)放入寄存器eax中,此时寄存器eax存的是m_data_的值(3)012B6856moveax,dword ptr [eax]}012B6858popedi012B6859popesi012B685Apopebx012B685Bmovesp,ebp012B685Dpopebp012B685Eretconst int& a = mymay.at(); 008E6AA2leaecx,[mymay]008E6AA5callMyMat::at (08E154Bh)#此时eax的值为3,将3存入地址ebp-24h中,008E6AAAmovdword ptr [ebp-24h],eax#将eax的值变成ebp-24h008E6AADleaeax,[ebp-24h]#将地址ebp-24h写到地址为a中,此时a代表的地址是ebp-24h008E6AB0movdword ptr [a],eaxcout << 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,所以一定会有临时变量产生。
T function1(){T t(0); return t; } T x=function1();
这里的过程是:
1.创建命名对象t
2.拷贝构造一个无名的临时对象,并返回这个临时对象
3.由临时对象拷贝构造对象x
4.T x=function1(); 这句语句结束时,析构临时对象
这里一共生成了3个对象,一个命名对象t,一个临时对象作为返回值,一个命名对象x。
下面的函数稍微复杂一定,它没有先定义一个中间变量t,看起来似乎是直接返回了一个临时变量。但实际上,如果不经过c++的优化,那么它并没有提高效率,因为它还是创建了3个对象。
T function2(){return T(0); } T x=function2();
这里的过程是:
1.创建一个无名对象
2.由无名对象拷贝构造一个无名的临时对象
3.析构无名对象,返回临时对象
4.由临时对象拷贝构造对象x
5.T x=function2()语句结束时,析构临时对象。
这里一共生成了3个对象,其中有2个对象都是马上被析构掉的,不能被后面的代码使用。既然是这样,那么就会有优化的余地,可以尝试着不要前面的两个临时变量。c++确实会做这样的优化,优化后的c++会避免匿名对象和临时对象这两个对象的生成,而直接生成x,这样就减少了两次对象生成-回收的消耗,提高了程序性能。
其实function1()这段代码也是会经过优化的,但因为临时对象t是一个命名对象,所以一定会被创建。存储返回值的临时对象是多余的,会被优化掉而不生成。
但是,程序员不应该依赖这种优化,因为c++不保证这种优化一定会做。
【C/C++|C/C++ 引用作为函数的返回值方式】以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
推荐阅读
- c/c++|extern “ C “,实现C与C++项目的相互调用
- C|【数据结构】 之 表达式求值
- 牛客网刷题记录|牛客网刷题记录 || 第一番
- MILP|【从零开始】coin-or/CoinUtils Osi Clp Cgl Cbc源码构建debug(CLion/CMake)
- c|C/C++学习资源(百度云盘链接)
- C|【c ++ primer 笔记】第4章 表达式
- C|【c ++ primer 笔记】第3章 字符串、向量和数组
- 笔记|C++之初识多态(Visual Studio 2019)
- C/C++|C/C++学习笔记——C基础(C语言概述)