【C++杂货铺】拷贝构造函数

在这里插入图片描述

📖定义
拷贝构造函数是构造函数的一个重载,它的本质还是构造函数,那就意味着,只有在创建对象的时候,编译器才会自动调用它,那他和普通的构造函数有什么区别呢?

拷贝构造函数,是创建对象的时候,用一个已存在的对象,去初始化待创建的对象。简单来说,就是在我们创建对象的时候,希望创建出来的对象,和一个已存在的对象一模一样,此时就应该用拷贝构造函数,而不是普通的构造函数。拷贝构造函数有一点类似于克隆技术。
在这里插入图片描述

Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//会去调用拷贝构造函数int a = 10;
int b = a;//不会调用拷贝构造

上面代码,首先定义了一个日期类对象d1,接着想创建第二个日期类对象d2,并且希望d2d1一模一样,也就是用d1去克隆出d2d2相当于是d1的一份拷贝。所以在创建d2对象的时候,参数列表直接传递了d1

小Tips:拷贝构造函数是针对自定义类型的,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数。内置类型不涉及拷贝构造函数,如上,用a去创建b,是由编译器直接把a所表示的空间中的内容直接拷贝到b所表示的空间,并不涉及拷贝构造函数。

📖拷贝构造函数的错误写法
有了上面的分析,可能很多朋友会觉得,那我直接在类里面再写一个构造函数,把它的形参设置成日期类对象,不就行了嘛,于是便得到了下面的代码:

Data(Data d)//错误的拷贝构造
{_year = d._year;_month = d._month;_day = d._day;
}

是不是觉得很简单?创建d2对象的时候,实参把d1传过来,然后用d接收,最后再把d的所有值赋值给this指针(当前this指针就指向d2),这一切堪称完美,但是我想告诉你,这种写法是大错特错的。

📖为什么是错的
问题出现在传参,就是实参d1传递给形参d的时候,上面代码中的形参d,既不是指针也不是引用,说明是值传递,值传递就意味着,形参d是实参d1的一份拷贝,注意:是拷贝,就是说,形参d要和实参d1一模一样,怎么才能让dd1一摸一样?调用拷贝构造函数呀。
在这里插入图片描述

形参d在接收实参d1的时候,又要去调用拷贝构造来创建d,这次调用拷贝构造,又会有一个形参d,这个形参d又需要调用拷贝构造才能创建,相信到这里,小伙伴们已经看出问题所在了———无穷递归,形参在接收的时候,会无穷无尽的去调用拷贝构造函数,就像套娃一样。
在这里插入图片描述
为了避免出现这种无穷递归,编译器会自行检查,如果拷贝构造函数的形参是值传递,编译时会直接报错。

在这里插入图片描述

📖必须是引用
为了打破上面的魔咒,拷贝构造函数的形参只能有一个,并且必须是类类型对象的引用。下面才是正确的拷贝构造函数:

Data(Data& d)//正确的拷贝构造
{_year = d._year;_month = d._month;_day = d._day;
}
Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);//

此时创建d2的时候,传递d1调用拷贝构造函数,形参d是一个日期类的引用,因为引用时区别名,意味着dd1的一个别名,此时就不会再去无穷无尽的调用拷贝构造啦。

📖建议加const
因为存在用一个const对象去初始化创建一个新对象这种场景,所以建议在拷贝构造函数的形参前面加上const,此时普通的对象能用,const对象也能用。

Data(const Data& d)//正确的拷贝构造
{_year = d._year;_month = d._month;_day = d._day;
}
const Data d1(2023, 7, 20);//定义一个日期类对象d1
Data d2(d1);

📖编译器生成的拷贝构造干了什么?
上一节提到,拷贝构造是一种默认成员函数,我们不写编译器会自动生成。编译器生成的默认拷贝构造函数,对内置类型按照字节方式直接拷贝(也叫值拷贝浅拷贝),对自定义类型是调用其拷贝构造函数完成拷贝

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

在这里插入图片描述

在这里插入图片描述
📖什么是浅拷贝
上面提到,编译器生成的拷贝构造函数,会对内置类型完成浅拷贝,浅拷贝就是以字节的方式,把一个字节里的内容直接拷贝到另一个字节中。
在这里插入图片描述

📖拷贝构造函数可以不写嘛?
通过上面的分析可以得出:编译器自己生成的构造函数对内置类型和自定义类型都做了处理。那是不是意味着我们就可以不写拷贝构造函数了呢?答案是否定的,对于日期类,我们确实可以不写,用编译器自己生成的,但是对于一些需要深拷贝的对象,构造函数是非写不可的。栈就是一个典型的需要我们自己写构造函数的例子

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

上面定义了一个栈类Stack,我们没有写它的拷贝构造函数,编译器会自动生成一个默认的拷贝构造函数,栈中的成员变量都是内置类型,默认的拷贝构造函数会对这三个成员变量都完成值拷贝(浅拷贝)。
在这里插入图片描述
此时浅拷贝的问题在于:对象s1和对象s2中的_array存的是同一块空间的地址,他俩指向了同一块空间,当程序退出,往s1s2中的任意一个对象push值,另一个也会跟着改变。s1s2要销毁,s2先销毁,s2销毁时调用析构函数,已经将0X11223344这块空间释放了,但是s1并不知道,到s1销毁的时候,会将0X11223344这块空间再释放一次,一块内存空间多次释放,最终就会导致程序崩溃。

