C++ 拷贝构造函数

拷贝构造函数是一种特殊的构造函数,具有一般构造函数的所有特性,其形参是本类的对象的引用。其作用是使用一个已经存在的对象(由拷贝构造函数的参数指定),去初始化同类的一个新对象。
如果程序员没有定义类的拷贝构造函数,系统会在必要时自动生成一个隐含的拷贝构造函数。这个隐含的拷贝构造函数的功能是:把初始值对象的每个数据成员的值都拷贝到新建立的对象中。这样就完成了同类对象的拷贝,这样得到的对象和原对象具有完全相同的数据成员,即完全相同的属性。
声明和实现拷贝构造函数的一般方法:

class 类名
{
public:
	类名(形参表);//构造函数
	类名(类名&对象名);//拷贝构造函数
	...
};
类名::类名(类名&对象名)//拷贝构造函数的实现
{
	函数体
}

【例】
通过水平和垂直两个方向的坐标值X和Y类来确定屏幕上的一个点。点Point类的定义如下:

class Point
{
publicPoint(int xx=0,int yy=0)
	{
		x=xx;
		y=yy;
	}
	Point(Point&p);
	int getX()
	{
		return x;
	}
	int getY()
	{
		return y;
	}
private:
	int x,y;
};
Point::Point(Point&p)
{
	x=p.x;
	y=p.y;
	cout<<"调用拷贝构造函数"<<endl;
}

类中声明了内联构造函数和拷贝构造函数。
拷贝构造函数的实现如下:

Point::Point(Point& p)
{
	x = p.x;
	y = p.y;
	cout << "调用拷贝构造函数" << endl;
}

普通构造函数是在对象被创建的时候调用,而构造函数在以下三种情况下会被调用:

1.当用类的一个对象去初始化该类的另一个对象时

例如:

#include<iostream>
using namespace std;
class Point
{
public:
		Point(int xx = 0, int yy = 0):x(xx),y(yy)
	{

	}
	Point(Point& p);
	int getX()
	{
		return x;
	}
	int getY()
	{
		return y;
	}
private:
	int x, y;
};
Point::Point(Point& p):x(p.x),y(p.y)
{
	cout << "调用拷贝构造函数" << endl;
}
int main()
{
	Point a(1, 2);
	Point b(a);//用对象a初始化对象b,拷贝构造函数被调用
	cout << b.getX() << endl;
	Point c = a;//用对象a初始化对象c,拷贝构造函数被调用
	cout << c.getX() << endl;
	return 0;
}

运行结果:
在这里插入图片描述
【提示】以上对于b和c的初始化都调用了拷贝构造函数,这两种写法只是形式上不同,执行的操作完全相同。
【例】
类提供一个默认的拷贝构造函数,用已有对象数据成员的值去依次初始化新对象数据成员的值。
如果程序员提供了拷贝构造函数,类就不会提供默认的拷贝构造函数。

#include<iostream>
using namespace std;
class A
{
public:
	A(int i=0):m_i(i){}
	void Print()
	{
		cout << m_i << endl;
	}
private:
	int m_i;
};
void main()
{
	/*下面两句话所实现的功能相当于:int i=10;int j=i;*/
	A a(5);//用整型5去构造了一个对象a   //调用了构造函数A(int)
	A b(a);//用a对象去初始化b对象,b也是一个新对象,b的值由a来给,相当于用a去构造b

	/*用a去初始化b,相当于用a对象里面所有数据成员的值去初始化b对象里面的所有数据成员*/

	/*要想构造对象,就要去调用当前类中的构造函数,A类中没有与b匹配的构造函数*/
    
	/*但是程序可以运行,并且输出正确的结果,说明当前的类里面默认提供了一个构造函数*/
	a.Print();
	b.Print();
}

【分析】
用a去初始化b,相当于用a对象里面所有数据成员的值去初始化b对象里面的所有数据成员;
要想构造对象,就要去调用当前类中的构造函数,发现A类中没有与b匹配的构造函数;
但是程序可以运行,并且输出正确的结果,说明当前的类里面默认提供了一个构造函数。
运行结果:
在这里插入图片描述

那么这个类默认提供的拷贝构造函数的参数是什么类型呢?

(1)拷贝构造函数如果为值类类型的参数:
#include<iostream>
using namespace std;
class A
{
public:
	A(int i=0):m_i(i){}
	
