『C++成长记』拷贝构造函数

 🔥博客主页:小王又困了

📚系列专栏:C++

🌟人之为学,不日近则日退

❤️感谢大家点赞👍收藏⭐评论✍️

目录

一、拷贝构造函数

📒1.1拷贝构造函数的概念

📒1.2拷贝构造函数的写法

📒1.3编译器生成的拷贝构造

📒1.4拷贝构造函数的用法

📒1.5拷贝构造函数典型调用场景


一、拷贝构造函数

📒1.1拷贝构造函数的概念

     拷贝构造函数,是一种特殊的构造函数。这种构造函数由编译器用,用于基于同一类的其他对象的构建及初始化,也就是是创建对象的时候,用一个已存在的对象,去初始化待创建的对象。拷贝构造函数的形参必须是引用,但并不限制为const,一般普遍的会加上const限制。

例如,如果我们有一个名为Date的类,并且我们希望创建一个新的Date对象作为现有Date对象的副本,我们可以使用拷贝构造函数来实现这一点。在这种情况下,新的Date对象将具有与现有对象相同的属性值

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

Data d1;//定义一个日期类对象d1
Data d2(d1);//会去调用拷贝构造函数

拷贝构造函数是针对自定义类型的,自定义类型的对象在拷贝的时候,C++规定必须要调用拷贝构造函数。 

📒1.2拷贝构造函数的写法

在上面的概念中提到,拷贝构造函数的形参必须是引用,这是为什么呢?

🎀形参不是引用的情况:

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

Test()
{
    Date d1(2023, 11, 20);
    Date d2(d1);
}

     我们创建一个d2对象,把实参d1传递过去,然后用形参 接收,最后把形参 的值赋给this指针(this指针指向的是d2),到这里我们觉得一切正常,但是这段代码有很大的错误。

🗒️为什么错误

问题就在于,我们传参时没有使用引用,上面代码传参使用的是值传递,值传递形参是实参的一份临时拷贝,拷贝也就是要形参d实参d1有相同的属性值,所以还要调用拷贝构造,这里就会引发无穷递归调用。

 形参 在接收实参d1的时候,又要去调用拷贝构造来创建 ,这次调用拷贝构造,又会有一个形参 ,这个形参又需要调用拷贝构造才能创建,一直递归调用。为了避免出现这种无穷递归,编译器会自行检查,如果拷贝构造函数的形参是值传递,编译时会直接报错。

🎀形参是引用的情况:

     为了使代码不在无穷递归,拷贝构造函数的形参只能有一个,并且必须是类类型对象的引用。下面才是正确的拷贝构造函数:

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

这里形参 d1的别名,它两共用一块空间,此时就不会再去无穷无尽的调用拷贝构造。

📒1.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;
}

📒1.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);
    Stack s2(s1);
    return 0;
}

如上代码,定义了一个Stack类,栈中的成员变量都是内置类型。我们没有写它的拷贝构造函数,编译器默认生成的拷贝构造函数会对这三个成员变量都完成值拷贝(浅拷贝)。

🗒️浅拷贝:

     浅拷贝是创建一个新的对象,并把原有的对象属性值完整地拷贝过来。这种拷贝方式既包括了原始类型的值,也包括了引用类型的内存地址。对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。

     s2调用拷贝构造函数,Stack类中没有显示定义的拷贝构造函数,则调用编译器默认生成的拷贝构造函数,将s1的值拷贝到s2中,因此s1s2指向同一块内存空间。当程序退出,s1s2要销毁,s2先销毁,s2销毁时调用析构函数,已经将0x11223344这块空间释放了,但是s1并不知道,到s1销毁的时候,会将0x11223344这块空间再释放一次,一块内存空间多次释放,最终就会导致程序崩溃。

🗒️深拷贝:

     通过上面的分析可以看出,简单的浅拷贝不能满足栈的需求,因此,对于栈,我们需要自己写一个拷贝构造函数,来实现深拷贝。深拷贝是对对象具体内容进行复制,它会创建一个新的对象实例,并复制所有属性以及这些属性指向的动态分配的内存

//自己写的拷贝构造函数,实现深拷贝
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;
}

