c++ 拷贝构造

news2025/1/12 4:00:25

我们思考一下这个问题:

观察以下代码,在运行的时候会崩溃

想一想为什么

#include<iostream>
using namespace std;

//栈类
typedef int DataType;
class Stack
{
public:
   //默认构造:
   
   Stack(size_t capacity = 3)
   {
   	_array = (DataType*)malloc(sizeof(DataType) * capacity);
   	if (NULL == _array)
   	{
   		perror("malloc 申请空间失败!!!");
   		return;
   	}

   	_capacity = capacity;
   	_size = 0;

   }


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

   //析构
   ~Stack()
   {
   	cout << "~Stack()" << endl;	
   	free(_array);
   	_array = nullptr;
   	_size = _capacity = 0;
   }

private:
   DataType* _array;
   int _capacity;
   int _size;

};
   	

void func1(Date d)
{
   d.Print();
}


void func2(Stack s)
{
   
} 


int main()
{


   Stack s1;

   func2(s1);

   return 0;
}

在这里插入图片描述
为什么这种情况下编译的时候会报错?
因为你这里有一个主函数,主函数里面有一个栈类型的变量s1,然后呢这个主函数中我们使用了一个名为func2的函数。这个时候编译器就又会去建立一个func2函数的栈帧
这个时候要进行传参。
传值方式的传参是一种值的拷贝(或者叫做浅拷贝),意思就是把S1这个空间里的值直接拷贝到 func2这个函数栈帧中。
但是这个时候我们要注意。
我们在主函数的那个S1里面的成员变量_a是一个指针。指向了我们向内存申请的空间。如果我们把这个成员变量_a的值直接拷贝到func2函数中。那么func2函数中也会存取一份指针变量_a的所存的内容(即我们向内存所申请空间的地址)。
由于这是c++,当我们调用了func 2之后,在出这个函数的时候,它会自动调用析构函数。而析构函数会自动释放指针变量所指的那个空间
于是乎_a所指向的那个空间被我们释放掉了,当我们回到主函数的时候,我们使用完s1结束的时候会再次调用析构函数,这样的话,会造成对同一个空间的再一次释放,所以程序会崩溃。

如何解决:

引用:& (引用其实就是对变量取别名)

void func2(Stack &s)
{
   
} 

如果我希望s的改变不去影响s1那该怎么办
即:你希望s 只是 s1 的拷贝
这个时候用一个对象去拷贝另外一个对象的时候,C++定义了一个函数来解决这样一个问题。
这个函数叫做拷贝构造
拷贝构造函数也是一个特殊的成员函数 其特征如下,

  1. 拷贝构造函数是构造函数的一个重载形式。
  2. 拷贝构造函数的参数只有一个,且必须是同类型对象 的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。(你如果不用引用的话,就会造成无穷递归)
    在这里插入图片描述

函数传参如果是内置类型,那就直接拷贝没问题
如果是自定义类型,则不能直接传,而是要调用一个函数来解决,这个函数就叫拷贝构造

为什么传值传参要调用拷贝构造函数?
为什么传值传参不能像c语言一样,结构体一样把相应的值依次的拷贝过去?

之前说了:像栈这样的类,如果你传值传参按浅拷贝的方式去拷贝的话,则会出现两次调用析构函数的情况,这会造成程序的崩溃。
所以这个时候我们必须要调用一个函数来解决这个问题,这个函数就是拷贝构造函数,他可以帮我们解决两次调用析构函数这个麻烦。这个拷贝构造函数可以完成深拷贝。

是不是有点懵,没关系,我们再来强调一下。

在c语言中,我们函数传值传参是直接拷贝的因为他不会出现一些问题
但是在c++中,如果我们直接拷贝的话,如果是栈这种类型对象的函数它会出现析构函数两次释放同一个空间的情况,
所以这个时候我们的拷贝就不能直接拷贝了,我们需要调用拷贝构造函数来进行拷贝。
我们使用拷贝构造函数来传参的话,那么函数它在传参的时候,它就不会直接传参,他会把参数先给拷贝构造函数,让拷贝构造函数处理完了之后再返回给函数,然后再进行函数中的内容。自定义类型传值传参必须调用拷贝构造。

就是在c++的逻辑中,任何传参都会先调用拷贝构造函数。
所以你会疑惑,为什么会造成深度递归呢?
因为当你使用函数的时候,拷贝构造函数和默认构造函数是构成函数重载的,那么当你调用这个函数的时候,你创建了一个d2嘛,那个d2面后面的括号里面写的是d1

