C++进阶之路:何为拷贝构造函数,深入理解浅拷贝与深拷贝(类与对象_中篇)


✨✨ 欢迎大家来访Srlua的博文(づ ̄3 ̄)づ╭~✨✨

🌟🌟 欢迎各位亲爱的读者,感谢你们抽出宝贵的时间来阅读我的文章。

我是Srlua小谢,在这里我会分享我的知识和经验。🎥

希望在这里,我们能一起探索IT世界的奥妙,提升我们的技能。🔮

记得先点赞👍后阅读哦~ 👏👏

📘📚 所属专栏:C/C++

欢迎访问我的主页:Srlua小谢 获取更多信息和资源。✨✨🌙🌙

​​

​​

目录

拷贝构造函数

概念 :

特征:

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

2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用

3.若未显式定义,编译器会生成默认的拷贝构造函数。

4. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

对象拷贝

浅拷贝:

深拷贝:

示例理解:

技术总结:

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


拷贝构造函数

概念 :

在创建对象时,可否创建一个与已存在对象一某一样的新对象呢?

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

特征:

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

1. 拷贝构造函数是构造函数的一个重载形式

#include <iostream>class MyClass {
private:int* data;public:// 默认构造函数MyClass() {data = new int(0);}// 拷贝构造函数MyClass(const MyClass& other) {data = new int(*other.data);}// 析构函数~MyClass() {delete data;}// 设置数据void setData(int value) {*data = value;}// 获取数据int getData() const {return *data;}
};int main() {MyClass obj1;obj1.setData(42);// 使用拷贝构造函数创建新对象MyClass obj2(obj1);std::cout << "obj1 data: " << obj1.getData() << std::endl;std::cout << "obj2 data: " << obj2.getData() << std::endl;return 0;
}

在上述示例中,MyClass 类拥有一个指针 data,在默认构造函数中为其分配内存,并在析构函数中释放内存。拷贝构造函数通过使用 new 运算符,在堆上分配新的内存,并将原对象的数据复制到新内存中。

运行示例代码,输出结果为:

obj1 data: 42
obj2 data: 42

可以看到,通过拷贝构造函数创建的新对象 obj2 具有与原对象 obj1 相同的数据。


2. 拷贝构造函数的参数只有一个必须是类类型对象的引用

使用传值方式编译器直接报错,因为会引发无穷递归调用。

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.若未显式定义,编译器会生成默认的拷贝构造函数。

默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

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. 编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗?

如果一个类没有指针或引用等需要特别注意的成员变量,那么编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,不需要自己显式实现。这是因为默认拷贝构造函数会逐个复制对象的所有非静态成员变量,包括简单类型(如 int、double 等)和数组等。

然而,当一个类拥有指针或引用等需要特别注意的成员变量时,编译器生成的默认拷贝构造函数不能保证正确的深拷贝,会导致浅拷贝问题和内存泄漏等问题。此时,需要手动定义一个拷贝构造函数来进行深拷贝操作,从而避免这些问题的出现。

因此,需要根据具体情况来决定是否需要自己显式实现拷贝构造函数。如果类中只有简单类型的成员变量,就可以使用编译器生成的默认拷贝构造函数;如果类中有指针或引用等需要特别注意的成员变量,就需要手动实现一个深拷贝的拷贝构造函数。

这里会发现下面的程序会崩溃掉?这里就需要我们用深拷贝去解决。

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;
}

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

对象拷贝

在C++中,对象拷贝指的是将一个对象的值复制到另一个对象中。常见的对象拷贝方法包括拷贝构造函数赋值运算符

拷贝构造函数是用来创建一个对象,该对象与另一个对象具有相同的值。它通常用于实现深拷贝,并且可以从其他对象中创建一个新对象。拷贝构造函数的语法如下:

class MyClass {
public:MyClass(const MyClass& other); // 拷贝构造函数
};