深浅拷贝的区别在于他们处理对象内存的方式不同。浅拷贝新旧对象还是共享同一块内存,改变其中一个,另一个也会受影响。而深拷贝则会复制出一个全新的对象实例,新对象跟原对象不共享内存,两者操作互不影响。因此在某些情况下,浅拷贝可能会使旧对象和新对象产生相互影响,这可能会导致数据的不一致。在这种情况下,你可能需要自定义深拷贝构造函数来创建一个新的、独立的对象实例。

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

📒1.5拷贝构造函数典型调用场景

  • 使用已存在对象创建新对象
  • 函数参数类型为类类型对象
  • 函数返回值类型为类类型对象
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;
}

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


🎁结语: 

     本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Java项目-瑞吉外卖Day6

导入用户地址功能&#xff0c;为用户添加地址&#xff1a; 添加AddressBook实体类&#xff0c;创建相关service&#xff0c;mapper&#xff0c;serviceImpl&#xff0c;controller类。 controller类直接使用的资料提供的代码。 实现菜品展示移动端开发&#xff1a; 看到前端发…

添加,更换和删除 vSphere License

目录 1. 删除 License2. 添加 License&#xff08;1&#xff09;输入许可证密钥&#xff08;2&#xff09;编辑许可证名称&#xff08;3&#xff09;确认许可证信息 3. 分配/更换 License&#xff08;1&#xff09;为 vCenter Server 分配 License&#xff08;2&#xff09;为 …

Android : 序列化 Parcelable 简单应用

1.Parcelable 介绍 Parcelable 是 Android 提供的一个序列化接口&#xff0c;用于将数据写入 Parcel&#xff0c;以及从 Parcel 中读取数据。一个类只要实现了这个接口&#xff0c;该类的对象就可以被序列化&#xff0c;主要用于 IPC&#xff08;进程间通信&#xff09;、Bind…

产品经理之如何编写竞品分析(医疗HIS系统管理详细案例模板)

目录 一.项目周期 二.竞品分析的目的 三.竞品分析包含的维度 四.如何选择竞品 五.竞品画布 六.案例模板 一.项目周期 在整个项目的周期&#xff0c;产品经理所做的事情主要在项目前期做市场分析、需求调研等&#xff0c;下面一张图概况了整个项目周期产品经理、开发工程师…

网络安全——Iptables防DDoS攻击实验

一、实验目的要求&#xff1a; 二、实验设备与环境&#xff1a; 三、实验原理&#xff1a; 四、实验步骤&#xff1a; 五、实验现象、结果记录及整理&#xff1a; 六、分析讨论与思考题解答&#xff1a; 一、实验目的要求&#xff1a; 1、掌握常见DDoS攻击SYN Flood的攻击…

gdb本地调试版本移植至ARM-Linux系统

移植ncurses库 本文使用的ncurses版本为ncurses-5.9.tar.gz 下载地址&#xff1a;https://ftp.gnu.org/gnu/ncurses/ncurses-5.9.tar.gz 1. 将ncurses压缩包拷贝至Linux主机或使用wget命令下载并解压 tar-zxvf ncurses-5.9.tar.gz 2. 解压后进入到ncurses-5.9目录…

解决员工安全隐患的终极方案!迅软DSE答疑员工终端安全管控策略揭秘!

企业终端安全管控对于企事业单位来说至关重要。迅软DSE终端安全系统提供了丰富的终端安全桌面管理策略&#xff0c;可以对终端用户的上网行为和终端操作行为进行管理和控制&#xff0c;从而实现桌面终端的标准化管理&#xff0c;解决终端安全管理问题&#xff0c;并提高员工工作…

阿里云SLB的使用总结

一、什么是SLB 实现k8s的服务service的一种推荐方式&#xff0c;也是服务上云后&#xff0c;替代LVS的一个必选产品。 那么它有什么作用呢&#xff1f; 1、负载均衡&#xff0c;是它与生俱来的。可以配置多个服务器组&#xff1a;包括虚拟服务器组、默认服务器组、主备服务器…

小程序使用Nodejs作为服务端,Nodejs与与MYSQL数据库相连

小程序使用Nodejs作为服务端,Nodejs与与MYSQL数据库相连 一、搭建环境二、配置Nodejs三、与小程序交互四、跨域处理/报错处理五、nodejs连接mysql数据库六、微信小程序连接nodejs报错七、小程序成功与服务端相连,且能操作数据库一、搭建环境 新建空文件夹:Win + R进入cmd命令…