📖深拷贝
通过上面的分析可以看出,简单的浅拷贝不能满足栈的需求,因此,对于栈,我们需要自己写一个拷贝构造函数,来实现深拷贝,深拷贝就是去堆上重新申请一块空间,把s1_array指向的空间中的内容,拷贝到新申请的空间,再让s2中的_array指向该空间。
在这里插入图片描述

//自己写的拷贝构造函数,实现深拷贝
Stack(const Stack& st)
{DataType* tmp = (DataType*)malloc(sizeof(DataType) * st._capacity);if (nullptr == tmp){perror("malloc申请空间失败");return;}memcpy(tmp, st._array, sizeof(DataType) * st._size);_array = tmp;_size = st._size;_capacity = st._capacity;
}

📖总结:
类中如果没有涉及资源申请时,拷贝构造函数写不写都可以;一旦涉及到资源申请时,拷贝构造函数是一定要写的,否则就是浅拷贝,最终析构的时候,就会释放多次,造成程序崩溃。

📖拷贝构造函数典型的调用场景:

  • 使用已存在对象创建新对象。
  • 函数参数类型为类类型对象。
  • 函数返回值为类类型对象。
class Data
{
public:Data(int year = 1, int month = 1, int day = 1){cout << "调用构造函数:" << this << endl;cout << endl;_year = year;_month = month;_day = day;}Data(const Data& d){cout << "调用拷贝构造:" << this << endl;cout << endl;_year = d._year;_month = d._month;_day = d._day;}~Data(){cout << "~Data()" << this << endl;cout << endl;}
private:int _year;int _month;int _day;//可以不用写析构,因为全是自定义类型,并且没有动态申请的空间,这三个成员变量会随着对象生命周期的结束而自动销毁
};
Data Text(Data x)
{Data tmp;return tmp;
}int main()
{Data d1(2023, 4, 29);Text(d1);return 0;
}

在这里插入图片描述
📖总结:
自定义类型在传参的时候,形参最好用引用来接收,这样可以避免调用拷贝构造函数,尤其是深拷贝的时候,会大大的提高效率,函数返回时,如果返回的对象在函数栈帧销毁后还在,最好也用引用返回。


🎁结语:
 今天的分享到这里就结束啦!如果觉得文章还不错的话,可以三连支持一下,您的支持就是春人前进的动力!
在这里插入图片描述

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

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

相关文章

【数学建模】为什么存在最优策略?

一、说明 在进行优化回归过程&#xff0c;首先要看看是否存在最优策略&#xff1f; 在有限马尔可夫决策过程 &#xff08;MDP&#xff09; 中&#xff0c;最优策略被定义为同时最大化所有状态值的策略。换句话说&#xff0c;如果存在最优策略&#xff0c;则最大化状态 s 值的策…

内存函数及其模拟实现

身体扛不住的时候&#xff0c;意志会带你杀出重围 文章目录 一、memcpy函数 函数介绍 模拟实现 二、memmove函数 函数介绍 模拟实现 三、memset函数 函数介绍 模拟实现 大家好&#xff0c;我是纪宁。这篇文章给大家介绍C语言中常见的内存处理函数。 一、memcpy函数 …

20.matlab数据分析极限(matlab程序)

1.简述 计算极限 MATLAB提供计算极限的limit函数。在其最基本的形式中&#xff0c;limit函数将表达式作为参数&#xff0c;并在独立变量为零时找到表达式的极限。 例如&#xff0c;要计算函数f(x)(x^3 5)/(x^4 7)的极限&#xff0c;因为x趋向于零。 syms xlimit((x^3 5)/…

day42-servlet下拉查询/单例模式

0目录 1.Servlet实现下拉查询&#xff08;两表&#xff09; 2.单例模式 1.实战 1.1 创建工程&#xff0c;准备环境... 1.2 接口 1.3 重写方法 1.4 servlet 1.5 list.jsp list.jsp详解 2.单例模式 2.1 饿汉模式&#xff1a;在程序加载时直接创建对象&#…

学习系统编程No.32【线程互斥实战】

引言&#xff1a; 北京时间&#xff1a;2023/7/19/15:22&#xff0c;昨天更新完博客&#xff0c;和舍友下了一会棋&#xff0c;快乐就是这么简单&#xff0c;哈哈哈&#xff01;总体来说&#xff0c;摆烂程度得到一定的改善&#xff0c;想要达到以前的水准&#xff0c;需要一定…

UTM 4.3 发布:在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS

UTM 4.3 发布&#xff1a;在 macOS 上优雅的使用 QEMU 虚拟化 Windows、Linux 和 macOS 在 iOS 中虚拟化 Windows、Linux 和 Unix 请访问原文链接&#xff1a;https://sysin.org/blog/utm-4/&#xff0c;查看最新版。原创作品&#xff0c;转载请保留出处。 作者主页&#xf…