	//A(A t){}//值类类型  error
	/*因为在传参的时候,a传给t的过程中又要调用构造函数,会产生递归调用,出不来结果 */
	void Print()
	{
		cout << m_i << endl;
	}
private:
	int m_i;
};
void main()
{
	A a(5);//用整型5去构造了一个对象a   //调用了构造函数A(int)
	//A b = a;
	A b(a);//用a对象去初始化b对象,b也是一个新对象,b的值由a来给,相当于用a去构造b
	a.Print();
	b.Print();
}

这样程序是错误的,编译不通过,因为在传参的时候,a传给t的过程中又要调用构造函数,会产生递归调用,出不来结果 。

(2)拷贝构造函数如果为指针类类型
#include<iostream>
using namespace std;
class A
{
public:
	A(int i=0):m_i(i){}
	A(A*t):m_i(t->m_i)
	//构造一个指针类类型的对象,就要传地址
	{
		cout << "A(A)" << endl;
	}
	//用指针类类型的拷贝构造函数  可以运行并输出正确的结果,初始化功能也可以实现
	void Print()
	{
		cout << m_i << endl;
	}
private:
	int m_i;
};
void main()
{
	A a(5);//用整型5去构造了一个对象a   //调用了构造函数A(int)
	//A b = a;
	//A b(a);//用a对象去初始化b对象,b也是一个新对象,b的值由a来给,相当于用a去构造b
	A b(&a);
	/*要构造一个指针类类型的对象,就要传地址,要用到传地址符&,用指针的话,这句话写成赋值形式就是A b = &a;
	这样就不知道到底是用a的地址去初始化b还是用a本身去初始化b,会产生视觉上的歧义,不建议使用*/
	a.Print();
	b.Print();
}

【分析】用指针类类型的拷贝构造函数 可以运行并输出正确的结果,初始化功能也可以实现。
但是要构造一个指针类类型的对象,就要传地址,要用到传地址符&,用指针的话,A b(&a);这句话写成赋值形式就是A b = &a;。这样就不知道到底是用a的地址去初始化b还是用a本身去初始化b,会产生视觉上的歧义,不建议使用。
运行结果:
在这里插入图片描述

(3)拷贝构造函数如果为引用类类型
#include<iostream>
using namespace std;
class A
{
public:
	A(int i=0):m_i(i){}
	//拷贝构造函数
	A(const A& t) :m_i(t.m_i)
		//不是把a传给t,而是把a重新叫了一个名字为t,t是a的一个别名
		//const 的作用:在初始化的时候并不修改a中数据成员的值
		//&:用a本身去给b构造
	{
		cout << "A(A)" << endl;
	}

	void Print()
	{
		cout << m_i << endl;
	}
private:
	int m_i;
};
void main()
{
	A a(5);//用整型5去构造了一个对象a   //调用了构造函数A(int)
	//A b = a;
	A b(a);//用a对象去初始化b对象,b也是一个新对象,b的值由a来给,相当于用a去构造b

	/*用a去初始化b,相当于用a对象里面所有数据成员的值去初始化b对象里面的所有数据成员*/

	/*要想构造对象,就要去调用当前类中的构造函数,A类中没有与b匹配的构造函数*/
    
	/*但是程序可以运行,并且输出正确的结果,说明当前的类里面默认提供了一个构造函数,这个构造函数参数里面的类型为引用类类型*/
	a.Print();
	b.Print();
}

【分析】
构造函数的参数为引用类类型,不是把a传给t,而是把a重新叫了一个名字为t,t是a的一个别名。
参数中const 的作用:在初始化的时候并不修改a中数据成员的值。
引用(&)的意义:用a本身去给b构造。

运行结果:
在这里插入图片描述

2.函数的形参是类的对象,是值类型的形参,调用函数时,进行形参和实参结合时。
#include<iostream>
using namespace std;
class Point
{
public:
		Point(int xx = 0, int yy = 0):x(xx),y(yy)
	{

	}
	Point(Point& p);
	int getX()
	{
		return x;
	}
	int getY()
	{
		return y;
	}
private:
	int x, y;
};
Point::Point(Point& p):x(p.x),y(p.y)
{
	cout << "调用拷贝构造函数" << endl;
}
void f(Point p)
{
	cout << p.getX() << endl;
}
int main()
{
	Point a(1, 2);
	Point b(a);//用对象a初始化对象b,拷贝构造函数被调用
	cout << b.getX() << endl;
	Point c = a;//用对象a初始化对象c,拷贝构造函数被调用
	cout << c.getX() << endl;
	f(a);//函数的形参为类的对象,调用函数时,拷贝构造函数被调用
	return 0;
}

运行结果:
在这里插入图片描述
函数传参为值传递的时候,由实参传递给形参,实参是旧对象,形参是新对象,所以要调用拷贝构造函数。
【例】

