【C++】类和对象——拷贝构造函数的概念、拷贝构造函数的特征

news2025/1/10 17:08:28

文章目录

  • 1.拷贝构造函数
    • 1.1拷贝构造函数的概念
    • 1.2拷贝构造函数的特征

1.拷贝构造函数

  在前面我们已经介绍了构造函数和析构函数的作用和使用方法,而拷贝构造函数则是在对象初始化时调用的一种特殊构造函数。拷贝构造函数可以帮助我们创建一个新的对象,该对象的值和另一个对象完全相同。拷贝构造函数是 C++ 中一个重要的概念,它可以用于复制一个对象,以便于在程序中进行各种操作。

在这里插入图片描述

1.1拷贝构造函数的概念

  拷贝构造函数是一种特殊类型的构造函数,用于创建一个对象的副本。

  其中“类名”为类的名称,“源对象”为被复制的对象的引用,它的原型为:

类名(const 类名& 源对象);

  拷贝构造函数有两种情况:

(1)如果没有显式地定义拷贝构造函数,编译器会生成一个默认的拷贝构造函数。 这个默认的拷贝构造函数会简单地复制每个数据成员的值。

(2)如果显式地定义了拷贝构造函数,编译器就不会生成默认的拷贝构造函数。 这时,程序员需要自己实现拷贝构造函数,以确保正确地复制对象的数据。

  在拷贝构造函数中,通常需要将源对象的数据成员的值复制到新对象中。特别地,如果类中包含指针成员,需要确保拷贝出来的对象不与源对象共享这些指针所指向的内存。

  总结:拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

1.2拷贝构造函数的特征

  拷贝构造函数也是特殊的成员函数,其特征如下:

(1)拷贝构造函数是构造函数的一个重载形式。

(2)拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。

  我们定义了一个日期类Date,该类有一个带默认参数的构造函数和一个拷贝构造函数。默认构造函数用于创建一个指定年月日的日期对象,而拷贝构造函数则用于创建一个新的日期对象,并将其初始化为另一个已存在的日期对象的副本。

  实现拷贝构造函数的方式是将已存在的日期对象的年、月、日成员变量的值分别复制到新创建的日期对象的对应成员变量中。

  在main函数中,创建两个日期对象d1和d2,其中d2通过调用拷贝构造函数并传入d1对象的引用而被创建,并与d1对象完全相同。需要注意的是,如果拷贝构造函数的实现方式有误,比如像上面代码中,拷贝构造函数形参应该写成Date(const Date& d)而不是Date(const Date d),否则在编译和运行程序时会出现各种问题,包括编译器报错和程序无限递归等。

class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
{
	_year = year;
	_month = month;
	_day = day;
}

// Date(const Date& d) // 正确写法
Date(const Date d) // 错误写法:编译报错,会引发无穷递归
{
	_year = d._year;
	_month = d._month;
	_day = d._day;
}

private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2(d1);
	return 0;
}

编译器报错引发程序无限递归:
在这里插入图片描述

(3)若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

  我们定义了两个类Time和Date,其中Time类包含了三个int类型的数据成员_hour、_minute和_second,并分别对它们设置了默认初始值。该类还有一个拷贝构造函数,在创建一个新的时间对象时,通过将已存在的时间对象的_hour、_minute、_second成员变量的值分别复制到新创建的时间对象的对应成员变量中来初始化该新对象。

  Date类包含了与日期相关的_year、_month以及_day成员变量,并且还包含了一个_time对象,用于表示一个具体的时间点。

  在main函数中,创建了一个日期对象d1,其默认初始化值为1970年1月1日,同时_time对象的_hour、_minute、_second成员变量默认为1。接着,在创建新的日期对象d2时,通过调用拷贝构造函数并传入d1对象的引用来将d2对象初始化为d1对象的副本。需要注意的是,虽然Date类没有显式定义拷贝构造函数,但是编译器会自动为该类生成一个默认的拷贝构造函数,以保证对象能够正确地被复制。

class Time
{
public:
Time()
{
	_hour = 1;
	_minute = 1;
	_second = 1;
}

Time(const Time& t)
{
	_hour = t._hour;
	_minute = t._minute;
	_second = t._second;
	cout << "Time::Time(const Time&)" << endl;
}

private:
	int _hour;
	int _minute;
	int _second;
};