其中 other 是要拷贝的对象的引用。

赋值运算符是用于将一个对象的值复制到另一个对象中的运算符。通常使用 = 符号进行赋值操作。赋值运算符的语法如下:

class MyClass {
public:MyClass& operator=(const MyClass& other); // 赋值运算符
};

其中 other 是要拷贝的对象的引用。注意赋值运算符返回值为当前对象的引用,以支持链式赋值操作。

需要注意的是,对象拷贝可能涉及浅拷贝和深拷贝的概念,因此需要根据情况选择适当的拷贝方法。如果类中包含指针或资源管理的成员变量,则需要手动实现深拷贝,以确保正确的对象复制和资源释放。否则,在执行浅拷贝时,两个对象将共享同一块内存,可能会导致悬挂指针、内存泄漏等问题。

在使用对象拷贝时,还需要注意对象的生命周期和内存管理,避免出现悬挂指针、内存泄漏等问题。

浅拷贝:

浅拷贝是指简单地将一个对象的值复制给另一个对象,包括对象中的所有成员变量。这意味着拷贝后的对象和原始对象共享同一块内存,当其中一个对象修改了内存中的值时,另一个对象也会受到影响。这种情况下,如果两个对象的析构函数试图同时释放同一块内存,会导致内存错误。

深拷贝:

深拷贝是指创建一个对象的独立副本,其中包括对象中的所有成员变量。这意味着拷贝后的对象拥有自己的内存空间,对其中一个对象的修改不会影响另一个对象。这种情况下,每个对象的析构函数可以安全地释放自己拥有的内存。

为了实现深拷贝,通常需要手动分配内存并将原始对象中的数据复制到新对象中,例如使用 new 运算符来动态分配内存,并通过拷贝构造函数或赋值运算符将数据复制到新对象中。而浅拷贝则可以使用默认的拷贝构造函数和赋值运算符,由编译器自动生成。

需要特别注意的是,如果类中包含指针或资源管理的成员变量(如动态分配的内存),则需要手动实现深拷贝以确保正确的对象复制和资源释放。否则,在执行浅拷贝时,两个对象将共享同一块内存,可能会导致悬挂指针、内存泄漏等问题。

因此,当类中存在指针或资源管理的成员变量时,通常需要自定义拷贝构造函数和赋值运算符,以实现深拷贝,避免出现潜在的问题。

示例理解:

假如现在你买了(构造)一个房子,开发商给你配了一个钥匙(指针成员变量)

那么,现在你的朋友也行买个和你一样的房子,也声明了另一个房子,然后(拷贝构造)这时我们发现系统崩溃了,为什么呢?而且我们可以发现运行的出来的地址是一样的,这证明两个人的钥匙配对的是同一套“房子”,所以这是错误的!因为C++不知道你复制一把钥匙的目的是什么,所以就只是单纯的复制了一把钥匙,这就是浅拷贝!

我们要效果是,你的朋友要的是和你有一样的房子,而不是同一个,所以我们自定义一个拷贝构造函数,这时的运行结果显示,两套房子的地址不一样了~这就是深拷贝!

析构函数析构完后意味着“第一队拆迁办”已经把第一套房子拆了,而此时两个钥匙指向的同一套房子,当“第二队拆迁办”来了之后,发现,好家伙,房子已经被拆了,所以程序就报错了!!

技术总结:

C++默认生成的拷贝构造函数,他的行为就是浅拷贝,他只会复制一个一摸一样的指针,并不会操作指针指向的东西。要想实现我们的逻辑需求,就要自定义拷贝构造函数,实现深拷贝。

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

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
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.mzph.cn/web/15718.shtml

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

相关文章

PostgreSQL基础(三):PostgreSQL的基础操作

文章目录 PostgreSQL的基础操作 一、用户操作 二、权限操作 三、操作任务

解决CLion调试时无法显示变量值的问题