class A
{
public:
	A(int i = 0) :m_i(i)
	{
		cout << "A" << m_i << endl;
	}
	~A()
	{
		cout << "~A" << m_i << endl;
	}
	A(const A& t) :m_i(t.m_i)

	{
		cout << "A(A)" <<m_i<< endl;
	}
private:
	int m_i;
};
void fn(A s) //A s(c) 用c构造s,调用拷贝构造函数
//值类类型的参数
//形参在调用这个函数的时候才给形参开辟空间,没有调用函数之前形参是没有的,所以s是新的
{
	cout << "fn" << endl;
	//fn函数在即将退出的时候要将局部对象s析构,要调用析构函数,~A 30
}
void main()
{
	A a(5);//调用普通构造函数A(int),输出A5
	A b(a);//调用拷贝构造函数A(A),输出A5
	A c(30);//调用普通构造函数A(int),输出A30
	fn(c);
	//调用fn函数,第一步传参:将c对象传给对象s,
	//c对象是已有的旧对象,s是新对象,用c去构造s,调用拷贝构造函数A(A),输出A30
	/*在调用fn函数的时候才会给形参s开辟空间,也就是说把b传给s的时候才会给s开辟空间
	相当于s对象是新对象,b对象是实参,s对象是形参,从旧对象b到新对象s*/
	//不是把c本身传给s,把c的值拷贝了一份给s去进行初始化

	//再将要退出主函数的时候,要将c对象,b对象,a对象释放掉,析构c,b,a
	//~A 30  ~A 5  ~A 5
}

【分析】主函数中调用fn函数,第一步传参:将c对象传给对象s,c对象是实参,s对象是形参,在调用fn函数的时候才会给形参s开辟空间,也就是说把c传给s的时候才会给s开辟空间。c对象是已有的旧对象,s是新对象,从旧对象b到新对象s,用c去构造s,调用拷贝构造函数A(A),输出30。调用拷贝构造函数不是把c本身传给s,把c的值拷贝了一份给s去进行初始化。再将要退出主函数的时候,要将c对象,b对象,a对象释放掉,依次析构c,b,a,~A 30 ,~A 5 , ~A 5
fn函数的参数是值类类型,形参在调用这个函数的时候才给形参开辟空间,没有调用函数之前形参是没有的,所以s是新的。fn函数在即将退出的时候要将局部对象s析构,要调用析构函数,~A 30
调试结果:
在这里插入图片描述

3.函数返回值为类的对象,函数执行完成返回调用者时。
#include<iostream>
using namespace std;
class Point
{
public:
		Point(int xx = 0, int yy = 0):x(xx),y(yy)
	{

	}
	Point(Point& p);
	int getX()
	{
		return x;
	}
	int getY()
	{
		return y;
	}
private:
	int x, y;
};
Point::Point(Point& p):x(p.x),y(p.y)
{
	cout << "调用拷贝构造函数" << endl;
}
void f(Point p)
{
	cout << p.getX() << endl;
}
Point g()
{
	Point a(1, 2);
	return a;//函数的返回值是类的对象,返回函数值时,拷贝构造函数被调用
}
int main()
{
	Point a(1, 2);
	Point b(a);//用对象a初始化对象b,拷贝构造函数被调用
	cout << b.getX() << endl;
	Point c = a;//用对象a初始化对象c,拷贝构造函数被调用
	cout << c.getX() << endl;
	f(a);//函数的形参为类的对象,调用函数时,拷贝构造函数被调用
	Point d;
	d = g();
	cout << d.getX() << endl;
	return 0;
}

运行结果:
在这里插入图片描述
函数返回值时类类型的值返回时,由局部对象构造临时对象,局部对象是旧对象,临时对象是新对象,所以调用拷贝构造函数。
【例】

class A
{
public:
	A(int i = 0) :m_i(i)
	{
		cout << "A" << m_i << endl;
	}
	~A()
	{
		cout << "~A" << m_i << endl;
	}
	A(const A& t) :m_i(t.m_i)

	{
		cout << "A(A)" << m_i << endl;
	}

	void Print()
	{
		cout << m_i << endl;
	}
private:
	int m_i;
};
void fn(A s) 

