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

 更新时间:2022年11月25日 09:25:16   作者:白给程序猿  
这篇文章主要介绍了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),都是会在内存中创建一个临时变量,将返回值拷贝到临时变量中,而返回值是作为函数调用栈区中的局部变量,随着函数的返回,栈区的销毁,而被销毁。

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

相关文章

  • 如何在C++类的外部调用类的私有方法

    如何在C++类的外部调用类的私有方法

    这篇文章主要介绍了如何在C++类的外部调用类的私有方法,文章围绕主题展开详细的内容介绍,具有一定的参考价值,需要的朋友可以参考一下
    2022-09-09
  • C语言从零探索函数的知识

    C语言从零探索函数的知识

    函数是一组一起执行一个任务的语句。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数,让我们一起来了解它
    2022-04-04
  • VC++的if语句应用范围分析

    VC++的if语句应用范围分析

    这篇文章主要介绍了VC++的if语句应用范围分析,对VC++初学者有很好的参考学习价值,需要的朋友可以参考下
    2014-08-08
  • c语言中assert断言用法实例详解

    c语言中assert断言用法实例详解

    断言是C语言中一种用于检查程序中假设语句正确性的方法,通过使用断言,开发人员可以在程序中插入一些条件,以确保程序的执行满足特定的预期,这篇文章主要给大家介绍了关于c语言中assert断言用法的相关资料,需要的朋友可以参考下
    2024-02-02
  • C语言开发实现井字棋及电脑落子优化示例详解

    C语言开发实现井字棋及电脑落子优化示例详解

    以前上课经常和同桌玩起井字棋,那么我们就当我们回忆童年,现在也用C语言来实现井字棋,本次代码相对于初阶的井字棋,在电脑下棋代码部分做了优化,使得电脑更加具有威胁
    2021-11-11
  • C++实现LeetCode(62.不同的路径)

    C++实现LeetCode(62.不同的路径)

    这篇文章主要介绍了C++实现LeetCode(62.不同的路径),本篇文章通过简要的案例,讲解了该项技术的了解与使用,以下就是详细内容,需要的朋友可以参考下
    2021-07-07
  • C++控制台实现简单注册登录

    C++控制台实现简单注册登录

    这篇文章主要为大家详细介绍了C++控制台实现简单注册登录,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-12-12
  • C++实现雷霆战机可视化小游戏

    C++实现雷霆战机可视化小游戏

    这篇文章主要为大家详细介绍了C++实现雷霆战机可视化小游戏,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2020-11-11
  • C++如何实现二叉树链表

    C++如何实现二叉树链表

    这篇文章主要介绍了C++如何实现二叉树链表,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2022-07-07
  • C语言入门篇--关键字static详解

    C语言入门篇--关键字static详解

    本篇文章是C语言系列基础篇,C语言中,static是用来修饰变量和函数:1.修饰局部变量–>静态局部变量2.修饰全局变量–>静态全局变量3.修饰函数–>静态函数
    2021-08-08

最新评论