一文详解C++拷贝构造函数

文章目录

  • 引入
  • 一、什么是拷贝构造函数?
  • 二、什么情况下使用拷贝构造函数?
  • 三、使用拷贝构造函数需要注意什么?
  • 四、深拷贝和浅拷贝
    • 浅拷贝
    • 深拷贝


引入

在现实生活中,可能存在一个与你一样的自己,我们称其为双胞胎。
在这里插入图片描述
相当于就是把自己复制一遍,内置类型如int,char这些要实现复制很简单只需要:

int a = 10;
int b = a;

那在创建类对象时,如何创建一个与已存在对象一摸一样的新对象呢?
答案是拷贝构造。

class Date
{
public:
    Date(int year = 2024, int month = 1, int day = 25)//全缺省构造函数
    {
        _year = year;
        _month = month;
        _day = day;
    }
    Date(const Date& d)//拷贝构造
    {
        _year = d._year;
        _month = d._month;
        _day = d._day;
    }
    ~Date()//析构函数
    {
        //cout << "~Time()" << endl;
    }
    void show()//普通函数
    {
        cout << _year << "-" << _month << "-" << _day << endl;
    }
private:
    int _year;
    int _month;
    int _day;
};


int main()
{
    Date a(2000, 1, 1);
    Date b = a; //注意这里的对象初始化要调用拷贝构造函数,而非赋值
    b.show();
    return 0;
}

在这里插入图片描述
从以上代码可以看出系统为对象 B 分配了内存并完成了与对象 A 的复制过程。就类对象而言,相同类型的类对象是通过拷贝构造函数来完成整个复制过程的。
具体过程是当编译器执行到Date b = a这一行代码时会自动调用b的拷贝构造函数,拷贝构造的参数为对象a,并在函数中完成赋值操作。


一、什么是拷贝构造函数?

同一个类的对象在内存中有完全相同的结构,如果作为一个整体进行复制或称拷贝是完全可行的。==这个拷贝过程只需要拷贝数据成员,==而函数成员是共用的(只有一份拷贝)。在建立对象时可用同一类的另一个对象来初始化该对象的存储空间,这时所用的构造函数称为拷贝构造函数。

拷贝构造函数本质上来说也是构造函数,是构造函数的一个重载。

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


二、什么情况下使用拷贝构造函数?

  • 使用已存在对象创建新对象(用旧对象去初始化新对象)
  • 函数参数类型为类类型对象(从实参传递给形参的过程,是用实参去构造形参)
  • 函数返回值类型为类类型对象(用局部对象去构造临时对象调用拷贝构造)
class Date
{
public:
    Date(int year = 2024, int month = 1, int day = 25)//全缺省构造函数
    {
        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;
}

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


三、使用拷贝构造函数需要注意什么?

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。
  • 上面我们知道,实参去构造形参的时候会调用拷贝构造的,如果构造函数没有用引用,那么在调用构造函数进行实参构造形参时就会调用构造函数,调用构造函数就又得用实参构造形参,又有得调用构造函数,这样下去就会导致一直调用构造函数,引发无穷递归调用。
    在这里插入图片描述
  1. 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

四、深拷贝和浅拷贝

浅拷贝

浅拷贝就是在对象复制时,仅仅只对对象中的数据成员进行简单的赋值,默认的拷贝构造就是浅拷贝,虽然大多数情况下浅拷贝就已经够用了,但如果出现资源申请的情况(申请内存),浅拷贝就会出现一些问题。

以下程序是我们自定义实现了一个栈,其中有个成员变量是指针DataType* _array;,在构造函数中我们在堆区申请了内存,让指针_array指向了内块内存。我们在主函数中写了Stack s2(s1);利用构造函数构造了一个s2,但默认的构造函数时是浅拷贝,在s2的构造函数中相当于有这么一句代码_array=s1._array;。这造成了两个指针指向了同一块内存空间,但这俩指针在不同的对象s1,s2中,在俩对象析构的时候会导致同一块内存空间释放两次,导致错误。

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

在这里插入图片描述


深拷贝

为了解决上述问题 我们就需要给s2中的_array也开辟和s1中的_array一样大小的空间,所以我们就需要深拷贝
在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:
手动写一个深拷贝的拷贝构造,各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