{
	cout << "fn" << endl;

}
A test()
{
	A tt(60);//调用普通构造函数A(int),输出A60
	return tt;
	/*test函数在栈区,主函数也在栈区,当前不能从test函数返回到主函数中,两个栈区不能直接进行操作*/
	/*把tt这个局部变量先给了一个临时对象,临时对象是新的,
	从局部对象tt到临时对象的时候调用了一次拷贝构造函数。
	这时局部变量tt就消失了,由当前的临时对象把值带回来给了c,给了c之后,临时对象就可以消失了*/
}
void main()
{
	A a(5);//调用普通构造函数A(int),输出A5
	A b(a);//调用拷贝构造函数A(A),输出A5
	A c(30);//调用普通构造函数A(int),输出A30
	fn(c);//调用fn函数,调用拷贝构造函数
	c = test();//调用test函数,调用拷贝构造函数
	//注意:这句话中的c没有调用拷贝构造函数,这里的c调用的是赋值运算符重载
}

【分析】
test函数在栈区,主函数也在栈区,当前不能从test函数返回到主函数中,两个栈区不能直接进行操作。所以在调用test函数的时候,把tt这个局部变量先给了一个临时对象,局部变量tt是旧对象,临时对象是新对象,从局部对象tt到临时对象的时候调用了拷贝构造函数。这时局部变量tt就消失了,由当前的临时对象把值带回来给了c,当临时对象把值给了c之后,临时对象也就可以消失了。
调试结果:
在这里插入图片描述
结果分析:
在这里插入图片描述

总结:

调用拷贝构造函数的三种情况:
都是用旧对象去构造新对象
(1)用已有对象去初始化新对象;
(2)函数传参为类的对象的值传递时;
(3)函数返回值是类类型的值返回时。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/56684.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

PySpark介绍与安装

Spark是什么 定义&#xff1a;Apache Spark是用于大规模数据&#xff08;large-scala data&#xff09;处理的统一&#xff08;unified&#xff09;分析引擎。 简单来说&#xff0c;Spark是一款分布式的计算框架&#xff0c;用于调度成百上千的服务器集群&#xff0c;计算TB、…

SQL编译优化原理

最近在团队的OLAP引擎上做了一些SQL编译优化的工作&#xff0c;整理到了语雀上&#xff0c;也顺便发在博客上了。SQL编译优化理论并不复杂&#xff0c;只需要掌握一些关系代数的基础就比较好理解&#xff1b;比较困难的在于reorder算法部分。 文章目录 基础概念关系代数等价 j…

Delphi 开发的QR二维码生成工具,开箱即用

目录 一、基本功能&#xff1a; 二、使用说明&#xff1a; 三、操作演示gif 四、下载链接 在日常的开发中&#xff0c;经常需要将一个链接生成为二维码图片&#xff0c;特别是在进行支付开发的时候&#xff0c;因为我们支付后台获取了支付链接&#xff0c;需要变成二维码扫…

《ChatGPT原理最佳解释,从根上理解ChatGPT》

【热点】 2022年11月30日&#xff0c;OpenAI发布ChatGPT&#xff08;全名&#xff1a;Chat Generative Pre-trained Transformer&#xff09;&#xff0c; 即聊天机器人程序 &#xff0c;开启AIGC的研究热潮。 ChatGPT是人工智能技术驱动的自然语言处理工具&#xff0c;它能够…

Java之Map接口

文章目录 简述Map中key-value特点 Map接口的常用方法Map的主要实现类&#xff1a;HashMapHashMap概述 Map实现类之二&#xff1a;LinkedHashMapMap实现类之三&#xff1a;TreeMapMap实现类之四&#xff1a;Hashtable&#xff08;古老实现类&#xff09;Map实现类之五&#xff1…

结构思考力-有效提升你的工作效率20倍以上

结构思考力是一种帮助人们察觉并改善自身思考结构的思考艺术&#xff0c;它将人们的思维表达以一种逻辑结构的形式来表示&#xff0c;从而简化人与人之间的沟通成本&#xff0c;提高工作效率。每个人思考问题的方式不同&#xff0c;不同的思考结构使人们的注意力的方向也不同&a…

寻找丢失数字:数学与位运算的解密之旅

本篇博客会讲解力扣“268. 丢失的数字”的解题思路&#xff0c;这是题目链接。 注意进阶中的描述&#xff1a;你能否实现线性时间复杂度、仅使用额外常数空间的算法解决此问题&#xff1f;这里我会讲解两种思路&#xff0c;它们的时间复杂度是O(N)&#xff0c;空间复杂度是O(1)…

3.playbook剧本二

文章目录 playbook二Roles模块roles模式安装LNMP创建nginxfiles目录handlers目录tasks目录templates目录vars目录 创建mysqltasks目录 创建phpfiles目录handlers目录tasks目录templates目录vars目录 创建LNMP剧本文件 playbook二 Roles模块 角色的作用&#xff1a;把playbook…

Linux CentOS系统怎么下载软件