class Date
{
private:
	// 基本类型(内置类型)
	int _year = 1970;
	int _month = 1;
	int _day = 1;
	// 自定义类型
	Time _t;
};

int main()
{
	Date d1;
	// 用已经存在的d1拷贝构造d2,此处会调用Date类的拷贝构造函数
	// 但Date类并没有显式定义拷贝构造函数,则编译器会给Date类生成一个默认的拷贝构造函数
	Date d2(d1);
	return 0;
}

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定义类型是调用其拷贝构造函数完成拷贝的。

(4)编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了。

// 这里会发现下面的程序会崩溃掉,这里就需要使用深拷贝去解决。
typedef int DataType;
class Stack
{
public:
Stack(size_t capacity = 10)
{
	_array = (DataType*)malloc(capacity * sizeof(DataType));
	if (nullptr == _array)
	{
		perror("malloc申请空间失败");
		return;
	}
	_size = 0;
	_capacity = capacity;
}

void Push(const DataType& data)
{
	// CheckCapacity();
	_array[_size] = data;
	_size++;
}

~Stack()
{
	if (_array)
	{
		free(_array);
		_array = nullptr;
		_capacity = 0;
		_size = 0;
	}
}

private:
	DataType *_array;
	size_t _size;
	size_t _capacity;
};

int main()
{
	Stack s1;
	s1.Push(1);
	s1.Push(2);
	s1.Push(3);
	s1.Push(4);
	Stack s2(s1);
	return 0;
}

  在main函数中,首先创建一个栈对象s1,并且向该对象中入栈了四个元素,然后再创建了一个新的栈对象s2,并将现有的栈对象s1作为参数传递进去,从而通过拷贝构造函数的调用将s2初始化为s1的副本。

  需要注意的是,由于Stack类定义的拷贝构造函数只是进行了浅拷贝,即仅仅将指针类型成员变量所指向的内存空间进行了复制,没有对该内存空间进行复制,这将会导致复制构造函数失效并出现内存相关的问题,比如程序退出时程序崩溃等。为了解决这个问题,可以使用深度拷贝来实现,确保新的对象和原对象各自独立占有自己的内存空间,而不是共享同一内存空间。

注意:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝。

在这里插入图片描述

(5)拷贝构造函数典型调用场景:

  1.使用已存在对象创建新对象

  2.函数参数类型为类类型对象

  3.函数返回值类型为类类型对象

  我们定义了一个日期类 Date,该类包含一个带3个参数的构造函数、一个拷贝构造函数和一个析构函数。

  在main函数中,我们首先创建一个名为d1的Date对象,通过调用带参数的构造函数,将其初始化为指定年月日为2022年1月13日。接着,我们调用了名为Test的函数,并将d1对象以值传递的形式传入。在函数内部,又通过调用Date类的拷贝构造函数,将传入的对象在内存中复制出一个新的对象temp。最后,函数返回值通过值传递的方式返回到main函数中。

  这里需要注意的是,因为传递参数的过程中涉及到复制构造函数的调用,所以在传参的过程中会调用Date类的拷贝构造函数,复制出一个新的临时对象。需要注意的是,在Test函数的执行过程中,函数内部所创建的临时对象temp在函数返回后也会被销毁。因此,在控制台的输出信息中可以看到,在Test函数中,先调用了拷贝构造函数,在函数结束时,临时对象temp被销毁,最后在main函数中,对象d1依旧存在,而且在程序结束时会调用Date的析构函数进行销毁。

class Date
{
public:
Date(int year, int minute, int day)
{
	cout << "Date(int,int,int):" << this << endl;
}	

Date(const Date& d)
{
	cout << "Date(const Date& d):" << this << endl;
}

~Date()
{
	cout << "~Date():" << this << endl;
}

private:
	int _year;
	int _month;
	int _day;
};

Date Test(Date d)
{
	Date temp(d);
	return temp;
}

int main()
{
	Date d1(2022,1,13);
	Test(d1);
	return 0;
}

  为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用尽量使用引用。
