c++类的引用作为函数返回值的原理 (c++左值引用可以操作函数返回值吗)

c++返回临时变量的指针,c++指针变量讲解

原创笔记,转载请注明出处!

点击【关注】,关注也是一种美德~

笔记二十五:C++函数返回值为局部变量&指针&引用

预备知识:

1、C++ 有三种参数传递方式:值传递,指针传递,引用传递

2、C/C++中,函数内部的一切变量(函数内部局部变量,形参)都是在其被调用时才被分配内存单元。子函数运行结束时,所有局部变量的内存单元会被系统释放。形参和函数内部的局部变量的生命期和作用域都是在函数内部(static变量的生命期除外)。

3、涉及到局部变量存储问题先看两个概念

堆栈:堆栈其实是两种数据结构。

堆:由程序员分配和释放。如在C/C++中程序员使用malloc/new分配堆空间,使用free/delete释放所申请的堆空间。特点:释放内存块顺序随意。

栈:栈是由系统自动分配和回收的内存。如一个子函数被调用时,系统会将函数内的局部变量的内存单元分配到栈上,当函数执行完毕时系统自动释放所分配的栈地址单元。特点:释放栈内存顺序为后进先出。

4、 变量的意义在于:它给一块内存存储区提供名字,方便程序对这块内存进行读写。变量包含两个值:左值和右值。左值是内存存储区的名字,右值是存放在存储区中的值

c++返回临时变量的指针,c++指针变量讲解

第一,函数内部返回局部变量过程

C/C++中的return返回局部变量值时,机制是:return将返回值拷贝到寄存器中(无地址),然后系统再将寄存器中的值赋给变量。返回的数值是a的拷贝值,子函数运行完毕后,局部变量被释放。

int func() //返回变量a的数值

{

int a;

a=10;

return a;

}

int main()

{

int b=func();

cout<<"b="<<b<<endl;

return 0;

}

分析子函数调用过程:

【1】当程序执行到int b=func();时,调用子函数func,程序转到func子函数入口地址处;

【2】程序进入func子函数(此子函数开始运行),系统为局部变量a分配内存,执行到return a 时,系统将a的值拷贝一份存入寄存器中,然后子函数执行完毕,返回主函数,局部变量a被释放,也就是说之前存放局部变量a的那块内存中的数据被释放了。

若子函数中有形参和局部变量,则在函数开始运行时,系统自动为局部变量分配栈空间,待函数运行完毕时系统自动释放在栈中为局部变量分配的内存单元中的数据。

【3】func子函数执行完毕,函数返回到调用子函数的地方继续执行,将保存在寄存器中的变量a的拷贝值赋给变量b,因为在执行主函数时变量b已经被赋予了内存空间,也就是内存地址,所以取b的地址是可以的。

c++返回临时变量的指针,c++指针变量讲解

第二,指针作为函数返回类型

指针也是变量,只是其表示的值是内存中的地址而已。因此上面的原理是一样的。

当程序执行return语句时,会将变量a的地址值(因为返回类型是指针)拷贝存入寄存器中,然后系统再将寄存器中的地址值赋给接收该地址的指针变量c,编译运行是没有错误的。

问题一:局部指针指向栈内存

但是存在这样一个问题:局部变量a在函数执行完毕func2执行完毕后,就被释放回收了,通过指针变量c再去访问之前的地址找到的就不是之前的数据了。

栈的分配和释放可以这样理解:栈内存块在计算机中不可能会移动,它的地址已经被固定。系统分不分配它,它就在那里。当为局部变量分配栈内存时,系统就将局部变量存入到栈的某个内存块中;当子函数运行结束局部变量应当被释放时,系统再将这些存入局部变量的栈内存中的数据清除掉,恢复原来没有被初始化的状态。

子函数中局部变量a的地址被return到父函数中去,只是在父函数中使用这个地址去访问内容时,此地址中的内容已经被清除了,或者已经被用作其他工作了,地址还是那个地址,内容已经不是那个内容了。

