C++类与对象四

news2024/10/25 18:58:20

C++类与对象(四)

上期我们介绍了构造函数和析构函数,这期我们来介绍拷贝函数运算符重载

拷贝函数

在现实生活中,可能存在另一个你。

cv

那在C++中,我们是否能创建一个与已知对象一样的新对象呢?

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

特征

一、拷贝构造函数是构造函数的一个重载形式

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

为什么参数必须用引用

在C语言中,Stack栈这个数据结构实现,我们都是用Stack *s等指针方式来传值传参,那么我们是否可以不通过指针,直接使用Stack s来传值传参呢?

struct Stack
{
	int *a;
	int top;
	int capacity;
}ST;

//初始化
void Init(Stack *s) {
	s->capacity = 4;
	s->a = (int*)malloc(sizeof(int) * s->capacity);
		if (s->a == nullptr) {
			perror("malloc fail!");
		}
		s->top = -1;
}

bool IsFull(Stack* s) {
	return s->top == s->capacity - 1;
}

bool IsEmpty(Stack* s) {
	return s->top == -1;
}

void Resize(Stack* s) {
	s->capacity *= 2;
	s->a = (int*)realloc(s->a, s->capacity * sizeof(int));
	if (s->a == nullptr) {
		perror("realloc fail!");
	}
}

void Push(Stack* s,int x) {
	if (IsFull(s)) {
		Resize(s);
	}
	s->a[++s->top] = x;
}

void Pop(Stack* s) {
	if (IsEmpty(s)) {
		perror("Stack is Empty!");
		exit(-1);
	}
	s->a[s->top--];
}

void Print(Stack *s) {
	if (IsEmpty(s)) {
		perror("Stack is Empty!");
		exit(-1);
	}
	for (int i = 0; i <= s->top; i++) {
		printf("%d ", s->a[i]);
	}
	printf("\n");
}

int main() {
	Stack s;
	Init(&s);

	Push(&s, 1);
	Push(&s, 2);
	Push(&s, 3);
	Push(&s, 4);
	Push(&s, 5);
	Push(&s, 6);
	Push(&s, 7);
	Push(&s, 8);

	Print(&s);

	return 0;
}

在以上代码中,是一个Stack的数据结构,正常运行是没有问题的

image-20241020230529271

当我们把Print函数 Stack *s换成Stacl s时继续运行

image-20241020230921479

bool IsEmpty(Stack s) {
	return s.top == -1;
}
void Print(Stack s) {
	if (IsEmpty(s)) {
		perror("Stack is Empty!");
		exit(-1);
	}
	for (int i = 0; i <= s.top; i++) {
		printf("%d ", s.a[i]);
	}
	printf("\n");
}

相应的需要修改PopIsEmpty等函数(这里就不放出来,自行体会)

当然我们将此类传值传参方法放到类里面的拷贝构造函数是以下这样的

class Date {
public:
	//构造函数
	Date(int year = 1, int month = 1, int day = 1) {
		_year = year;
		_month = month;
		_day = day;
	}

	//拷贝函数
	Date(Date dd) {
		_year = dd._year;
		_month = dd._month;
		_day = dd._day;
	}//这里的参数是Date dd!!!
	//Date类不需要析构函数,因为变量都是内置类型,由编译器回收资源
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Date d1;//创建d1对象
	Date d2(d1);//用拷贝函数拷贝生成d2对象,拷贝对象是d1

	return 0;
}

注意拷贝构造函数的参数!

image-20241020231511530

在VS2022中,会直接报错,而在老一些的编译器版本例如VS2013等可能不会对其优化,因此我们需要明白为什么C++的拷贝构造函数的参数为什么必须是引用,而不能直接使用类当参数。

C++的类中,实例化一个新对象,如果没有显示定义构造函数和拷贝构造函数,会自动生成默认的构造函数和拷贝构造函数

看以下的图,拷贝d1时,不用引用传参会引起无穷递归,因为Date dd也会调用他自身的拷贝函数,从而不断递归,引起无穷递归。

image-20241021101614048

所以,参数必须是用引用

//拷贝函数
Date(Date& dd) {
	_year = dd._year;
	_month = dd._month;
	_day = dd._day;
}//引用传参

浅拷贝和深拷贝

浅拷贝

