类的默认成员函数【拷贝(复制)构造函数】

2023-11-07

拷贝(复制)构造函数

用一个已经存在的对象初始化另一个新对象时,编译器会自动调用拷贝构造函数。


1、拷贝构造函数是构造函数的一种重载形式

2、拷贝构造函数的参数:单个形参,传递const类类型的引用

1)如果传值引用,会引发无穷调用
在这里插入图片描述
2)如果不加const,可能会因为我们不小心写错代码,改变已经存在的对象

例1:拷贝构造函数参数不加const

class Date
{
public:
	Date(int year = 2000, int month = 4, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 拷贝构造函数
	Date(Date& d) // 不加const
	{
		_year = d._year;
		_month = d._month;
		_day = d._day++; // 假如这我们不小心写错了
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 4, 19);
	Date d2(d1);

	system("pause");
	return 0;
}

创建d1调用构造函数之后,初始化好的d1对象中的成员变量的值如下图:
在这里插入图片描述当我们调用拷贝构造函数初始化完d2后,d1的值发生了改变,我们只是想用d1的值初始化d2,却把d1改了,这岂不是很离谱在这里插入图片描述
当我们加上const,它就不允许我们修改了,这就防止了我们手滑写错
在这里插入图片描述

3、编译器自动调用拷贝构造函数的三种情况

1) 用一个对象初始化同类的另一个对象

2)当函数的参数为类的对象时。在调用函数时需要将实参对象完整的传递给形参,形参实例化时会对实参进行拷贝,按实参复制一个形参,系统通过调用拷贝构造函数来实现的,这样能保证形参具有和实参完全相同的值

3)函数的返回值是类的对象。在函数调用完毕将返回值带回函数调用处时。此时需要将函数中的对象复制一个临时对象并传给该函数的调用处。

未显式定义拷贝构造函数,编译器自动生成的默认拷贝构造函数的拷贝方式是浅拷贝

4、必须显式定义拷贝构造函数的场景

1)当类中不涉及资源管理时,我们自己显式定义的拷贝构造函数和编译器自动生成的,最终效果是一样的

例2:Date类中不涉及资源管理

① 显式定义拷贝构造函数

class Date
{
public:
	Date(int year = 2000, int month = 4, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	// 拷贝构造函数
	Date(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 4, 19); // 调用构造函数
	Date d2(d1); // 调用拷贝构造函数

	system("pause");
	return 0;
}

这里是引用

② 还是Date类,未显式定义拷贝构造函数,编译器自动生成默认拷贝构造函数

class Date
{
public:
	Date(int year = 2000, int month = 4, int day = 21)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d1(2021, 4, 19);
	Date d2(d1);

	system("pause");
	return 0;
}

和上边的监视窗口中的内容一模一样这里是引用

2)当类中涉及到资源管理

例3:String类中,拷贝构造函数中使用malloc在堆上申请空间

① 未显式定义拷贝构造函数

class String
{
public:
	// 构造函数
	String(const char* str = "jia")
	{
		_str = (char*)malloc(strlen(str) + 1);
		strcpy(_str, str);
	}

	// 析构函数
	~String()
	{
		free(_str);
		_str = nullptr;
	}
private:
	char* _str;
};

// 当TestString函数执行完,s1、s2就要被销毁,这样可以看到销毁时调用析构函数的结果
// 如果直接写到main函数中,要return之后,对象才会被销毁,看不到调用析构函数的结果,需要将输出定向到文件中
void TestString()
{
	String s1("hello");
	String s2(s1);
}
int main()
{
	TestString();

	system("pause");
	return 0;
}

s1和s2中_str指向同一块内存空间
在这里插入图片描述

s1和s2中的_str变量的地址相同,这两个对象指向同一块内存空间。在这里插入图片描述
调用完TestString()函数后,s1和s2对象销毁时,会调用析构函数,先释放s2中的资源,可以看到s2指向的地址已经变成了空,而s1中访问的空间已经变成了无效访问
在这里插入图片描述
也就是说,此时堆上申请的空间已经被释放了,当s1调用析构函数再次free这段空间时,就会出错
在这里插入图片描述

② 显式定义拷贝构造函数

class String
{
public:
	// 构造函数
	String(const char* str = "jia")
	{
		// 从堆上开辟能保存字符串大小的一段空间,并将这段空间的首地址传给_str
		_str = (char*)malloc(strlen(str) + 1);
		// 将str字符串中的字符一个一个拷贝到_str指向的空间中
		strcpy(_str, str);
	}
	
	// 拷贝构造函数
	String(const String& s)
	{
		_str = (char*)malloc(strlen(s._str)+1);
		strcpy(_str, s._str);
	}

	// 析构函数
	~String()
	{
		cout << "~String()" << endl;
		free(_str);
		_str = nullptr;
	}
private:
	char* _str;
};

void TestString()
{
	String s1("hello");
	String s2(s1);
}
int main()
{
	TestString();

	system("pause");
	return 0;
}

s1中_str和s2中_str指向各自的内存空间
在这里插入图片描述

这次s2初始化时调用了显式定义的拷贝构造函数,我们在拷贝构造函数中给s2对象的_str在堆上新申请了一块空间,s1和s2各自拥有一段内存空间,所以s1和s2中_str中的地址不同
在这里插入图片描述
销毁对象时,调用析构函数free的时候,各自释放自己的那块空间,就不会出错了
在这里插入图片描述
结论:类中涉及到资源管理时一定要显式定义拷贝构造函数!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

类的默认成员函数【拷贝(复制)构造函数】 的相关文章