因此,这是很危险的操作:在父函数中用此地址访问其内容时,有可能刚刚被释放掉的这块内存又被系统分配另外的局部变量的,而此时,你所访问的结果只是会导致程序结果不正确而已,编译运行都是没问题的。

问题二:指针指向常量存储区

char * test()

{

char * p ="AutoCodes";

return p;

}

int main()

{

cout<<"AutoCodes:"<<test<<endl;//函数名代表地址

cout<<"AutoCodes:"<<test()<<endl;//执行函数

system("pause");

return 0;

}

运行结果:

AutoCodes:010A1253

AutoCodes:AutoCodes

请按任意键继续. . .

首先注意函数加括号和不加括号的区别,不加括号的函数名代表函数首地址,加括号就执行到这就是运行函数了。

这里输出正确!为什么呢?

因为p虽然为局部指针,子函数结束,p被释放,但是p指向的并不是栈内存而是常量存储区,即使子函数结束,这段内存存储的依旧是"AutoCodes",所以拷贝返回的地址是有效的,结果自然就是正确的。

小结:

(1)return

不管是返回指针还是返回值,return将return之后的值存到寄存器中,回到父函数再将返回的值赋给变量。

(2)局部地址

在函数内返回一个指针会出错的原因:子函数运行完毕时,存局部变量的所有栈地址的内容已经被释放。若在父函数中再访问这些地址中的内容时,因为这些地址的内容已经被释放,所访问到的值可能是乱的、不定的。

(3)分配/释放内存

分配内存,就是将某变量存入到某块内存中的一个地址中;释放内存,就是将此内存中的内容清除掉,恢复内存未被初始化的状态。

c++返回临时变量的指针,c++指针变量讲解

第三,引用作为函数返回类型

危险警告:

对函数返回值是引用这个知识点,目前掌握的很有欠缺,存在很多不懂的地方,网罗网上的一些资料也没有说的太清楚,整合后结合自己的理解,有了这些想法,不一定准确,或者说就是在胡说八道,本着严谨求学的态度,我不能嚷嚷自己写的东西是对的!这样误人子弟,毕竟很多时候我看到网上的资料有的就会当真了,等后来发现矛盾的地方再回来看看,哦,原来之前看的文章有错误,这种情况会带偏读者的思路。

所以还请求大佬指点,目前能想到的一些疑惑都写在了文末,还请大佬答疑,非常感谢!

-------------------------------------------------------------------------------------------------------------

函数返回类型是引用,这是C++的难点。分为两部分,一是返回基础数据类型的引用,二是返回复杂数据类型的引用,返回复杂数据类型的引用后面操作符重载再说,本文主要阐述返回基础数据类型的引用。

问题一:"值"和"引用"的区别

返回"值"和返回"引用"是不同的。

函数返回时会产生一个临时变量作为函数返回值的副本,而返回引用时不会产生值的副本。

问题二:返回引用的本质

局部变量返回指针或引用都是地址,而返回数值是值。当函数返回一个引用时,则返回一个指向返回值的隐式指针。

问题三:关于函数返回值的规则

函数的返回值可以是数值和全局变量的指针或引用。

函数的返回值不能是局部对象或者是局部变量的指针或引用!

问题四:当函数返回值为引用时的规则

若返回栈变量,不能成为其他引用的初始值,不能作为左值使用;

若返回静态变量或全局变量:可以成为其他引用的初始值,既可以作为右值使用,也可以作为左值使用;

问题五:C++之中函数的返回分为以下几种情况

1、返回非引用类型:函数的返回值用于初始化在跳出函数时候创建的临时对象。用函数返回值来初始化临时对象与用实参初始化形参的方法是一样的。如果返回类型不是引用的话,在函数返回的地方,会将返回值复制给临时对象。且其返回值既可以是局部对象,也可以是表达式的结果。