C语言:杨氏矩阵中查找某数(时间复杂度小于O(N))

题目&#xff1a; 有一个数字矩阵&#xff08;二维数组&#xff09;&#xff0c; 矩阵的每行从左到右是递增的&#xff0c;矩阵从上到下是递增的&#xff0c; 请编写程序在这样的矩阵中查找某个数字是否存在&#xff0c; 要求&#xff1a;时间复杂度小于O(N)。 思路&#xff1…

《零基础入门学习Python》第057讲:论一只爬虫的自我修养5:正则表达式

如果你在课后有勤加练习&#xff0c;那么你对于字符串的查找应该是已经深恶痛绝了&#xff0c;你发现下载一个网页是很容易的&#xff0c;但是要在网页中查找到你需要的内容&#xff0c;那就是困难的&#xff0c;你发现字符串查找并没有你想象的那么简单&#xff0c;并不是说直…

(已解决)RuntimeError: Java gateway process exited before sending its port number

今天用Pycharm远程使用pysaprk解释器时&#xff0c;跑代码出现了这个错误&#xff1a; RuntimeError: Java gateway process exited before sending its port number 找了好多博客都没解决问题&#xff0c;有说重装spark的&#xff0c;有说本地配Java_home的&#xff0c;后面我…

leetcode 47. 全排列 II

2023.7.23 这道题是上一题全排列 的一个升级版。 唯一区别就是需要增加一个树层去重的操作&#xff0c;因为数组nums中允许有重复的元素了&#xff0c;而上一题没有重复元素。 下面看代码&#xff1a; class Solution { public:vector<vector<int>> ans;vector<…

如何评测一个大语言模型?

编者按&#xff1a;大型语言模型&#xff08;Large language models, LLMs&#xff09;因其在学术界和工业界展现出前所未有的性能而备受青睐。随着 LLMs 在研究和实际应用中被广泛使用&#xff0c;对其进行有效评测变得愈发重要。近期已有多篇论文围绕大模型的评测进行研究&am…

RocketMQ教程-(4)-领域模型-消费者分组ConsumerGroup

定义​ 消费者分组是 Apache RocketMQ 系统中承载多个消费行为一致的消费者的负载均衡分组。 和消费者不同&#xff0c;消费者分组并不是运行实体&#xff0c;而是一个逻辑资源。在 Apache RocketMQ 中&#xff0c;通过消费者分组内初始化多个消费者实现消费性能的水平扩展以…

【云原生】Docker网络及Cgroup资源控制

一、Docker网络 1.docker网络实现原理 Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为Container-IP&#xff0c;同时Docker网桥是每个容器的默认网关。…

微信小程序——页面跳转方法和场景用法总结

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

如何理解spring cloud 和 spring cloud Alibaba

Spring Cloud是一个基于Spring Framework构建的用于开发和构建分布式系统的开源框架。它提供了一系列的工具和组件&#xff0c;用于帮助开发者快速构建、部署和管理微服务架构。Spring Cloud提供了包括服务发现、配置管理、负载均衡、断路器等在内的众多功能。 Spring Cloud A…

【JVM】JVM执行流程 JVM类加载 垃圾回收机制等

目录 &#x1f337;1、JVM是什么&#xff1f; &#x1f337;2、JVM的执行流程&#xff08;能够描述数据区5部分&#xff09; &#x1f337;3、JVM类加载过程 &#x1f337;4、双亲委派机制&#xff1a;描述类加载的过程 问题1&#xff1a;类加载器 问题2&#xff1a;什么…

支付宝原生小程序组件与父级传递数据(微信小程序基本一样)

1. 声明组件 在对应的目录下,右击点击 新建小程序,之后会生成对应的文件 2. 子组件 Component({data: {colorList: [#165FF6, #3D16F6,

一元多项式的表示及相加

实现思路&#xff1a; 通过链表实现&#xff0c;会更为简单直观。用链表中的每个结点表示多项式中的每一项&#xff0c;多项式每一项都是由数据域&#xff08;包含系数和指数&#xff09;和指针域构成的&#xff0c;所以在定义表示结点的结构体时&#xff0c;可如下所示进行定义…

FFMPEG android mac 编译 支持DASH/OPENSSL问题汇总

一 下载源码 FFMPEG https://github.com/FFmpeg/FFmpeg/tree/release/4.3 二 编写脚本 支持https 就必须添加open SSL 的编译 具体可以查看我的另一个关于open SSL 的 然后我们配置的一些路径 涉及 ndk 的一定要查看你用的ndk 版本 是否存在这些路径这是第一步 然后如果支…

python爬虫入门

基础回顾 使用函数, 先导入, 直接点方法名使用 import math m math.log10(100) print(m)python 交互模式 input输入示例 age int(input("请输入年龄")) age 1 print(age)if else 的使用 和java一样, 只是不加括号, else if 阉割成了 elif 与或非 java : &am…