C++ 拷贝构造函数

news2024/9/28 23:31:30

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

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;
}
1.类提供一个默认的拷贝构造函数

类提供一个默认的拷贝构造函数,用已有对象数据成员的值去依次初始化新对象数据成员的值。
如果程序员提供了拷贝构造函数,类就不会提供默认的拷贝构造函数。

#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.函数传参为值传递时调用拷贝构造函数

函数传参为值传递的时候,由实参传递给形参,实参是旧对象,形参是新对象,所以要调用拷贝构造函数。

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.函数返回值为类类型的值返回时调用拷贝构造函数

函数返回值时类类型的只返回时,由局部对象构造临时对象,局部对象是旧对象,临时对象是新对象,所以调用拷贝构造函数。

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之后,临时对象也就可以消失了。
调试结果:
在这里插入图片描述
结果分析:
在这里插入图片描述

4.调用拷贝构造函数的三种情况

都是用旧对象去构造新对象
(1)用已有对象去初始化新对象;
(2)函数传参为值传递时;
(3)函数返回值时类类型的只返回时。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动驾驶感知系统--惯性导航定位系统

惯性导航定位 惯性是所有质量体本身的基本属性&#xff0c;所以建立在牛顿定律基础上的惯性导航系统&#xff08;Inertial Navigation System,INS&#xff09;(简称惯导系统)不与外界发生任何光电联系&#xff0c;仅靠系统本身就能对车辆进行连续的三维定位和三维定向。卫星导…

Ubuntu-文件和目录相关命令一

&#x1f52e;linux的文件系统结构 ⛳目录结构及目录路径 &#x1f9e9;文件系统层次结构标准FHS Filesystem Hierarchy Standard(文件系统层次结构标准&#xff09; Linux是开源的软件&#xff0c;各Linux发行机构都可以按照自己的需求对文件系统进行裁剪&#xff0c;所以众多…

MyBatisPlus从入门到精通-3

紧接着上一篇的查询 接下来的重点介绍增删改操作了 Insert id&#xff08;主键&#xff09;生成策略 前面的案列中我们没有指定id字段 但是它是生成了一个很长的id&#xff0c;并不是我们数据表定义自增 这是Mp内部算法出来的一个值 其实根据不同应用场景&#xff0c;应该使…

抖音SEO源代码的部署与搭建技巧详解

抖音SEO源代码的部署与搭建是一项重要的技术&#xff0c;促进了抖音的发展。在此&#xff0c;我将为大家详细介绍抖音SEO源代码的部署与搭建技巧。 首先&#xff0c;我们需要了解抖音SEO源代码的含义。SEO源代码是搜索引擎优化的核心&#xff0c;它是用于帮助搜索引擎更好地理解…

PHP使用Redis实战实录3:数据类型比较、大小限制和性能扩展

PHP使用Redis实战实录系列 PHP使用Redis实战实录1&#xff1a;宝塔环境搭建、6379端口配置、Redis服务启动失败解决方案PHP使用Redis实战实录2&#xff1a;Redis扩展方法和PHP连接Redis的多种方案PHP使用Redis实战实录3&#xff1a;数据类型比较、大小限制和性能扩展 数据类型…

pytorch的发展历史,与其他框架的联系

我一直是这样以为的&#xff1a;pytorch的底层实现是c(这一点没有问题&#xff0c;见下边的pytorch结构图),然后这个部分顺理成章的被命名为torch,并提供c接口,我们在python中常用的是带有python接口的&#xff0c;所以被称为pytorch。昨天无意中看到Torch是由lua语言写的&…

docker 部署 mysql8.0 无法访问

文章目录 &#x1f5fd;先来说我的是什么情况&#x1fa81;问题描述&#x1fa81;解决方法&#xff1a;✔️1 重启iptables✔️2 重启docker &#x1fa81;其他有可能连不上的原因✔️1 客户端不支持caching_sha2_password的加密方式✔️2 my.conf 配置只有本机可以访问 &#…

用JavaScript和HTML实现一个精美的计算器

文章目录 一、前言二、技术栈三、功能实现3.1 引入样式3.2 编写显示页面3.2 美化计算器页面3.3 实现计算器逻辑 四、总结 一、前言 计算器是我们日常生活中经常使用的工具之一&#xff0c;可以帮助我们进行简单的数学运算。在本博文中&#xff0c;我将使用JavaScript编写一个漂…

【我们一起60天准备考研算法面试(大全)-第二十八天 28/60】【枚举】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