Linux CenOS系统想要下载软件可以在Linux内置的应用商店&#xff0c;并通过Yum 包管理器来下载&#xff08;直接使用yum命令下载软件&#xff09; 在Linux系统中&#xff0c;Yum&#xff08;Yellowdog Updater, Modified&#xff09;是用于管理RPM软件包的一个包管理器。 安装…

golang自带的命令行解析库flag库实践

1. 简介 flag用于解析命令行选项。有过类 Unix 系统使用经验的童鞋对命令行选项应该不陌生。例如命令ls -al列出当前目录下所有文件和目录的详细信息&#xff0c;其中-al就是命令行选项。 命令行选项在实际开发中很常用&#xff0c;特别是在写工具的时候。 指定配置文件的路径…

windows编译新版本linphone

目录​​​​​​​ 环境 获取源码(使用5.0.0版本5.3.0-alpha有问题编译不过) 编译环境准备 编译&#xff08;使用ninja&#xff09; 编译&#xff08;不适用使用ninja&#xff09; 报错解决 linphone-desktop是一款基于SIP的标准开源网络电话系统&#xff0c;它使用了Qt…

Bug的严重等级和优先级别与分类

一、 Bug的严重等级定义&#xff1a; 1、 Blocker 即系统无法执行、崩溃或严重资源不足、应用模块无法启动或异常退出、无法测试、造成系统不稳定。 严重花屏内存泄漏 用户数据丢失或破坏系统崩溃/死机/冻结模块无法启动或异常退出严重的数值计算错误功能设计与需求严重不符其…

危化品行业防雷检测综合解决方案

危化品是指具有毒害、腐蚀、爆炸、燃烧、助燃等性质&#xff0c;能够对人体、设施或者环境造成危害的化学品。危化品的生产、储存、运输、使用等过程中&#xff0c;都存在着遭受雷击引发火灾或者爆炸事故的风险。因此&#xff0c;对危化品场所进行防雷检测&#xff0c;是保障危…

科研周报1

时间&#xff1a;2023-07-26至2023-08-02 overleaf (LaTex) 生成并排子图 查看以下这段与chatgpt的对话&#xff1a; https://chat.openai.com/share/e7fbdccd-2847-4dbb-b816-db2b7455c628 如果要生成上下排列的子图&#xff0c;将\hfill更换为\即可 其他 前馈控制 参考…

SpringBoot 实现数据加密脱敏(注解 + 反射 + AOP)

SpringBoot 实现数据加密脱敏&#xff08;注解 反射 AOP&#xff09; 场景&#xff1a;响应政府要求&#xff0c;商业软件应保证用户基本信息不被泄露&#xff0c;不能直接展示用户手机号&#xff0c;身份证&#xff0c;地址等敏感信息。 根据上面场景描述&#xff0c;我们…

简单工厂模式VS策略模式

简单工厂模式VS策略模式 今天复习设计模式&#xff0c;由于简单工厂模式和策略模式太像了&#xff0c;重新整理梳理一下 简单工厂模式MUL图&#xff1a; 策略模式UML图&#xff1a; 1、简单工厂模式中只管创建实例&#xff0c;具体怎么使用工厂实例由调用方决定&#xff0c…

invalid use of incomplete type class ui(new Ui::MainWindow)报错,解决方案

invalid use of incomplete type class ui(new Ui::MainWindow报错&#xff0c;解决方案 原因解决方案 原因 就是在我改控件button的名字的时候&#xff0c;没有选中控件&#xff0c;导致吧mainwindow的名字改了。。。 解决方案 吧mainwindow的名字改回来 MainWindow 完美解…

blender的下载安装和配置中文环境

引言 在3D建模和动画设计领域&#xff0c;Blender 作为一款强大且免费的开源软件&#xff0c;一直以优秀的性能和对众多技术的支持赢得了大批用户的喜爱。然而&#xff0c;对于刚接触这款软件的用户而言&#xff0c;其安装和配置过程可能会带来一定困扰&#xff0c;尤其是在设…

尝试多数据表 sqlite

C 唯一值得骄傲的地方就是 通过指针来回寻址 &#x1f602; 提高使用的灵活性 小脚本buff 加成

Spring AOP 中的代理对象是怎么创建出来的?

文章目录 1. AOP 用法2. 原理分析2.1 doCreateBean2.2 postProcessAfterInitialization2.3 getAdvicesAndAdvisorsForBean2.3.1 findCandidateAdvisors2.3.2 findAdvisorsThatCanApply2.3.3 extendAdvisors 2.4 createProxy 今天和小伙伴们聊一聊 Spring AOP 中的代理对象是怎么…