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的输出结果分别是多少

执行结果如下图所示:

C++中的函数返回值与拷贝用法

在输出结果里我们可以清楚的看到,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");
        }

执行结果如下图所示: 

执行结果如下图所示:

C++中的函数返回值与拷贝用法

可以看到,当我们返回的是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) 。

C++中的函数返回值与拷贝用法

完成temp对象的创建后,get函数出栈,对应的栈区内容被销毁,这时系统会调用m对象的析构函数,注意这里有一个陷阱!!!!

由于m对象是在main方法下的栈区创建的,因此get方法出栈后,系统调用m析构函数并没有真正把m对象在栈区销毁(因为它根本就不是在get方法的栈区上),调用析构函数仅仅是将data指针所指向的内存空间被销毁了(delete data;),这也解释了为什么m.data的值为-572662307。

main方法执行完毕后,m对象才会调用析构函数真正被销毁,当然,这也会带来另一个问题,data指向的内存区被执行了两次delete,运行结束后你也就会发现还会有一个**“析构”**和一个内存问题报错。

C++中的函数返回值与拷贝用法

回到我们返回的值上:

n = get(m); 

这里实际上可以理解成

Man temp(m);
n=temp;

当然由于get函数的退出调用析构函数时,data指针指向的内存区域数据已经被销毁,自然n和m得到的值是一个错误值了。

总结

对于函数返回值类型为非引用类型(当然引用类型也可以理解为Man& temp=m),都是会在内存中创建一个临时变量,将返回值拷贝到临时变量中,而返回值是作为函数调用栈区中的局部变量,随着函数的返回,栈区的销毁,而被销毁。

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

原文地址:https://blog.csdn.net/qq_39913402/article/details/105939391