2、返回引用

(1)当函数返回引用类型的时候,没有复制返回值,而是返回对象的引用(即对象本身)。

我的理解:返回引用类型时,返回的事对象的引用,本质是返回变量的地址,在主函数栈帧时,会自动解引用,由一个地址变成一个完整的变量,后面通过程序解释。(个人理解,请大神指点!)

(2)返回函数内部声明的局部变量的引用,在调用函数的时候用引用接收,执行无错误,但结果不一定正确;

总结

1、引用其实本质就是地址

2、当函数返回值类型为引用时,一般就用引用类型去接收,如果用非引用类型接受,就等于将函数返回的引用的数据值,赋值给了该接收对象,和函数返回非引用类型是一样的效果。

3、当函数返回值类型为引用时,如果不考虑接收问题,则有一个特性,则是可以直接操作该函数返回的引用,如放在=左面 +=等。

通过一个程序来说明:

#include <iostream>

using namespace std;

int &func() //返回变量a的引用

{

int a;

a=10;

cout<<"子函数中变量a的地址为: "<<&a<<endl<<endl;

return a;

}

int main()

{

int b=func();//进入主函数自动解引用,相当于 b=a;

int &c=func();//相当于 &c=a;

cout<<"变量b的值为: "<<b<<endl<<endl;

cout<<"变量c的值为: "<<c<<endl<<endl;

cout<<"变量b的地址为: "<<&b<<endl<<endl;

cout<<"变量c的地址为: "<<&c<<" 与子函数中变量a的地址相同"<<endl<<endl;

system("pause");

return 0;

}

运行结果:

子函数中变量a的地址为: 0019F790

子函数中变量a的地址为: 0019F790

变量b的值为: 10

变量c的值为: 258665792

变量b的地址为: 0019F87C

变量c的地址为: 0019F790 与子函数中变量a的地址相同

请按任意键继续. . .

个人理解(不一定准确,等大佬指点!):

1、子函数func返回引用类型,实质返回变量a的地址,当回到main函数时,系统自动解引用,也就是将变量a的地址跟其地址中的内容*绑捆**起来,那么变量a就像 int a = 10 一样,成为了一个"完整"的变量。

执行int b=func(); 就相当于b=a,把a的值赋给了变量b,而后子函数执行完毕,其中的局部变量a释放,这些都不影响在main函数中的变量b,并且变量b已经具有a的值了,所以输出b的值,是10,没有问题。

2、执行int &c=func();相当于&c=a,也就是定义一个变量a的引用,一个别名,a和c是同一个事物,共存亡,当子函数func运行完毕,其局部变量被释放,那么变量a就没了,也就是变量c被释放了,所以输出变量c的数值就是乱码,并且变量c的地址跟变量a的地址是同一个地址,引用嘛。

疑难杂症

1、如图,单步调试,函数执行完子函数,返回主函数时,尚未给变量b赋值以前,子函数中的变量a已经释放了,那么func的返回值此时是存储在哪呢?不是说返回引用是不产生副本吗?

c++返回临时变量的指针,c++指针变量讲解

2、在网上我看到有关篇文章提到这样一句话"在主函数栈帧时,会自动解引用",我也就根据这句话有了先返回地址,然后解引用,变成一个"完整"的变量a的思路,对通过int型变量和int&变量去接收返回的引用进行了解释,这种解释对吗?如果不对,那么返回的引用类型是怎么分别赋值给int和int&呢?

3、通过单步调试,我看到 int b=func(); 这个语句执行顺序是这样的:【定义变量b但没有进行初始化】-->【运行函数func并获取返回值】-->【返回值赋值给变量b】,那么func函数的调用结束是return之后呢还是"}"之后呢,还是b赋值完成这个赋值语句结束之后呢?

原创笔记,转载请注明出处!

更多精彩请关注微信公众号:依法编程