C++STL的list模拟实现

文章目录 前言 list实现push_back迭代器(重点)普通迭代器const迭代器 inserterase析构函数构造函数拷贝构造赋值 vector和list的区别 前言 要实现STL的list, 首先我们还得看一下list的源码。 我们看到这么一个东西&#xff0c;我们知道C兼容C&#xff0c;可以用struct来创建一…

Quartus II + Modelsim 脚本仿真

软件版本&#xff1a;Intel Quartus Prime Design Suite: 23.2 方式参考附件Intel 官方文档&#xff1a;Questa*-Intel FPGA Edition Quick-Start: Intel Quartus Prime Pro Edition 第1步&#xff0c;创建一个ram ip&#xff0c;并形成一个例化的top层ip 第2步&#xff0c;自…

独立完成软件的功能的测试(2)

独立完成软件的功能的测试&#xff08;2&#xff09; &#xff08;12.13&#xff09; 1. 对穷举场景设计测试点&#xff08;等价类划分法&#xff09; 等价类划分法的概念&#xff1a; 说明&#xff1a;数据有共同特征&#xff0c;成功失败分类&#xff1a; 有效&#xff1a…

FPGA使用乘法的方式

FPGA使用乘法的方式 方法一:直接使用乘法符“*” 源代码 module multiply(input [7:0] a,input [7:0] b,output wire [15:0] result);(*use_dsp48 = "yes"*) wire [15:0] result;assign result = a*b; endmodule仿真代码 module multiply_tb();reg [7:0] a; re…

大象慧云:从设立分部到迁移总部 与贵阳贵安共筑税务数字化未来

近年来&#xff0c;贵阳贵安着力提升政务服务水平&#xff0c;通过擦亮“贵人服务”品牌&#xff0c;持续优化营商环境。在这样的环境下&#xff0c;再加上“大数据基因”&#xff0c;对于希望在大数据领域大展拳脚的企业来说&#xff0c;贵阳贵安无疑成为了一个极具吸引力的选…

MySQL笔记-第11章_数据处理之增删改

视频链接&#xff1a;【MySQL数据库入门到大牛&#xff0c;mysql安装到优化&#xff0c;百科全书级&#xff0c;全网天花板】 文章目录 第11章_数据处理之增删改1. 插入数据1.1 实际问题1.2 方式1&#xff1a;VALUES的方式添加1.3 方式2&#xff1a;将查询结果插入到表中 2. 更…

C语言—每日选择题—Day46

第一题 1. 下列程序段的输出结果是&#xff08;&#xff09; #include <stdio.h> int main() {int x 1,a 0,b 0;switch(x) {case 0: b;case 1: a;case 2: a;b;}printf("a%d,b%d\n", a, b);return 0; } A&#xff1a;a2,b1 B&#xff1a;a1,b1 C&#xf…

CGAL的3D Alpha Wrapping

1、介绍 几何建模和处理中的各种任务都需要将三维对象表示为有效的曲面网格&#xff0c;其中“有效”指的是不透水、无交叉、可定向和2流形的网格。这样的表示提供了内部/外部和测地线邻域的定义良好的概念。 3D数据通常是通过测量和重建获得的&#xff0c;由人类设计&#xff…

深入理解网络 I/O 多路复用:Epoll

&#x1f52d; 嗨&#xff0c;您好 &#x1f44b; 我是 vnjohn&#xff0c;在互联网企业担任 Java 开发&#xff0c;CSDN 优质创作者 &#x1f4d6; 推荐专栏&#xff1a;Spring、MySQL、Nacos、Java&#xff0c;后续其他专栏会持续优化更新迭代 &#x1f332;文章所在专栏&…

【FPGA】综合设计练习题目

前言 这是作者这学期上的数电实验期末大作业的题目&#xff0c;综合性还是十分强的&#xff0c;根据组号作者是需要做“4、篮球比赛计分器”&#xff0c;相关代码会在之后一篇发出来&#xff0c;这篇文章用于记录练习题目&#xff0c;说不定以后有兴趣或者有时间了回来做做。 …

随机拆分文件夹划分训练验证集

import os from shutil import copy, rmtree import randomdef mk_file(file_path: str):if os