那么他的这个形式是符合函数重载的形式的,这个时候他就会去调用拷贝构造函数,他调用拷贝构造函数的时候,(我们调用函数的时候,第一步先传参,C++的逻辑嘛,传参之前先要传给拷贝构造函数,当拷贝构造函数处理完成之后再传回给调用了的那个函数)那么它就会先把d1的内容传给拷贝构造函数,然后传给拷贝构造函数之后就要进行下一步了,但是当你传给拷贝构造函数之后,你会发现,拷贝后函数又会认为你的这个行为是一个传参的行为,所以他就又会传给拷贝构造函数,就这样一直循环了,死循环了,造成了一个深度的递归

与默认构造函数和析构函数的不同

拷贝构造函数和我们之前学的析构函数和默认构造函数的不同就是当我们不写它的时候,编译器默认生成的拷贝构造函数可以处理内置类型,它会对内置类型进行值拷贝 对自定义类型调用他相应的拷贝。

总结一下

日期类(Date)不需要我们实现拷贝构造,编译器默认生成的就可以直接用了。
但是栈这个类需要我们自己来写一个拷贝构造函数来实现深拷贝,因为编译器默认生成的会出现问题

注意:

//拷贝构造函数 用同类型的对象来初拷贝始化它,所以叫拷贝构造
   Date(const Date& d)
   {
   	cout << "Date(Date& d)" << endl;
   	_year = d._year;
   	_month = d._month;
   	_day = d._day;
   }

我们为什么要加一个const?
因为在使用的时候,如果某一天你喝了酒,你把两个变量的位置写反了,这个时候你又没有察觉。那么就会造成一些错误的情况出现。
所以这个时候我们可以在前面加一个const限制一下。
加了const之后,他的权限就变小了,也符合引用的规则

进行引用的时候,权限是可以缩小的,但是是不能放大的

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

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

相关文章

在广告宣传领域,PS透明屏有哪些优点表现?

PS透明屏是一种新型的显示技术&#xff0c;它将传统的显示屏幕与透明材料相结合&#xff0c;使得屏幕能够同时显示图像和透过屏幕看到背后的物体。 这种技术在商业展示、广告宣传、产品展示等领域有着广泛的应用前景。 PS透明屏的工作原理是利用透明材料的特性&#xff0c;通…

iTOP-i.MX8MM开发板添加 isb 转串口设备驱动

对于通过 USB 接口访问的模块&#xff0c;在 Linux 内核中集成 USB 驱动程序。我们需要配置内核选中支持 GSM 和 CDMA 模块的 USB 转串口驱动 > Device Drivers -> USB support (USB_SUPPORT [y]) -> USB Serial Converter support (USB_SERIAL [y]) -> USB dr…

计算机网络(7) --- UDP协议和TCP协议

计算机网络&#xff08;6&#xff09; --- https协议_哈里沃克的博客-CSDN博客https协议https://blog.csdn.net/m0_63488627/article/details/132112683?spm1001.2014.3001.5501 目录 1.补充知识 1.PORT端口号 2.端口号范围划分 3.知名端口号 2.UDP协议 1.UDP报头 2.U…

EVE-NG MPLS 静态 LSP

1 拓扑 2 配置步骤 2.1 配置接口IP 和路由 LER1 interface GigabitEthernet1/0ip address 10.1.1.1 255.255.255.0quitinterface GigabitEthernet2/0ip address 11.1.1.1 255.255.255.0quitip route-static 21.1.1.0 24 10.1.1.2VPC1 ip 11.1.1.100/24 11.1.1.1 配置完成后…

LeetCode--HOT100题(22)

目录 题目描述&#xff1a;160. 相交链表&#xff08;简单&#xff09;题目接口解题思路代码 PS: 题目描述&#xff1a;160. 相交链表&#xff08;简单&#xff09; 给你两个单链表的头节点 headA 和 headB &#xff0c;请你找出并返回两个单链表相交的起始节点。如果两个链表…

gradle项目Connection timed out,build时先下载gradle问题download gradle-x.x-bin.zip

IDEA 导入 Gradle 项目&#xff0c;编译的时候会默认下载 配置版本的Gradle.zip问题&#xff0c;一般会下载失败&#xff0c;提示Connection timed out&#xff0c;连接超时。 解决办法&#xff1a; 修改项目根目录下gradle目录下的gradle-wrapper.properties文件&#xff0c;…

小红书投放复盘怎么做,达人流量统计

作为众多品牌进行产品宣推与传播的重要阵地&#xff0c;红薯一直以来都备受关注。而随着平台投放成本的水涨船高&#xff0c;如何优化投放结果&#xff0c;就成了关键问题。所以&#xff0c;今天就来分享一下&#xff0c;小红书投放复盘怎么做&#xff0c;达人流量统计&#xf…

《操作系统真象还原》学习笔记:第六章 完善内核

一.函数调用约定简介 调用约定&#xff0c;calling conventions&#xff0c;从字面上理解&#xff0c;它是调用函数时的一套约定&#xff0c;是被调用代码的接口&#xff0c;它体现在&#xff1a; 参数的传递方式&#xff0c;是放在寄存器中&#xff1f;栈中&#xff1f;还是…

