当前位置: 首页 > news >正文

C++ 类和对象 拷贝构造函数

一 拷贝构造函数的概念:

拷贝构造函数是一种特殊的构造函数,用于创建一个对象是另一个对象的副本。当需要用一个已存在的对象来初始化一个新对象时,或者将对象传递给函数或从函数返回对象时,会调用拷贝构造函数。

二 拷贝构造函数的特点:

1:拷贝构造函数是构造函数的一个重载形式。

2:拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错, 因为会引发无穷递归调用。

3:若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按 字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

注意:在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的,而自定 义类型是调用其拷贝构造函数完成拷贝的。

4:编译器生成的默认拷贝构造函数已经可以完成字节序的值拷贝了,还需要自己显式实现吗? 当然像日期类这样的类是没必要的。

2.1 代码示例:

class Time 
{
public:// 普通构造函数Time(int hour = 0, int minute = 0, int second = 0) {_hour = hour;_minute = minute;_second = second;}// 拷贝构造函数,使用引用传递Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;}void Print() const {std::cout << _hour << ":" << _minute << ":" << _second << std::endl;}private:int _hour;int _minute;int _second;
};int main()
{Time t1(10, 20, 30);   // 使用普通构造函数//构造函数的重载Time t2 = t1;          // 使用拷贝构造函数//Time t2(t1);        // 拷贝构造的另一种写法t1.Print();t2.Print();return 0;
}

输出:

2.2 为什么要使用引用呢?

我们在 increment 函数中改变x的值并没有间接性改变a,这是因为传过去的只是编译器创建实参的一个副本,而修改副本怎么可能可以改变a呢?

#include <iostream>void increment(int x) 
{x = x + 1;  // 修改的是副本,不影响实参
}int main() 
{int a = 5;increment(a);  // 传递a的副本std::cout << a << std::endl;  // 输出5,原始值a未被修改return 0;
}

知道传值传参的本质之后,再来想一想为什么要用引用?咱们先来说说如果没用用引用的后果会是怎么样,当把自定义类型传出去后且不用引用或者指针来接收,它会

调用 Time(const Time other),其中 othert1 的按值传递副本。

为了按值传递,编译器需要创建 other 的副本。

创建 other 的副本时,再次调用 Time(const Time other)

这个新调用的 Time(const Time other) 又需要创建自己的 other 副本,再次调用 Time(const Time other)

如此反复,导致无限递归调用,最终导致栈溢出。

图:

C++规定,自定义类型的拷贝,都会调用拷贝构造

那为什么要引用呢?

首先我们来回顾一下引用 :

1:引用是现有变量的另一个名字。

2:它们不创建新对象,只是指向已有对象。

3:引用只是指向现有对象,不创建新副本

因为引用就是它本身,所以何来创建新副本这一说法,创建新副本是怕改变副本从而导致改变实参值

2.3 总结:

1:按值传递会递归:每次传递对象会复制对象,导致无限递归。

2:引用传递避免递归:引用只是指向对象本身,不会复制对象

三 默认拷贝构造:

当你没有显式定义拷贝构造函数时,编译器会为你自动生成一个默认的拷贝构造函数。这个默认拷贝构造函数会逐个拷贝对象的所有成员变量。

3.1 内置类型与自定义类型的拷贝:

内置类型:如 int, char, float 等,拷贝时直接按照字节方式进行复制,也就是直接复制其值。

自定义类型:如类和结构体,拷贝时会调用该类型的拷贝构造函数。

3.2 代码示例:

内置类型:

#include <iostream>class MyClass 
{
public:int x;  // 内置类型成员
};int main() 
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1;  // 使用编译器生成的默认拷贝构造函数std::cout << "obj1.x: " << obj1.x << std::endl; std::cout << "obj2.x: " << obj2.x << std::endl;return 0;
}

输出:

对于一个类里面只有内置类型成员那编译器生成的默认拷贝构造会自动复制其值。

自定义类型:

#include <iostream>class Time 
{
public:// 默认构造函数Time() {  _hour = 0;_minute = 0;_second = 0;}// 拷贝构造函数Time(const Time& other) {_hour = other._hour;_minute = other._minute;_second = other._second;std::cout << "Time::Time(const Time& other)" << std::endl;}private:int _hour;int _minute;int _second;
};class MyClass 
{
public:int x;  // 内置类型成员Time t; // 自定义类型成员
};int main() 
{MyClass obj1;obj1.x = 10;MyClass obj2 = obj1;  // 使用编译器生成的默认拷贝构造函数std::cout << "obj1.x: " << obj1.x << std::endl;std::cout << "obj2.x: " << obj2.x << std::endl; return 0;
}

当执行MyClass obj2 = obj1; 因obj1类里面有自定义类型 t 所以编译器生成的默认拷贝构造会自动调用Time(const Time& other) 来完成

3.3 总结:

内置类型:编译器默认拷贝构造函数会直接复制其值。

自定义类型:编译器默认拷贝构造函数会调用该类型的拷贝构造函数来复制其内容。

四 内存分区:

要理解好深拷贝与浅拷贝那就得先了解内存是怎么样分区的。

计算机程序运行时,内存通常被分为四个主要区域:栈区、堆区、全局静态区和只读区(常量区和代码区)。

4.1 栈区:

局部变量:函数内部定义的变量。

形参(函数参数):函数定义时的参数。

返回地址:函数调用后的返回地址。

特点:

栈区中访问速度快且栈的内存连续分配

因存储的都是 局部/形参/返回地址 所以栈区空间小,存储的生命周期短

在我们局部变量所在的函数执行完成时,它会自动释放内存

4.2 堆区:

动态分配的数据:通过 newmalloc 等动态分配函数分配的内存。

特点:

因存储的都是new 或者malloc开辟的空间所以堆区空间大,所以访问速度慢

堆中的内存分配和释放是通过指针进行的,可能不是连续的。

堆区的内存需要程序员手动管理,必须手动释放动态分配的内存,否则会导致内存泄漏。

4.3 全区/静态区:

全局变量:在所有函数外部定义的变量。

静态变量:使用 static 关键字定义的变量。

特点:

全局变量和静态变量在程序的整个运行期间一直存在,直到程序结束。

全局变量可以在程序的所有函数中访问,静态变量在声明的作用域内共享

4.4 只读常量区:

常量:程序中定义的常量。

代码:程序的指令代码。

特点:

常量区的数据在程序运行期间不能被修改,保证了数据的安全性和稳定性。

代码区存储程序的指令代码,在程序运行时被载入内存以执行。

五 浅拷贝:

首先我们来回顾C语言里面的基本类型指针类型

5.1 基本类型:

基本类型是C语言内置的数据类型,它们用于存储最基本的数值数据。常见的基本类型包括:int float char……

5.2 指针类型:

指针类型是存储内存地址的数据类型。指针用于指向其他变量或对象在内存中的位置。

5.3 基本类型代码示例:

#include <iostream>class BasicType 
{
public:int value;// 构造函数BasicType(int v) {value = v;}// 拷贝构造函数BasicType(const BasicType& other) {value = other.value;}
};int main() 
{BasicType obj1(10);BasicType obj2 = obj1;  // 浅拷贝,复制基本类型的值std::cout << "改变前: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;obj2.value = 20;  // 修改obj2的值std::cout << "改变后: " << std::endl;std::cout << "obj1.value: " << obj1.value << std::endl;std::cout << "obj2.value: " << obj2.value << std::endl;return 0;
}

输出:

值会被复制但修改新对象的值不会影响原对象。

5.3 指针类型代码示例:

#include <iostream>class SimplePointer 
{
public:int* ptr;  // 成员变量 ptr// 构造函数SimplePointer(int value)
{ptr = (int*)malloc(sizeof(int));  // 动态分配内存并初始化if (ptr != nullptr) {*ptr = value;}
}SimplePointer(const SimplePointer& other) {this->ptr = other.ptr;  // 浅拷贝,复制内存地址}void print() const {std::cout << "Value: " << *ptr << std::endl;}
};int main() 
{SimplePointer obj1(10);  // 创建第一个对象,并将值初始化为10SimplePointer obj2(obj1);  // 使用拷贝构造函数(浅拷贝)// 打印初始值std::cout << "Initial values:" << std::endl;obj1.print();obj2.print();// 修改obj2的值*obj2.ptr = 20;// 打印修改后的值std::cout << "After change:" << std::endl;obj1.print();obj2.print(); return 0;
}

输出:

复制内存地址,共享同一块内存,修改会互相影响

六 深拷贝:

#include <iostream>
#include <cstdlib>
#include <cstring>class SimpleClass 
{
public:int* ptr;// 默认构造函数SimpleClass(int value) {ptr = (int*)malloc(sizeof(int));  // 动态分配内存并初始化if (ptr != nullptr) {*ptr = value;}}// 深拷贝构造函数SimpleClass(const SimpleClass& other) {ptr = (int*)malloc(sizeof(int));  // 分配新内存if (ptr != nullptr) {*ptr = *(other.ptr);  // 复制内容}}// 析构函数~SimpleClass() {if (ptr != nullptr) {free(ptr);  // 释放内存}}void Print() const {if (ptr != nullptr) {std::cout << "Value: " << *ptr << std::endl;}}
};int main() 
{SimpleClass obj1(10);  // 创建对象,ptr 指向的值为 10SimpleClass obj2 = obj1;  // 使用深拷贝构造函数obj1.Print();obj2.Print();// 修改 obj2 的值if (obj2.ptr != nullptr) {*(obj2.ptr) = 20;}obj1.Print();obj2.Print();return 0;
}

输出:

深拷贝不仅复制对象的指针成员,还为指针指向的内容分配新的内存,并复制原对象的数据。这样,两个对象拥有独立的内存,修改一个不会影响另一个。

相关文章:

C++ 类和对象 拷贝构造函数

一 拷贝构造函数的概念&#xff1a; 拷贝构造函数是一种特殊的构造函数&#xff0c;用于创建一个对象是另一个对象的副本。当需要用一个已存在的对象来初始化一个新对象时&#xff0c;或者将对象传递给函数或从函数返回对象时&#xff0c;会调用拷贝构造函数。 二 拷贝构造函…...

C# —— Math对象

Math 数学类 提供了一些相关数学计算的属性和方法、四舍五入、向上求整、向下求整、开平方&#xff0c;几次方 最大值和最小值 sin cos 绝对值 方法 1.Math 常用的字段 Math.PI double x 2 * 180 / Math.PI; Console.WriteLine(x); 2 Math.Abs() 求绝对值 int a -3; Con…...

Face_recognition实现人脸识别

这里写自定义目录标题 欢迎使用Markdown编辑器一、安装人脸识别库face_recognition1.1 安装cmake1.2 安装dlib库1.3 安装face_recognition 二、3个常用的人脸识别案例2.1 识别并绘制人脸框2.2 提取并绘制人脸关键点2.3 人脸匹配及标注 欢迎使用Markdown编辑器 本文基于face_re…...

1-3分钟爆款视频素材在哪找啊?这9个热门爆款素材网站分享给你

在如今快节奏的时代&#xff0c;短视频已成为吸引观众注意力的黄金手段。然而&#xff0c;要制作出1-3分钟的爆款视频&#xff0c;除了创意和剪辑技巧外&#xff0c;选择合适的素材至关重要。那么&#xff0c;哪里可以找到那些能让你的视频脱颖而出的爆款素材呢&#xff1f;不用…...

武汉免费 【FPGA实战训练】 Vivado入门与设计师资课程

一&#xff0e;背景介绍 当今高度数字化和智能化的工业领域&#xff0c;对高效、灵活且可靠的技术解决方案的需求日益迫切。随着工业 4.0 时代的到来&#xff0c;工业生产过程正经历着前所未有的变革&#xff0c;从传统的机械化、自动化逐步迈向智能化和信息化。在这一背景下&…...

【vite创建项目】

搭建vue3tsvitepinia框架 一、安装vite并创建项目1、用vite构建项目2、配置vite3、找不到模块 “path“ 或其相对应的类型声明。 二、安装element-plus1、安装element-plus2、引入框架 三、安装sass sass-loader1、安装sass 四、安装vue-router-next 路由1、安装vue-router42搭…...

最优化方法 运筹学【】

1.无约束 常用公式 线搜索准则&#xff1a;求步长 精确线搜索&#xff08;argmin&#xff09; 最速下降&#xff1a;sd&#xff1a;线性收敛 2.算法 SD dk&#xff1a;付梯度-g newton dk&#xff1a;Gkd-g 二阶收敛&#xff0c;步长为1 阻尼牛顿&#xff1a;步长用先搜…...

探索 WebKit 的动感世界:设备方向和运动支持全解析

探索 WebKit 的动感世界&#xff1a;设备方向和运动支持全解析 随着移动设备的普及&#xff0c;网页应用对设备方向和运动的感知需求日益增长。WebKit 作为众多流行移动浏览器的渲染引擎&#xff0c;提供了对设备方向和运动的全面支持&#xff0c;使得 Web 应用能够根据设备的…...

高考假期预习指南

IT专业入门&#xff0c;高考假期预习指南 对于希望进入IT行业的学生来说&#xff0c;假期是学习信息技术的最佳时机。 在信息化快速发展的时代&#xff0c;IT行业的发展前景广阔&#xff0c;但高技能要求使新生可能感到迷茫。 建议新生制定详细的学习计划&#xff0c;包括了解…...

Spring Boot 事件监听机制工作原理

前言&#xff1a; 我们知道在 Spring 、Spring Boot 的启动源码中都大量的使用了事件监听机制&#xff0c;也就是我们说的的监听器&#xff0c;监听器的实现基于观察者模式&#xff0c;也就是我们所说的发布订阅模式&#xff0c;这种模式可以在一定程度上实现代码的解耦&#…...

【AI大模型】驱动的未来:穿戴设备如何革新血液、皮肤检测与营养健康管理

文章目录 1. 引言2. 现状与挑战3. AI大模型与穿戴设备概述4. 数据采集与预处理4.1 数据集成与增强4.2 数据清洗与异常检测 5. 模型架构与训练5.1 高级模型架构5.2 模型训练与调优 6. 个性化营养建议系统6.1 营养建议生成优化6.2 用户反馈与系统优化 7. 关键血液成分与健康状况评…...

【FFmpeg】avcodec_open2函数

目录 1. avcodec_open21.1 编解码器的预初始化&#xff08;ff_encode_preinit & ff_decode_preinit&#xff09;1.2 编解码器的初始化&#xff08;init&#xff09;1.3 释放编解码器&#xff08;ff_codec_close&#xff09; FFmpeg相关记录&#xff1a; 示例工程&#xff…...

matlab:对带参数a关于x的方程求解

题目 讲解 简洁对各个式子的内部含义用浅显易懂的话语总结出来了&#xff0c;耐心体会 f(a) (x)exp(x)x^ax^(sqrt(x))-100;%因为下面的fzero的第一个数需要一个fun&#xff0c;所以这里有两个句柄&#xff0c;第一个a是输入的&#xff0c;第二个x是需要被解出的 A0:0.1:2;%创…...

Yolov10训练,转化onnx,推理

yolov10对于大目标的效果好&#xff0c;小目标不好 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 目录 一、如果你训练过yolov5&#xff0c;yolov8&#xff0c;的话那么你可以直接用之前的环境就行 二、配置好后就可以配置文件…...

GEE代码实例教程详解:洪水灾害监测

简介 在本篇博客中&#xff0c;我们将使用Google Earth Engine (GEE) 进行洪水灾害监测。通过分析Sentinel-1雷达数据&#xff0c;我们可以识别特定时间段内的洪水变化情况。 背景知识 Sentinel-1数据集 Sentinel-1是欧洲空间局提供的雷达卫星数据集&#xff0c;它能够提供…...

运维锅总详解系统设计原则

本文对CAP、BASE、ACID、SOLID 原则、12-Factor 应用方法论等12种系统设计原则进行分析举例&#xff0c;希望对您在进行系统设计、理解系统运行背后遵循的原理有所帮助&#xff01; 一、CAP、BASE、ACID简介 以下是 ACID、CAP 和 BASE 系统设计原则的详细说明及其应用举例&am…...

深度学习笔记: 最详尽解释预测系统的分类指标(精确率、召回率和 F1 值)

欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家&#xff01; 预测系统的分类指标(精确率、召回率和 F1 值) 简介 让我们来谈谈预测系统的分类指标以及对精确率、召回…...

GEE代码实例教程详解:MODIS土地覆盖分类与面积计算

简介 在本篇博客中&#xff0c;我们将使用Google Earth Engine (GEE) 对MODIS土地覆盖数据进行分析。通过MODIS/061/MCD12Q1数据集&#xff0c;我们可以识别不同的土地覆盖类型&#xff0c;并计算每种类型的总面积。 背景知识 MODIS MCD12Q1数据集 MODIS/061/MCD12Q1是NASA…...

LT86101UXE 国产原装 HDMI2.0 / DVI中继器方案 分辨率 4Kx2K 用于多显示器 DVI/HDMI电缆扩展模块

1. 描述 Lontium LT86101UXE HDMI2.0 / DVI中继器特性高速中继器符合HDMI2.0/1.4规范,最大6 gbps高速数据率、自适应均衡RX输入和pre-emphasized TX输出支持长电缆应用程序,没有晶体在船上保存BOM成本,内部灵活的PCB TX巷交换路由。 LT86101UXE HDMI2.0/DVI中继器自动检测线缆损…...

FastApi中的常见请求类型

FastApi中的常见请求类型 后端开发语言中&#xff0c;我钟情于node&#xff0c;高效的异步处理真是让我眼前一亮&#xff0c;同时&#xff0c;简单易懂的语法也让我非常倾心 但是但是&#xff0c;因为考虑要写一个深度学习算法的后端接口&#xff0c;所以不得不选用python作为…...

React hook之useRef

React useRef 详解 useRef 是 React 提供的一个 Hook&#xff0c;用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途&#xff0c;下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...

【入坑系列】TiDB 强制索引在不同库下不生效问题

文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...

java 实现excel文件转pdf | 无水印 | 无限制

文章目录 目录 文章目录 前言 1.项目远程仓库配置 2.pom文件引入相关依赖 3.代码破解 二、Excel转PDF 1.代码实现 2.Aspose.License.xml 授权文件 总结 前言 java处理excel转pdf一直没找到什么好用的免费jar包工具,自己手写的难度,恐怕高级程序员花费一年的事件,也…...

蓝牙 BLE 扫描面试题大全(2):进阶面试题与实战演练

前文覆盖了 BLE 扫描的基础概念与经典问题蓝牙 BLE 扫描面试题大全(1)&#xff1a;从基础到实战的深度解析-CSDN博客&#xff0c;但实际面试中&#xff0c;企业更关注候选人对复杂场景的应对能力&#xff08;如多设备并发扫描、低功耗与高发现率的平衡&#xff09;和前沿技术的…...

基于Uniapp开发HarmonyOS 5.0旅游应用技术实践

一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架&#xff0c;支持"一次开发&#xff0c;多端部署"&#xff0c;可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务&#xff0c;为旅游应用带来&#xf…...

C++ 基础特性深度解析

目录 引言 一、命名空间&#xff08;namespace&#xff09; C 中的命名空间​ 与 C 语言的对比​ 二、缺省参数​ C 中的缺省参数​ 与 C 语言的对比​ 三、引用&#xff08;reference&#xff09;​ C 中的引用​ 与 C 语言的对比​ 四、inline&#xff08;内联函数…...

Linux --进程控制

本文从以下五个方面来初步认识进程控制&#xff1a; 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程&#xff0c;创建出来的进程就是子进程&#xff0c;原来的进程为父进程。…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

C#学习第29天:表达式树(Expression Trees)

目录 什么是表达式树&#xff1f; 核心概念 1.表达式树的构建 2. 表达式树与Lambda表达式 3.解析和访问表达式树 4.动态条件查询 表达式树的优势 1.动态构建查询 2.LINQ 提供程序支持&#xff1a; 3.性能优化 4.元数据处理 5.代码转换和重写 适用场景 代码复杂性…...

基于Springboot+Vue的办公管理系统

角色&#xff1a; 管理员、员工 技术&#xff1a; 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能&#xff1a; 该办公管理系统是一个综合性的企业内部管理平台&#xff0c;旨在提升企业运营效率和员工管理水…...