C++中的函数返回值与拷贝用法
C++函数返回值与拷贝
先来谈谈对C++中函数返回return的理解,自己本来在学Java,但是平时学校的项目是用的C++,所以在平时搬砖时经常会有一些问题,今天就来谈谈前段时间注意到的一个很小的知识点,话不多说,先上列子。
首先我们创建一个简单的Man类,实现它的无参构造函数、有参构造函数和析构函数:
class Man { public: Man() { cout << "构造" << endl; data = new int(0); } Man(const Man& m) { cout << "拷贝构造" << endl; this->data = m.data; } ~Man() { cout << "析构" << endl; delete data; } int* data; };
声明一个get函数获取一个Man的对象
Man get(Man& m) { cout << "----" << endl; return m; }
在main函数中执行下列代码
void main() { Man m, n; //cout << "before m=" << &m << "n=" << &n << endl; *m.data = 5; printf("m.data is %d\n", *m.data); n = get(m); printf("m.data is %d\n", *m.data); printf("n.data is %d\n", *n.data); system("pause"); }
你可以试着想一想三个printf的输出结果分别是多少
执行结果如下图所示:
在输出结果里我们可以清楚的看到,Man m, n; 创建了m,n两个对象,调用了构造函数,对m对象中的data赋值,然后我们调用get(Man& man) 函数,注意这里函数参数是引用类型,因此传入的对象是m对象本身,这里我们要区别get(Man man) 两种函数参数类型的区别,我稍后再提。get(Man& man) 函数调用完毕后,返回对象m。
按照我们过去的分析会认为对象n等于get函数返回的m对象 (n=m) (注意这里等号=被重载过),m对象中的int* data 成员值直接赋值给了n对象中的data成员,输出时照理说m和n的data值都应该等于5的,但是:
为什么这里输出结果却表明这个data指针指向的空间被销毁了?
为什么get函数执行里会多出了拷贝构造和析构这两个过程呢?
如果我们返回值为Man&会有什么区别变化呢?
这里我们做一个对比,填加一个getR函数,返回值为Man& 引用类型:
Man& getR(Man& m) { cout << "----" << endl; return m; }
接下来我们调用getR这个函数看一看输出结果:
void main() { Man m, n; *m.data = 5; printf("m.data is %d\n", *m.data); n = getR(m); printf("m.data is %d\n", *m.data); printf("n.data is %d\n", *n.data); system("pause"); }
执行结果如下图所示:
执行结果如下图所示:
可以看到,当我们返回的是m对象的的引用时,getR 函数执行时没有调用拷贝构造和析构函数
这里我解释一下返回值不是引用的情况时整个函数执行的过程
(个人拙劣的理解)
我们再回到get这个函数:
Man get(Man& m) { cout << "----" << endl; return m; }
首先函数参数传入m这个对象的引用我们毋庸置疑,关键就在return这里。
我们捋一捋函数从开始到结束这个过程,随着Main函数调用get函数,get函数入栈,同时get方法对应的栈帧(储存函数局部变量、返回地址等信息)也入栈,这里的局部变量也就是m对象的引用。
当我们return这个m对象时,会在内存中创建一个临时的Man temp对象,同时这个temp对象调用其拷贝构造函数,也就是Man temp(m) 。
完成temp对象的创建后,get函数出栈,对应的栈区内容被销毁,这时系统会调用m对象的析构函数,注意这里有一个陷阱!!!!
由于m对象是在main方法下的栈区创建的,因此get方法出栈后,系统调用m析构函数并没有真正把m对象在栈区销毁(因为它根本就不是在get方法的栈区上),调用析构函数仅仅是将data指针所指向的内存空间被销毁了(delete data;),这也解释了为什么m.data的值为-572662307。
当main方法执行完毕后,m对象才会调用析构函数真正被销毁,当然,这也会带来另一个问题,data指向的内存区被执行了两次delete,运行结束后你也就会发现还会有一个**“析构”**和一个内存问题报错。
回到我们返回的值上:
n = get(m);
这里实际上可以理解成
Man temp(m); n=temp;
当然由于get函数的退出调用析构函数时,data指针指向的内存区域数据已经被销毁,自然n和m得到的值是一个错误值了。
总结
对于函数返回值类型为非引用类型(当然引用类型也可以理解为Man& temp=m),都是会在内存中创建一个临时变量,将返回值拷贝到临时变量中,而返回值是作为函数调用栈区中的局部变量,随着函数的返回,栈区的销毁,而被销毁。
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。
最新评论