JavaSE知识点整理---集合篇

文章目录 1、数组与集合的区别?2、List、Set、Map的区别&#xff1f;3、ArrayList、LinkedList、Vector的区别&#xff1f;4、Java集合的快速失败机制【fail-fast】5、List接口常用方法6、List的三种遍历方式 1、数组与集合的区别? 在Java中&#xff0c;数组和集合都是容器&a…

如何使用 reqwest 包

GET 请求 向连接发起一个 GET 请求&#xff1a;https://hacker-news.firebaseio.com/v0/topstories.json&#xff0c;并解析返回的内容。 尝试发起请求 因为是 GET 请求&#xff0c;可以先在浏览器中进行查看&#xff0c;浏览器可以正常显示一个 id 列表&#xff0c;如下所示…

24届华东理工大学近5年自动化考研院校分析

今天给大家带来的是华东理工大学控制考研分析 满满干货&#xff5e;还不快快点赞收藏 一、华东理工大学 学校简介 华东理工大学原名华东化工学院&#xff0c;1956年被定为全国首批招收研究生的学校之一&#xff0c;1960年起被中共中央确定为教育部直属的全国重点大学&#…

【Winform学习笔记(七)】Winform无边框窗体拖动功能

Winform无边框窗体拖动功能 前言正文1、设置无边框模式2、无边框窗体拖动方法1、通过Panel控件实现窗体移动2、通过窗体事件实现窗体移动3、调用系统API实现窗体移动4、重写WndProc()实现窗体移动 前言 在本文中主要介绍 如何将窗体设置成无边框模式、以及实现无边框窗体拖动功…

Java中的enum枚举类型

知识介绍 在Java中&#xff0c;enum&#xff08;枚举&#xff09;是一种特殊的数据类型&#xff0c;用于定义一组命名的常量。枚举类型允许你在代码中创建一个固定的、预定义的值集合&#xff0c;这些值可以在整个程序中使用。使用枚举可以使代码更加清晰、可读&#xff0c;并…

Bootload U-Boot分析

Bootloader是在操作系统运行之前执行的一段小程序。通过这段小程序可以初始化硬件设备、建立内存空间的映射表&#xff0c;从而建立适当的系统软硬件环境&#xff0c;为最终调用操作系统内核做好准备。 对于嵌入式系统&#xff0c;Bootloader是基于特定硬件平台来实现的。因此…

【Hystrix技术指南】(5)Command创建和执行实现

创建流程 构建HystrixCommand或者HystrixObservableCommand对象 *使用Hystrix的第一步是创建一个HystrixCommand或者HystrixObservableCommand对象来表示你需要发给依赖服务的请求。 若只期望依赖服务每次返回单一的回应&#xff0c;按如下方式构造一个HystrixCommand即可&a…

FreeRTOS(vTaskList与vTaskGetRunTimeStats)

目录 1、Cube配置 ①配置SYS ②配置TIM3 ③配置USART2 ④配置FreeRTOS ⑤配置中断优先级 2、代码添加改动 ①在main函数合适位置开启TIM3中断 ②修改HAL_TIM_PeriodElapsedCallback函数 ③完善两个相关函数 ④vTaskList与vTaskGetRunTimeStats的使用 vTaskList&#xff…

C# 有效的字母异位词

242 有效的字母异位词 给定两个字符串 和 &#xff0c;编写一个函数来判断 是否是 的字母异位词。stts 注意&#xff1a;若 和 中每个字符出现的次数都相同&#xff0c;则称 和 互为字母异位词。stst 示例 1: 输入: s “anagram”, t “nagaram” 输出: true 示例 2: 输…

探索国产嵌入式Python解决方案的方法(开源)

大家好&#xff0c;今天我们要介绍一款适用于单片机的嵌入式Python开源项目 -- PikaPython。 第一&#xff1a;嵌入式Python的发展趋势 在嵌入式领域软硬件的发展趋势中&#xff0c;硬件的成本日益降低&#xff0c;性能逐渐提升。这种趋势使得Python在芯片上的运行难度已经大大…

学python的心得体会1000字,学python的心得体会2000字

这篇文章主要介绍了学python的心得体会2000字&#xff0c;具有一定借鉴价值&#xff0c;需要的朋友可以参考下。希望大家阅读完这篇文章后大有收获&#xff0c;下面让小编带着大家一起了解一下。 1. 初学者应该从简单的练习开始&#xff0c;先掌握基本的语法和概念&#xff0c;…

Three.js光源

目录 Three.js入门 Three.js光源 本文我们将研究three.js中的灯光类型和JavaScript中的光源&#xff0c;探索不同的光源设置。我们的目标是全面理解光源设置和类型&#xff0c;比如环境光、半球光、矩形光、方向光、点光源和聚光灯。我们将逐个介绍它们的属性参数和使用方法…