在这里插入图片描述

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

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

相关文章

互联网中的web3.0和gpt有何联系?

文章目录 ⭐前言⭐web 3.0&#x1f496; web1.0-web3.0的概念 ⭐chatgpt&#x1f496; gpt的概念 ⭐总结⭐结尾 ⭐前言 大家好&#xff0c;我是yma16&#xff0c;本文分享互联网中的web3.0和gpt的关系。 互联网的发展 第一台计算机的出现 世界上第一台通用计算机“ENIAC”于…

C语言(扫雷)

扫雷 开发过程开发思路菜单界面游戏界面的打印雷的随机产生扫雷以及判断胜利条件代码整合 开发过程 准备工作效果展示 准备工作&#xff1a; game.h 一个头文件–>声明函数 test.c 为主文件 game.c 为功能函数实现文件 效果展示 开发思路 菜单界面 游戏界面打印&…

二、机器人的结构设计

1 、螺丝连接的坚固性 坚固性是机器人能顺利完成指定任务的一个重要条件&#xff0c;无论我们程序设计的如何完美&#xff0c; 如果不能保证机器人具有坚固性和稳定性&#xff0c;就无法保证任务的顺利完成&#xff0c;机器人在运行时如 果发生散架和分裂都会影响其功能的实现…

阿里云的白名单规则如何实现IP限制和访问控制?

阿里云的白名单规则如何实现IP限制和访问控制&#xff1f;   [本文由阿里云代理商[聚搜云]撰写]   随着企业在云计算领域的深入应用&#xff0c;网络安全问题日益凸显。阿里云提供了一种名为“白名单”的规则&#xff0c;帮助用户实现IP限制和访问控制。本文将详细阐述阿里…

“ 最近 ” ,准备跳槽的可以看看

前两天跟朋友感慨&#xff0c;今年的铜三铁四、裁员、疫情导致好多人都没拿到offer!现在已经12月了&#xff0c;具体明年的金三银四只剩下两个月。 对于想跳槽的职场人来说&#xff0c;绝对要从现在开始做准备了。这时候&#xff0c;很多高薪技术岗、管理岗的缺口和市场需求也…

【ROS】ROS2中的概念和名词解释

1、工作空间 workspace ROS以固定的目录结构创建项目工程&#xff0c;项目根目录称为工作空间 1.1 典型工作空间结构 src&#xff1a; 代码空间&#xff1b; build&#xff1a; 编译空间&#xff0c;保存编译过程中产生的中间文件&#xff1b; install&#xff1a;安装空间…

一种在不改变源码的情况下测试看门狗复位的方法

什么是“看门狗”&#xff1f; 看门狗定时器&#xff08;WDT&#xff0c;Watch Dog Timer&#xff09;是单片机的一个组成部分&#xff0c;它实际上是一个计数器&#xff0c;一般给看门狗一个数字&#xff0c;程序开始运行后看门狗开始倒计数。如果程序运行正常&#xff0c;过…

git使用X篇_2_Git全套教程IDEA版(git、GitHub、Gitee码云、搭建公司内部GitLab、与IDEA集成等内容)

本文是根据以下视频及网上总结进行更新后的介绍git使用的博文。包含了git、GitHub、Gitee码云、搭建公司内部GitLab、与IDEA集成等内容。 笔记来源&#xff1a;【尚硅谷】5h打通Git全套教程IDEA版&#xff08;涵盖GitHub\Gitee码云\GitLab&#xff09; 文章目录 初识 Git0、内容…

vue-echarts图表的应用(总结)

vue项目中echarts图表的应用(总结) 一 . 安装echarts包 npm i echarts 二 . 放置两个图表的div&#xff0c;并给定高宽 <div class"chart"><!-- 图表 --><div ref"social" style" width: 100%; height:100% " /> </div&g…

Python入门(十五)函数(三)

函数&#xff08;三&#xff09; 1.返回值1.1 返回简单值1.2 让实参变成可选的1.3 返回字典1.4 结合使用函数和while循环 作者&#xff1a;Xiou 1.返回值 函数并非总是直接显示输出&#xff0c;它还可以处理一些数据&#xff0c;并返回一个或一组值。函数返回的值称为返回值。…