根据上面的内容。我们成功构建了Date类的拷贝构造函数,接下来我们调试以上修改好的程序

image-20241021102405748

成功把d1的内容拷贝到d2

那么是不是所有类都可以这样子拷贝呢?答案是:不可以!!!!!!!!!

Queue队列这个类演示一下

class Stack {
public:
	//构造函数
	Stack(int capacity=4) {
		_array = (int*)malloc(sizeof(int) * capacity);
		if (_array == nullptr) {
			perror("malloc fail");
			exit(-1);
		}
		_capacity = capacity;
		_size = 0;
	}
	//拷贝函数
	Stack(Stack& ST) {
		_array = ST._array;
		_capacity = ST._capacity;
		_size = ST._size;
	}
	//析构函数
    ~Stack() {
        if (_array != nullptr) {
            free(_array);
            _array = nullptr;
            _capacity = 0;
            _size = 0;
        }
	}
private:
	int* _array;
	int _capacity;
	int _size;
 };

class Queue {
	Stack push;
	Stack pop;
	int _size = 0;
};
int main() {

	Queue q1;
	Queue q2(q1);

	return 0;
}

image-20241021103339389

编译一下是显示没有语法错误的,然鹅运行程序时却

image-20241021103749835

为什么会出现错误呢?最主要的原因是拷贝函数只进行了浅拷贝也叫值拷贝,没有进行深度拷贝也叫深拷贝

原因剖析:

创建好q1对象后,继续执行拷贝构造q2对象

image-20241021105041657

已经执行完毕拷贝构造,当继续执行时会自动调用q1的析构函数,也就是Stack pushStack pop的析构函数~Stack,此时q2是浅拷贝,也就是q2里面的push 和pop类的地址与q1是一样的,当出了q1的作用域第一次调用析构函数,当出了q2的作用域会第二次调用析构函数。从而导致了释放两次_array的空间导致报错。

简单来说就是当 q1q2 退出作用域时,q1q2 中的 Stack 成员 pushpop 会调用各自的析构函数。由于它们共用了相同的 _array,这会导致同一块内存被 free 两次,从而引发错误。

想要解决这个办法,就需要用到深拷贝

深拷贝

既然直接拷贝不行,我们可以新开辟一个内存空间,来拷贝q1的所有内容,这种开辟新空间的拷贝操作叫深拷贝

//拷贝函数
Stack(Stack& ST) {
	//浅拷贝
	/*_array = ST._array;
	_capacity = ST._capacity;
	_size = ST._size;*/
	//深拷贝
	_array = (int*)malloc(sizeof(int) * ST._capacity);
	if (_array == nullptr) {
		perror("Copy fail!");
		exit(-1);
	}
	//将ST的内用拷贝到新创建的_array数组
	memcpy(_array, ST._array, sizeof(int) * ST._size);
	_capacity = ST._capacity;
	_size = ST._size;
}
  1. 拷贝构造函数不会修改原对象(q1)

    • 在调用拷贝构造函数时,q1 的数据只会被读取,不会被修改,也不会重新分配内存。
  2. 新对象(q2)有自己独立的内存

    • q2 在拷贝构造函数中通过 malloc 分配了一块新的内存,并将 q1 的数据复制到这块内存中,因此 q1q2 的内存是独立的。
  3. 原对象(q1)的内存地址不变

    • 在整个拷贝过程中,q1_array 地址不会发生变化,因为拷贝构造函数只为新对象分配内存。
  4. malloc 执行两次,但只针对不同对象

    • q1 在构造时执行了一次 mallocq2 在拷贝构造时为自己执行了一次 malloc,这两次分配是独立的,互不影响。

因此,拷贝函数是一个特殊的函数,并不会改变原对象的内容,而仅仅是作拷贝用。调试程序,可以看见q1q2_array的地址是不一样的,是两块独立的内存空间。而这个过程中q1_array是不变的

image-20241021112251003

总结

**浅拷贝:**浅拷贝只复制对象的基本属性和指针,而不复制指针所指向的实际数据。这意味着源对象和目标对象中的指针会指向同一块内存。

特点:快速、节省内存。

可能导致问题:当一个对象被销毁时,它的指针所指向的内存也会被释放,另一个对象也会因为释放导致无效。

适用情况:适合于没有动态内存分配或者不需要独立对象的情况。例如Date类只有内置类型的类