1 问题描述 使用CLion的时候&#xff0c;调试时无法显示变量的值&#xff0c;例如&#xff1a; 图来自StackOverflow。 2 解决办法 可以尝试切换调试器解决&#xff0c;在Linux下&#xff0c;CLion支持GDB和LLDB&#xff0c;如果GDB不行&#xff0c;可以切换到LLDB。 切换方…

软件杯 题目: 基于深度学习的疲劳驾驶检测 深度学习

文章目录 0 前言1 课题背景2 实现目标3 当前市面上疲劳驾驶检测的方法4 相关数据集5 基于头部姿态的驾驶疲劳检测5.1 如何确定疲劳状态5.2 算法步骤5.3 打瞌睡判断 6 基于CNN与SVM的疲劳检测方法6.1 网络结构6.2 疲劳图像分类训练6.3 训练结果 7 最后 0 前言 &#x1f525; 优…

为什么单片机不能直接驱动继电器和电磁阀

文章是瑞生网转载&#xff0c;PDF格式文章下载&#xff1a; 为什么单片机不能直接驱动继电器和电磁阀.pdf: https://url83.ctfile.com/f/45573183-1247189072-10b6d1?p7526 (访问密码: 7526)

Science 基于尖峰时序编码的模拟神经触觉系统,可实现动态对象分类

快速处理和有效利用手与物体交互过程中产生的动态触觉信号&#xff08;例如触摸和抓握&#xff09;对于触觉探索和灵巧的物体操作至关重要。将电子皮肤&#xff08;e-skins&#xff09;推进到模仿自然触觉的水平&#xff0c;是恢复截肢者和瘫痪患者丧失的功能的可行解决方案&am…

实现地图上展示坐标时,不要全部展示、只展示几个距离相对较大marker点位,随着地图放大再全部展示出来。

比例尺级别地面分辨率 &#xff08;米/像素&#xff09;比例尺0156543.031&#xff1a;591658700.82178271.5151&#xff1a;295829350.4239135.75751&#xff1a;147914675.2319567.878751&#xff1a;73957337.649783.9393751&#xff1a;36978668.854891.9696881&#xff1a…

电机控制系列模块解析(22)—— 零矢量刹车

一、零矢量刹车 基本概念 逆变器通常采用三相桥式结构&#xff0c;包含六个功率开关元件&#xff08;如IGBT或MOSFET&#xff09;&#xff0c;分为上桥臂和下桥臂。每个桥臂由两个反并联的开关元件组成&#xff0c;上桥臂和下桥臂对应于电机三相绕组的正负端。正常工作时&…

【C++题解】1699 - 输出是2的倍数,但非3的倍数的数

问题&#xff1a;1699 - 输出是2的倍数&#xff0c;但非3的倍数的数 类型&#xff1a;循环 题目描述&#xff1a; 请从键盘读入一个整数 n&#xff0c;输出 1∼n 中所有是 2 的倍数&#xff0c;但非 3 的倍数的数&#xff0c;每行 1个。 比如&#xff0c;读入一个整数10 &…

Spring AI实战之二:Chat API基础知识大串讲(重要)

欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码)&#xff1a;https://github.com/zq2599/blog_demos Spring AI实战全系列链接 Spring AI实战之一&#xff1a;快速体验(OpenAI)Spring AI实战之二&#xff1a;Chat API基础知识大串讲(重要)SpringAIOllama三部曲…

Linux:进程地址空间、进程控制(一.进程创建、进程终止、进程等待)

上次介绍了环境变量&#xff1a;Linux&#xff1a;进程概念&#xff08;四.main函数的参数、环境变量及其相关操作&#xff09; 文章目录 1.程序地址空间知识点总结上述空间排布结构是在内存吗&#xff1f;&#xff08;进程地址空间引入&#xff09; 2.进程地址空间明确几个点进…

NDIS小端口驱动开发(三)