随机推荐

  • oracle重复数据保留需要的一条数据

    由于功能开发进度的问题 人员录入的时候仅能够多次录入 不能够录入之后直接以该数据未蓝本引入导致多部门的时候必须多次创建冗余的数据 且由于数据录入的不规范 录入了许多相同的数据 特别是同单位同部门的数据 故需要处理此类数据 因此需要对此类重复
  • Unity --- 文本输入框的使用

    文本输入框有两个版本 一个是旧版的文本输入框 一个是新版的输入字段 这里选择旧版 其实旧版和新版的唯一区别就是text组件有些不同 其它的没啥不同 上面这两张图就是文本输入框中最重要的 input field 输入区域 组件的参数了 上面这
  • leetcode报错:member access within null pointer of type 'struct ListNode'

    背景 在编写判断单链表是否有环时 出现这错误 错误出现原因 错误出现原因 color Red text 38169 35823 20986 29616 21407 22240 因为试图使用空指针 解决方法 解决方法 color Red te
  • 音频模块的介绍

    一 术语总结 1 HIFI 级 HIFI 一词通常指高保真音频 High Fidelity Audio 是指尽可能保持音频信号的原始质量 让听众感受到最真实的音乐表现 因此 HIFI级 通常指具有高保真音频性能的产品或设备 例如高保真耳机
  • MAC使用Visual Studio Code开发C/C++

    MAC使用Visual Studio Code开发C C 一 前置概念 理解 二 环境准备 三 编译 运行 四 补充 一 前置概念 理解 VS code只是一个纯文本编辑器 editor 不是IDE 集成开发环境 不含编译器 compile
  • html天气插件iframe,分享常用7款天气预报代码iframe嵌入网页方式

    如果在网站上加入天气预报功能 你找不到更好的天气预报代码 可以看下本站和大家分享的7款天气预报代码iframe嵌入网页方式 天气预报代码1 src http appnews qq com cgi bin news qq search cit
  • python:pydub模块

    一 安装 1 安装模块 pip install pydub 2 安装插件 云盘中下载文件ffmpeg 打开电脑上的控制面板 系统 高级系统设置 环境变量 然后双击path 看到如下的界面 然后点新建会出现一个新建的地址栏 你需要在这个新建地
  • 备忘:maven 错误信息: Plugin execution not covered by lifecycle configuration

    在一个pom文件中 由于需要设置了一下几个默认goal的版本号 如下
  • 算法题:回文数

    力扣 思路 用栈 public static boolean isPalindrome int x if x lt 0 return false if x 0 return true 怎么取每位数字 String s String valu
  • 2023-DataWorks数仓开发手册收藏版

    DataWorks开发规范 1 数仓基本概念 1 4 1 ods数据源层表命名规范 1 4 2 dim维表层表命名规范 1 4 3 dwd数据明细层表命名规范 1 4 3 dws数据明细层表命名规范 1 4 4 ads数据应用层表命名规范
  • Docker从入门到精通

    目录 一 初识 Docker 1 Docker概念 2 安装Docker CentOS系统 3 Docker的架构 4 阿里云镜像加速 5 Docker容器虚拟化 与 传统虚拟机比较 二 Docker 服务相关命令 1 启动docker 服
  • 普通树转二叉树:左儿子右兄弟表示法

    这两天在吃力地学DP的优化 被虐地不行不行的 搞个小插曲 左儿子右兄弟 顾名思义 是一棵转换后的树 它是一棵二叉树 一个节点的左子树表示的是原树中这个节点的子节点 一个节点的右子树表示的是这个节点在原树中的兄弟 父节点相同的点 这么表示有什
  • 【机器学习】Reinforcement Learning-强化学习学习笔记

    一 强化学习的定义 1 1 什么是强化学习 首先 强化学习并不是某一种特定的算法 而是一类算法的统称 解决序列决策问题的一类方法 通过寻求最优策略 获取最大回报 强化学习就是智能体从环境到动作映射的学习 以使回报信号 激励信号 函数值最大
  • 在LINUX上安装Memcache服务器(实践记录)

    下载并安装Memcache服务器端 服务器端主要是安装memcache服务器端 下载 http www danga com memcached dist memcached 1 2 2 tar gz memcached 1 4 5 tar
  • Redux原理分析

    Redux原理分析 Redux是什么 很多人认为redux必须要结合React使用 其实并不是的 Redux 是 JavaScript 状态容器 只要你的项目中使用到了状态 并且状态十分复杂 那么你就可以使用Redux管理你的项目状态 它可
  • 如何卸载华为手机内置app

    作者 朱金灿 来源 clever101的专栏 为什么大多数人学不会人工智能编程 gt gt gt 最近自用的华为手机的存储空间快满了 想把手机内置的app清理一下 然后到网上搜索解决方案 大致弄明白了步骤 主要是通过华为手机内置APP卸载工
  • windows下cmd快速生成大文件命令

    fsutil file createnew 文件名 文件大小
  • C/C++中的头文件加上头文件中的ifndef/define/endif有什么作用?

    参考博客 https blog csdn net summer00072 article details 80883514 主要的作用就是防止头文件重复包含了 这个作用在Qt中是已经给你做好了 qt新建一个文件的时候就会直接给你建立完毕
  • Android结合ButterKnife创建自己的BaseActivity(兼容6.0)

    在Android开发中 我们需要创建一个BaseActivity来方便对APP中所有的activity进行统一操作 今天我们就来说说如何结合ButterKnife来打造一个加强版的BaseActivity 不熟悉ButterKnife的朋友
  • 类的默认成员函数【拷贝(复制)构造函数】

    拷贝 复制 构造函数 用一个已经存在的对象初始化另一个新对象时 编译器会自动调用拷贝构造函数 1 拷贝构造函数是构造函数的一种重载形式 2 拷贝构造函数的参数 单个形参 传递const类类型的引用 1 如果传值引用 会引发无穷调用 2 如果