深拷贝:深拷贝会复制对象及其所指向的所有数据,包括指针指向的内容。这意味着每个对象都有自己独立的内存。

特点:比浅拷贝更耗费时间和内存,因为需要为每个指针分配新的内存并复制数据。
避免了悬空指针的问题,因为每个对象都持有自己的数据副本。

适用情况:适合于含有动态内存分配的对象,或需要独立副本的情况。例如队列、栈、二叉树等需要开辟空间的数据结构。

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

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

相关文章

六.python面向对象

学过C或者Java的同学一定了解过面向对象的相关内容&#xff0c;编程语言一般分为两种设计方式&#xff1a;面向对象、面向过程&#xff0c;早期的编程语言多是面向过程的&#xff0c;由多个过程组合在一起&#xff0c;而Python在设计的时候就是一种面向对象的语言&#xff0c;因…

[学习笔记]线段树(全)

线段树是一种可以处理区间问题的优秀数据结构. 线段树是一颗二叉树, 其中的每一个节点都代表了某个区间的信息. 普通线段树 这里默认您已经会了以下操作: 建树(以单点修改的形式)单点修改/查询区间查询 如果不会的话请见OI Wiki 着重讲解区间修改中 tag 的用法 对于区间修…

InternVL-1.1: Enhance Chinese and OCR Capabilities

Blog:https://internvl.github.io/blog/2024-01-24-InternVL-1.1/ 指南:https://internvl.readthedocs.io/en/latest/internvl1.1/introduction.html InternVL-Chat-V1-1 结构类似于 LLaVA,包括一个 ViT、一个 MLP 投影器和一个 LLM。如上图所示,我们通过一个简单的 MLP …

ubuntu服务器离线安装pytorch(cpu版本)

一、查看服务器是否有nvidia显卡&#xff08;无输出则没有nvidia显卡&#xff0c;则不需要安装nvidia驱动、cuda、cudnn&#xff09; lspci | grep -i nvidia 二、本地下载对应版本的torch&#xff08;对应python版本和linux系统&#xff09; 注意&#xff1a;cpu版本&#…

iOS调试真机出现的 “__llvm_profile_initialize“ 错误

一、错误形式&#xff1a; app启动就崩溃&#xff0c;如下&#xff1a; Demo__llvm_profile_initialize:0x1045f7ab0 <0>: stp x20, x19, [sp, #-0x20]!0x1045f7ab4 <4>: stp x29, x30, [sp, #0x10]0x1045f7ab8 <8>: add x29, sp, #0x100x1…

python最新h5st4.9.1调用源码(2025-10-25)

废话不多说&#xff0c;直接上源码&#xff0c;需要技术支持的私。 一、调用js方法&#xff1a; # -*- coding: utf-8 -*- """ -------------------------------------------------Author: byc6352File: jdh5st.pyTime: 2024/10/25 08:03Technical Support:by…

Python 20个必学实例盘点

Python 1. 计算器程序&#xff1a; def add(x, y): return x ydef subtract(x, y): return x - ydef multiply(x, y): return x * ydef divide(x, y): try: return x / y except ZeroDivisionError: print("除数不能…

基于SpringBoot+Vue+uniapp微信小程序的文玩销售小程序的详细设计和实现

项目运行截图 技术框架 后端采用SpringBoot框架 Spring Boot 是一个用于快速开发基于 Spring 框架的应用程序的开源框架。它采用约定大于配置的理念&#xff0c;提供了一套默认的配置&#xff0c;让开发者可以更专注于业务逻辑而不是配置文件。Spring Boot 通过自动化配置和约…

【网路原理】——HTTP状态码和Postman使用

阿华代码&#xff0c;不是逆风&#xff0c;就是我疯 你们的点赞收藏是我前进最大的动力&#xff01;&#xff01; 希望本文内容能够帮助到你&#xff01;&#xff01; 目录 引入&#xff1a; 一&#xff1a;状态码 1&#xff1a;状态码标准 2&#xff1a;常见的状态码 ①2…

依托微信小程序,畅享校园二手交易

作者介绍&#xff1a;✌️大厂全栈码农|毕设实战开发&#xff0c;专注于大学生项目实战开发、讲解和毕业答疑辅导。 &#x1f345;获取源码联系方式请查看文末&#x1f345; 推荐订阅精彩专栏 &#x1f447;&#x1f3fb; 避免错过下次更新 Springboot项目精选实战案例 更多项目…