微型端口驱动程序处理来自过度驱动程序的发送请求&#xff0c;并发出接收指示。 在单个函数调用中&#xff0c;NDIS 微型端口驱动程序可以指示具有多个接收 NET_BUFFER_LIST 结构的链接列表。 微型端口驱动程序可以处理对每个NET_BUFFER_LIST结构上具有多个 NET_BUFFER 结构的多…

JAVA -- > 初识JAVA

初始JAVA 第一个JAVA程序详解 public class Main {public static void main(String[] args) {System.out.println("Hello world");} }1.public class Main: 类型,作为被public修饰的类,必须与文件名一致 2.public static 是JAVA中main函数准写法,记住该格式即可 …

Slash后台管理系统代码阅读笔记 如何实现环形统计图表卡片?

目前&#xff0c;工作台界面的上半部分已经基本梳理完毕了。 接下来&#xff0c;我们看看这个环形图卡片是怎么实现的&#xff1f; 具体代码如下&#xff1a; {/*图表卡片*/} <Row gutter{[16, 16]} className"mt-4" justify"center">{/*环形图表…

U盘引导盘制作Rufus v4.5.2180

软件介绍 Rufus小巧实用开源免费的U盘系统启动盘制作工具和格式化U盘的小工具&#xff0c;它可以快速将ISO镜像文件制作成可引导的USB启动安装盘&#xff0c;支持Windows或Linux启动&#xff0c;堪称写入镜像速度最快的U盘系统制作工具。 软件截图 更新日志 github.com/pbat…

嵌入式全栈开发学习笔记---C语言笔试复习大全24

目录 内存管理 内存分配 堆和栈的区别&#xff1f;&#xff08;面试重点&#xff09; 申请内存的函数 malloc realloc free gcc工具链 编译的过程&#xff08;面试重点&#xff09; 第一步&#xff0c;预处理&#xff1a; 第二步&#xff0c;编译&#xff1a; 第三…

解决LabVIEW通过OPC Server读取PLC地址时的错误180121602

在使用LabVIEW通过OPC Server读取PLC地址时&#xff0c;若遇到错误代码180121602&#xff0c;建议检查网络连接、OPC Server和PLC配置、用户权限及LabVIEW设置。确保网络畅通&#xff0c;正确配置OPC变量&#xff0c;取消缓冲设置以实时读取数据&#xff0c;并使用诊断工具验证…

2 使用香橙派AIpro报错 No module named ‘acllite utils‘

当使用jupyter运行香橙派的notebooks下面的案例的时候启动使用jupyter lab 然后自动跳转到jupyter页面。如下图: 这是自动跳转过来的。然后运行下面的包的导入后报错: 报错为No module named ‘acllite utils’,那么我们打开notebooks文件夹下面的start_notebooks.sh文件:…

【C++练级之路】【Lv.21】C++11——列表初始化和声明

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、列表初始化1.1 内置类型1.2 结构体或类1.3 容器 二、声明2.1 auto2.2 decltype2.3 nullptr 三、STL的…

从“反超”到“引领”,中国卫浴品牌凭何遥遥领先?

作者 | 曾响铃 文 | 响铃说 前不久&#xff0c;第28届中国国际厨房、卫浴设施展览会(以下简称“中国国际厨卫展”)在上海如期举行&#xff0c;就结果来说真的让人大开眼界。 冲水声比蚊子声更小的马桶、能化身无感交互平台的魔镜柜、可以语音交互的淋浴器&#xff0c;这些“…

Keli5烧写STM32程序时出现ST-LINK USB communication error错误(USB 通信错误)

1错误原图 2错误原因 前提驱动安装正确 原因1 usb接触不良&#xff08;极少出现&#xff09; 解决方法 更换USB线 还不行连下载器一起更换 原因2&#xff08;出现概率比较大&#xff09; 下载器的固件出现问题或下载器固件版本与Keli5的版本不匹配 解决方法 在Keli5的…