VBA技术资料MF35:VBA_在Excel中过滤数据

【分享成果&#xff0c;随喜正能量】好马好在腿&#xff0c;好人好在嘴。不会烧香得罪神&#xff0c;不会讲话得罪人。慢慢的你就会发现&#xff0c;一颗好心&#xff0c;永远比不上一张好嘴。。 我给VBA的定义&#xff1a;VBA是个人小型自动化处理的有效工具。利用好了&#…

Android实例——自定义控件

自定义View 对现有控件进行扩展 案例一&#xff1a;添加背景 如下继承TextView public class MyTextView extends androidx.appcompat.widget.AppCompatTextView {private Paint mPaint1;private Paint mPaint2;public MyTextView(Context context) {this(context, null);}…

wireshark抓包新手使用教程(超详细)

一、简介 Wireshark是一款非常流行的网络封包分析软件&#xff0c;可以截取各种网络数据包&#xff0c;并显示数据包详细信息。 为了安全考虑&#xff0c;wireshark只能查看封包&#xff0c;而不能修改封包的内容&#xff0c;或者发送封包。 wireshark能获取HTTP&#xff0c;也…

day47-Testimonial Box Switcher(推荐箱切换器-动态进度条自动更新卡片信息)

50 天学习 50 个项目 - HTMLCSS and JavaScript day47-Testimonial Box Switcher&#xff08;推荐箱切换器-动态进度条自动更新卡片信息&#xff09; 效果 index.html <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"…

Docker续集+Docker Compose

目录 Containerd与docker的关系 runCrunC与Containerd的关联 OCI协议Dockerfile多阶段构建&#xff08;解决&#xff1a;如何让一个镜像变得更小 &#xff09;多阶段构建Images瘦身实践.dockerignore Docker Compose快速开始Quick StartCompose 命令常用命令命令说明 Compose 模…

11. Mybatis 的增删查改【万字详解】

目录 1. 数据的查找 select 1.1 查询所有数据 1.2 通过 id 进行查找 2. 插入数据 insert 3. 修改数据 update 4. 删除数据 delete 5. $ 和 # 的区别 5.1 SQL 注入 用户登录 6. Spring Boot 打印 SQL 日志 7. order by 排序 8. like 查询 9. 通过页面返回数据 10. …

C++--菱形继承

1.什么是菱形继承 单继承&#xff1a;一个子类只有一个直接父类时称这个继承关系为单继承 多继承&#xff1a;一个子类有两个或以上直接父类时称这个继承关系为多继承 菱形继承的问题&#xff1a;菱形继承有数据冗余和二义性的问题&#xff0c;数据冗余是由于创建多个相同类型的…

【C++】优先级队列的基本概念以及其模拟实现

文章目录 补充知识&#xff1a;仿函数一、优先级队列&#xff1a;1.引入2.介绍 二、priority_queue的模拟实现1.大体框架2.私有成员函数&#xff1a;1.向下调整&#xff08;AdjustDown&#xff09;2.向上调整&#xff08;AdjustUp&#xff09; 3.公有成员函数1大小&#xff08;…

Windows驱动第一节(什么是驱动?)

本文来自微软,由本人兴趣爱好人工翻译(非机翻) What is a driver? - Windows drivers | Microsoft Learn 我想很难给驱动这个词一个准确的定义.最基础的定义是驱动是一个用于让操作系统和硬件设备通信的软件组件. 举一个例子,假设一个应用程序需要从硬件设备读取一些数据,这…

2023河南萌新联赛第(三)场:郑州大学 A - 发工资咯

2023河南萌新联赛第&#xff08;三&#xff09;场&#xff1a;郑州大学 A - 发工资咯 时间限制&#xff1a;C/C 2秒&#xff0c;其他语言4秒 空间限制&#xff1a;C/C 262144K&#xff0c;其他语言524288K 64bit IO Format: %lld 题目描述 一个公司有n个人&#xff0c;每个月都…

C++类与对象 - 3(拷贝构造函数和运算符重载)(超详细)

C类与对象 - 3 1. 拷贝构造函数1.1 概念1.2 特征 2. 赋值运算符重载2.1 运算符重载2.2 赋值运算符重载记点2.3 前置和后置重载 3. const成员函数记点 4. 取地址及const取地址操作符重载 1. 拷贝构造函数 1.1 概念 在现实生活中&#xff0c;可能存在一个与你一样的自己&#x…