普通数组矩阵

文章目录 普通数组一、最大子数组二、合并区间三、轮转数组四、除自身以外数组的乘积五、缺失的第一个正数 普通数组 一、最大子数组 题目链接 方法一&#xff1a;动态规划 方法二&#xff1a;前缀和&#xff08;有点难理解&#xff09; 二、合并区间 题目链接 三、轮…

Linux中部署Mysql保姆级教程

一、版本说明 本文的版本号是5.7.30,5.6及以上版本的MySQL要求Linux系统虚拟内存不能小于1G,否则MySQL可能无法运行。 二、安装前的准备 2.1查看系统自带的Mariadb rpm -qa|grep mariadb 安装mysql为什么需要卸载mariadb: 以前的Linux系统中数据库大部分是mysql,…

android 利用adb将apk安装到模拟器中的方法

1、安装完成了sdk以后&#xff0c;会有一个工具集&#xff0c;里面有一个adb.exe&#xff0c;这个文件可以查看模拟器列表&#xff0c;及安装apk到模拟器中。 可以将这个目录加到环境变量中&#xff0c;这样就不用定位到目录&#xff0c; 然后使用adb命令了。 2、这里我们先定…

纯血鸿蒙:国产操作系统的崛起与安卓的本质区别

华为正式发布纯血鸿蒙&#xff08;Harmony OS 5.0&#xff09;标志着中国科技产业进入了一个崭新的时代。随着这一操作系统的推出&#xff0c;中国不仅在消费电子领域迎来了一个新的操作系统生态&#xff0c;更是加强了中国在全球科技领域的发言权。本文将深入探讨纯血鸿蒙与安…

win10怎么卸载软件干净?电脑彻底删除软件的方法介绍,一键清理卸载残留!

电脑上经常会下载各种各样的软件来协助我们办公&#xff0c;不同的软件能够满足不同的需求。 但是不少软件可能使用频率没有那么高&#xff0c;甚至完全不使用。这个时候就需要将这些不常用的电脑软件卸载掉了&#xff0c;卸载软件能够释放一定的存储空间&#xff0c;提高电脑…

封装echarts组件,即插即用(附源码)

前言&#xff1a;最近一个项目刚收工&#xff0c;分享一个常用的封装echarts的组件。 一、直接上组件代码 <template><el-card class"echart-card" shadow"hover"><template v-slot:header><div class"card-header">&…

[网络协议篇] UDP协议

文章目录 1. 简介2. 特点3. UDP数据报结构4. 基于UDP的应用层协议5. UDP安全性问题6. 使用udp传输数据的系统就一定不可靠吗&#xff1f;7. 基于UDP的主机探活 python实现 1. 简介 User Datagram Protocol&#xff0c;用户数据报协议&#xff0c;基于IP协议提供面向无连接的网…

郑州地铁携手百望云,以数电票平台升级打造坚实便民惠民服务能力

随着城市的快速发展&#xff0c;地铁成为很多大城市市民出行的主力工具。为民众出行提供安全、便捷、合规的出行服务&#xff0c;成为相关机构的重点工作。 近日&#xff0c;郑州地铁集团有限公司&#xff08;以下简称“郑州地铁”&#xff09;与百望云达成合作&#xff0c;开…

免杀对抗—特征码修改花指令资源修改加壳保护

前言 今天主要是讲这个特征码定位以及这个花指令&#xff0c;资源修改这个东西其实有点投机取巧啦&#xff0c;至于这个加壳之前也讲过了&#xff0c;直接工具搞就行。那么什么是特征码定位呢&#xff0c;众所周知&#xff0c;杀软是通过特征码来检测我们的exe是否是后门的&am…

老外说“奶茶”不叫“milk tea”?!那叫什么?柯桥英语口语学习生活日语培训

奶茶的英文怎么说&#xff1f; 其实&#xff0c;“奶茶”在英文中有多种表达方式&#xff0c;但最常见且准确的应该是“tea with milk”。这种表达方式直接描述了奶茶的本质——茶与牛奶的结合。当然&#xff0c;你也可以用“milky tea”来表达&#xff0c;但相对来说&#xf…