c++ 拷贝构造
我们思考一下这个问题:
观察以下代码,在运行的时候会崩溃
想一想为什么
#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++定义了一个函数来解决这样一个问题。
这个函数叫做拷贝构造。
拷贝构造函数也是一个特殊的成员函数 其特征如下,
- 拷贝构造函数是构造函数的一个重载形式。
- 拷贝构造函数的参数只有一个,且必须是同类型对象 的引用,使用传值方式编译器直接报错,因为会引发无穷递归调用。(你如果不用引用的话,就会造成无穷递归)
函数传参如果是内置类型,那就直接拷贝没问题
如果是自定义类型,则不能直接传,而是要调用一个函数来解决,这个函数就叫拷贝构造
为什么传值传参要调用拷贝构造函数?
为什么传值传参不能像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之后,他的权限就变小了,也符合引用的规则
进行引用的时候,权限是可以缩小的,但是是不能放大的
相关文章:

c++ 拷贝构造
我们思考一下这个问题: 观察以下代码,在运行的时候会崩溃 想一想为什么 #include<iostream> using namespace std;//栈类 typedef int DataType; class Stack { public://默认构造:Stack(size_t capacity 3){_array (DataType*)ma…...
MISRA 2012学习笔记(1)-Directives
文章目录 说明Directives2 编译与构建Dir 2.1 3 需求可追溯性Dir 3.1 4 代码设计Dir 4.1Dir 4.2Dir 4.3Dir 4.4Dir 4.5Dir 4.6Dir 4.7Dir 4.8Dir 4.9Dir 4.10Dir 4.11Dir 4.12Dir 4.13 说明 以下等级一般分为三种,建议,必要,强制 建议&#…...
升级node版本后vue2的项目node-sass、sass-loader安装报错(14.x升级到16.x)
node升级到16.x版本后,对应的node-sass需要升级到^6.0.0,此时sass-loader的版本需要升级到10.2.0以上 ,具体对应版本规则可参考链接: https://github.com/webpack-contrib/sass-loader/releases?page3 vue2通过vue/cli创建的项目࿰…...
深入理解CSS选择器:选择正确的方式掌控样式与布局
文章目录 CSS 概括CSS 选择器元素选择器(Element Selector)类选择器(Class Selector)ID 选择器(ID Selector)通用选择器(Universal Selector)属性选择器(Attribute Selec…...
qt设置控件的风格样式
设置tablewidget ui.tableWidget_MaterialLibrary->setStyleSheet("QTableView {""color:#DCDCDC;""background-color: #444444;""border: 1px solid #242424;""alternate-background-color:#525252;""gridline-co…...

简单易懂的Transformer学习笔记
1. 整体概述 2. Encoder 2.1 Embedding 2.2 位置编码 2.2.1 为什么需要位置编码 2.2.2 位置编码公式 2.2.3 为什么位置编码可行 2.3 注意力机制 2.3.1 基本注意力机制 2.3.2 在Trm中是如何操作的 2.3.3 多头注意力机制 2.4 残差网络 2.5 Batch Normal & Layer Narmal 2.…...

C语言经典小游戏之三子棋(超详解释+源码)
“纵有疾风来,人生不言弃,风乍起,合当奋意向此生。” 今天我们一起来学习一下三子棋小游戏用C语言怎么写出来? 三子棋小游戏 1.游戏规则介绍2.游戏准备3.游戏的实现3.1生成菜单3.2游戏的具体实现3.2.1初始化棋盘3.2.2打印棋盘3.2…...

宝塔Linux面板点击SSL闪退打不开?怎么解决?
宝塔Linux面板点击SSL证书闪退如何解决?旧版本的宝塔Linux面板确实存在这种情况,如何解决?升级你的宝塔Linux面板即可。新手站长分享宝塔面板SSL闪退的解决方法: 宝塔面板点击SSL证书闪退解决方法 问题:宝塔Linux面板…...
Problem: 6953. 判断是否能拆分数组
Problem: 6953. 判断是否能拆分数组 文章目录 思路解题方法复杂度Code 思路 针对题目中的以下目标,可以转换寻求数组中是否存在前后两个元素之和>m的情况,如果存在则返回ture,如果不存在则返回false。能这样转换的原因是,如果…...

MobiSys 2023 | 多用户心跳监测的双重成形声学感知
注1:本文系“无线感知论文速递”系列之一,致力于简洁清晰完整地介绍、解读无线感知领域最新的顶会/顶刊论文(包括但不限于 Nature/Science及其子刊; MobiCom, Sigcom, MobiSys, NSDI, SenSys, Ubicomp; JSAC, 雷达学报 等)。本次介绍的论文是:<<MobiSys’23,Multi-User A…...

Netty:ChannelInitializer添加到ChannelPipeline完成任务以后会自动删除自己
说明 io.netty.channel.ChannelInitializer是一个特殊的ChannelInboundHandler。它的主要作用是向 Channel对应的ChannelPipeline中增加ChannelHandler。执行完ChannelInitializer的initChannel(C ch)函数以后,ChannelInitializer就会从ChannelPipeline自动删除自己…...

【VUE】项目本地开启https访问模式(vite4)
在实际开发中,有时候需要项目以https形式进行页面访问/调试,下面介绍下非vue-cli创建的vue项目如何开启https 环境 vue: ^3.2.47vite: ^4.1.4 根据官方文档:开发服务器选项 | Vite 官方中文文档 ps:首次操作,不要被类…...

【状态估计】一维粒子滤波研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

设计模式-迭代器模式在Java中使用示例
场景 为开发一套销售管理系统,在对该系统进行分析和设计时,发现经常需要对系统中的商品数据、客户数据等进行遍历, 为了复用这些遍历代码,开发人员设计了一个抽象的数据集合类AbstractObjectList,而将存储商品和客户…...

Maven入职学习
一、什么是Maven? 概念: Maven是一种框架。它可以用作依赖管理工具、构建工具。 它可以管理jar包的规模、jar包的来源、jar包之间的依赖关系。 它的用途就是管理规模庞大的jar包,脱离IDE环境执行构建操作。 具体使用: 工作机…...

【多音音频测试信号】具有指定采样率和样本数的多音信号,生成多音信号的相位降低波峰因数研究(Matlab代码实现)
💥💥💞💞欢迎来到本博客❤️❤️💥💥 🏆博主优势:🌞🌞🌞博客内容尽量做到思维缜密,逻辑清晰,为了方便读者。 ⛳️座右铭&a…...

LeetCode150道面试经典题-删除有序数组中的重复项(简单)
1.题目 给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。 考虑 nums 的唯一元素的数量为 k ,…...

人大金仓数据库Docker部署
docker 搭建 yum -y install yum-utilsyum-config-manager --add-repo http://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposystemctl start docker.servicesystemctl enable docker.servicesystemctl status docker.service 配置Docker cd /etc/docker/ vi da…...

Leetcode-每日一题【剑指 Offer 07. 重建二叉树】
题目 输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点。 假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 示例 1: Input: preorder [3,9,20,15,7], inorder [9,3,15,20,7]Output: [3,9,20,null,null,15,7] 示例 2: Input: preo…...
Shell编程快速入门
Shell编程快速入门 脚本格式要求 脚本以#!/bin/bash开头脚本需要有可执行权限 脚本的常用执行方式 方式1:输入脚本的绝对路径或相对路径方式2:sh脚本 Shell的变量 Shell变量介绍 Linux Shell中的变量分为系统变量和用户自定义变量 系统变量&#…...
浅谈 React Hooks
React Hooks 是 React 16.8 引入的一组 API,用于在函数组件中使用 state 和其他 React 特性(例如生命周期方法、context 等)。Hooks 通过简洁的函数接口,解决了状态与 UI 的高度解耦,通过函数式编程范式实现更灵活 Rea…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

VB.net复制Ntag213卡写入UID
本示例使用的发卡器:https://item.taobao.com/item.htm?ftt&id615391857885 一、读取旧Ntag卡的UID和数据 Private Sub Button15_Click(sender As Object, e As EventArgs) Handles Button15.Click轻松读卡技术支持:网站:Dim i, j As IntegerDim cardidhex, …...

循环冗余码校验CRC码 算法步骤+详细实例计算
通信过程:(白话解释) 我们将原始待发送的消息称为 M M M,依据发送接收消息双方约定的生成多项式 G ( x ) G(x) G(x)(意思就是 G ( x ) G(x) G(x) 是已知的)࿰…...
Go 语言接口详解
Go 语言接口详解 核心概念 接口定义 在 Go 语言中,接口是一种抽象类型,它定义了一组方法的集合: // 定义接口 type Shape interface {Area() float64Perimeter() float64 } 接口实现 Go 接口的实现是隐式的: // 矩形结构体…...

学习STC51单片机31(芯片为STC89C52RCRC)OLED显示屏1
每日一言 生活的美好,总是藏在那些你咬牙坚持的日子里。 硬件:OLED 以后要用到OLED的时候找到这个文件 OLED的设备地址 SSD1306"SSD" 是品牌缩写,"1306" 是产品编号。 驱动 OLED 屏幕的 IIC 总线数据传输格式 示意图 …...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
leetcodeSQL解题:3564. 季节性销售分析
leetcodeSQL解题:3564. 季节性销售分析 题目: 表:sales ---------------------- | Column Name | Type | ---------------------- | sale_id | int | | product_id | int | | sale_date | date | | quantity | int | | price | decimal | -…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...