【2023】Redis主从复制模式集群

资源有限&#xff0c;本文使用Docker部署目录 &#x1f3b6;主从模式介绍&#x1f3b6; 搭建主从模式集群&#x1f3b6; 使用命令搭建主从集群&#x1f3b6; 通过配置文件搭建主从模式集群 &#x1f3b6;配置读写分离&#x1f3b6; 用心跳机制提高主从复制的可靠性&#x1f3b6…

[golang 微服务] 3. ProtoBuf认识与使用

一.protobuf简介 前言 在移动互联网时代&#xff0c; 手机流量、 电量是最为有限的资源&#xff0c;而移动端的即时通讯应用无疑必须得直面这两点。解决流量过大的基本方法就是 使用高度压缩的通信协议&#xff0c;而数据压缩后流量减小带来的自然结果也就是省电&#xff1a;因…

#Verilog HDL# Verilog设计中的竞争问题和解决办法

经过前面文章的学习&#xff0c;我们知道&#xff1a;不管是Verilog设计语言&#xff0c;还是Sytemverilog验证语言&#xff0c;标准都定义了语言调度机制&#xff0c;来规范各家编译器和仿真器的开发。今天&#xff0c;我们着重看一下Verilog 硬件设计语言中竞争问题&#xff…

算法拾遗三十一马拉车算法

算法拾遗三十一马拉车算法 回文是什么回文暴力求法 Manacher算法回文直径和回文半径最右回文边界最右回文右边界的中心C位置Manacher求解过程Manacher 题 回文是什么 一个字符串正过来念和反过来念一样&#xff0c;总的来说就是有一个对称轴可能在字符上也可能在范围上面 回文…

算法刷题总结 (十一) 二叉树

算法总结11 二叉树 一、二叉树的概念1.1、什么是二叉树&#xff1f;1.2、二叉树的常见类型1.2.1、无数值&#xff08;1&#xff09;、满二叉树&#xff08;2&#xff09;、完全二叉树 1.2.2、有数值&#xff08;3&#xff09;、二叉搜索树&#xff08;4&#xff09;、平衡二叉搜…

设置服务器ssh远程连接时超时关闭的时间

我们通过ssh远程连接服务器时&#xff0c;如果一段时间客户端没有使用&#xff0c;就会与服务器断开连接。这个断开的时间我们是可以自己的设置的。 以linux centos系统为例&#xff0c; 具体设置方法如下&#xff1a; 1、通过下面的命令编译sshd配置文件 vim /etc/ssh/sshd_…

SylixOS vutex

vutex 概念 SylixOS 引入了与 Linux futex 类似的用户快速锁 vutex&#xff08;vitual user mutex&#xff09;&#xff08;SylixOS 习惯称为“等待变量锁”&#xff09;&#xff1b;vutex 包括两个操作&#xff1a;pend 和 post&#xff0c;pend 操作用于等待期望值得到满足&…

DFS专题

题单地址&#xff1a;【237题】算法基础精选题单_ACM竞赛_ACM/CSP/ICPC/CCPC/比赛经验/题解/资讯_牛客竞赛OJ_牛客网 老子的全排列呢 dfs回溯 int n 8; int idx; int record[10]; bool vis[10];void dfs(int num) {if(numn){for(int i1;i<n;i) cout<<record[i]<…

【ONE·C++ || C++11(一)】

总言 主要介绍C11中的一些功能语法。 文章目录 总言0、思维导图1、基本情况简介2、统一的列表初始化2.1、{}的使用2.2、initializer_list2.2.1、基础介绍2.2.2、在各容器中实现说明 3、声明3.1、auto3.2、nullptr3.3、decltype 4、范围for5、智能指针6、STL中一些变化6.1、C11…

一、版本控制

1、什么是版本控制 1.1、版本控制的概念 版本控制&#xff08;Revision control&#xff09;是一种在开发的过程中用于管理我们对文件、目录或工程等内容的修改历史&#xff0c;方便查看更改历史记录&#xff0c;备份以便恢复以前的版本的软件工程技术。 1.2、版本控制的作用…