    Stack(const Stack& s)
    {
        //s1空间有多大就申请多大的空间
        _array= (DataType*)malloc(s._capacity * sizeof(DataType));
        if (_array == NULL)
        {
            exit(-1);
        }
        for (int i = 0; i < s._size; i++)
        {
            _array[i] = s._array[i];
            _size++;
        }
        _capacity = s._capacity;
    }

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

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

相关文章

5|领域建模实践(上):怎样既准确又深刻地理解业务知识?

上节课咱们完成了事件风暴&#xff0c;梳理了系统的行为需求。但你可能也发现了&#xff0c;其实还有些微妙的业务概念还没有澄清&#xff0c;这就要靠领域建模来完成了。 建立领域模型是 DDD 的核心。要建好领域建模&#xff0c;需要理论和实践相结合。由于我们的模型有一定的…

CSC签证费报销的相关规定及要求-主要国家签证费报销凭据

国家留学基金委&#xff08;CSC&#xff09;派出流程很多是在留学服务机构办理&#xff0c;即北京教育部留学服务中心及教育部出国人员上海集训部&#xff0c;其中含签证费报销。本篇知识人网小编以上海集训部为例&#xff0c;详细解读一下签证费报销的相关规定及要求&#xff…

sql 行转列 日周月 图表统计

目录 目录 需求 准备 月 分析 按月分组 行转列 错误版本 正确版本 日 分析 行转列 周 分析 按周分组 行转列 本年 需求 页面有三个按钮 日周月&#xff0c;统计一周中每天(日)&#xff0c;一月中每周(周)&#xff0c;一年中每月(月)&#xff0c;设备台数 点…

Linux中断 -- 中断路由、优先级、数据和标识

目录 1.中断路由 2.中断优先级 3.中断平衡 4.Linux内核中重要的数据结构 5.中断标识 承前文&#xff0c;本文从中断路由、优先级、数据结构和标识意义等方面对Linux内核中断进行一步的解析。 1.中断路由 Aset affinity flow GIC文中有提到SPI类型中断的路由控制器寄存器为…

Leetcode—114. 二叉树展开为链表【中等】

2023每日刷题&#xff08;九十八&#xff09; Leetcode—114. 二叉树展开为链表 Morris-like算法思想 可以发现展开的顺序其实就是二叉树的先序遍历。算法和 94 题中序遍历的 Morris 算法有些神似&#xff0c;我们需要两步完成这道题。 将左子树插入到右子树的地方将原来的右…

Java - OpenSSL与国密OpenSSL

文章目录 一、定义 OpenSSL&#xff1a;OpenSSL是一个开放源代码的SSL/TLS协议实现&#xff0c;也是一个功能丰富的加密库&#xff0c;提供了各种主要的加密算法、常用的密钥和证书封装管理功能以及SSL协议。它被广泛应用于Web服务器、电子邮件服务器、VPN等网络应用中&#x…

线性表--栈

1.什么是栈&#xff1f; 栈是一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除 操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出的原则。 压栈&#xff1a;栈的插入操作叫做进栈/压栈/入栈&#xff…

YOLOv5改进 | Conv篇 | 在线重参数化卷积OREPA助力二次创新(提高推理速度 + FPS)

一、本文介绍 本文给大家带来的改进机制是一种重参数化的卷积模块OREPA,这种重参数化模块非常适合用于二次创新,我们可以将其替换网络中的其它卷积模块可以不影响推理速度的同时让模型学习到更多的特征。OREPA是通过在线卷积重参数化(Online Convolutional Re-parameteriza…

TensorFlow2实战-系列教程3:猫狗识别1

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 1、项目介绍 基本流程&#xff1a; 数据预处理&#xff1a;图像数据处理&#xff0c…

Spring 的执行流程以及 Bean 的作用域和生命周期

文章目录 Bean 的作用域更改作用域的方式singletonprototype Spring 执行流程Bean 的生命周期 Bean 的作用域 Spring 容器在初始化⼀个 Bean 的实例时&#xff0c;同时会指定该实例的作用域。Bean 有6种作用域 singleton&#xff1a;单例作用域prototype&#xff1a;原型作用域…

Hadoop-MapReduce-MRAppMaster启动篇

一、源码下载 下面是hadoop官方源码下载地址&#xff0c;我下载的是hadoop-3.2.4&#xff0c;那就一起来看下吧 Index of /dist/hadoop/core 二、上下文 在上一篇<Hadoop-MapReduce-源码跟读-客户端篇>中已经将到&#xff1a;作业提交到ResourceManager&#xff0c;那…

首发:2024全球DAO组织发展研究

作者&#xff0c;张群&#xff08;专注DAO及区块链应用研究&#xff0c;赛联区块链教育首席讲师&#xff0c;工信部赛迪特邀资深专家&#xff0c;CSDN认证业界专家&#xff0c;微软认证专家&#xff0c;多家企业区块链产品顾问&#xff09; DAO&#xff08;去中心化自治组织&am…

adb测试冷启动和热启动 Permission Denial解决

先清理日志 adb shell logcat -c 打开手机模拟器中的去哪儿网&#xff0c;然后日志找到包名和MainActivity adb shell logcat |grep Main com.Qunar/com.mqunar.atom.alexhome.ui.activity.MainActivity 把手机模拟器的去哪儿的进程给杀掉 执行 命令 adb shell am start -W…

TensorFlow2实战-系列教程1:回归问题预测

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 1、环境测试 import tensorflow as tf import numpy as np tf.__version__打印结果 ‘…

深入理解Redis:如何设置缓存数据的过期时间及其背后的机制

目录 Redis 给缓存数据设置过期时间 Redis是如何判断数据是否过期的呢&#xff1f; 过期的数据的删除策略 Redis 内存淘汰机制 Redis 给缓存数据设置过期时间 一般情况下&#xff0c;我们设置保存的缓存数据的时候都会设置一个过期时间。为什么呢&#xff1f; 因为内存是有…

4小时精通MyBatisPlus框架

目录 1.介绍 2.快速入门 2.1.环境准备 2.2.快速开始 2.2.1引入依赖 2.2.2.定义Mapper ​编辑 2.2.3.测试 2.3.常见注解 ​编辑 2.3.1.TableName 2.3.2.TableId 2.3.3.TableField 2.4.常见配置 3.核心功能 3.1.条件构造器 3.1.1.QueryWrapper 3.1.2.UpdateWra…

Redis(八)哨兵机制(sentinel)

文章目录 哨兵机制案例认识异常 哨兵运行流程及选举原理主观下线(Subjectively Down)ODown客观下线(Objectively Down)选举出领导者哨兵选出新master过程 哨兵使用建议 哨兵机制 吹哨人巡查监控后台master主机是否故障&#xff0c;如果故障了根据投票数自动将某一个从库转换为新…

Java 基础知识-File类

大家好我是苏麟 , 今天聊聊File . 资料来自黑马程序员 File类 java.io.File 类是文件和目录路径名的抽象表示&#xff0c;主要用于文件和目录的创建、查找和删除等操作。 构造方法 public File(String pathname) &#xff1a;通过将给定的路径名字符串转换为抽象路径名来创建…

盘古信息IMS OS 数垒制造操作系统+ 产品及生态部正式营运

启新址吉祥如意&#xff0c;登高楼再谱新篇。2024年1月22日&#xff0c;广东盘古信息科技股份有限公司新办公楼层正式投入使用并举行了揭牌仪式&#xff0c;以崭新的面貌、奋进的姿态开启全新篇章。 盘古信息总部位于东莞市南信产业园&#xff0c;现根据公司战略发展需求、赋能…

【双目】基于findChessboardCorners的双目精度评估,可以直接使用

1. 基于findChessboardCorners的双目精度评估 原理&#xff1a; 代码&#xff1a; #include <iostream> #include <opencv2/opencv.hpp>using namespace std; using namespace cv;int main() {// 加载图像auto srcimage imread("/home/oem/data/steroe_p…