(C++) 拷贝构造函数

目录

一、基本介绍

二、为什么需要拷贝构造函数

三、拷贝构造函数

四、传参时的问题

五、完整代码


一、基本介绍

        拷贝构造函数是C++中一个特殊的构造函数,用于创建一个类的对象作为另一个同类对象的副本。当一个对象以值的形式被传递给函数、从函数返回,或者用另一个同类对象直接初始化一个新对象时。例如,ClassName obj1 = obj2;这行代码就会调用obj2的拷贝构造函数来初始化obj1

        拷贝构造函数的主要作用是确保对象的正确复制,特别是当类成员包含指针或动态分配的资源时。在这种情况下,默认的拷贝构造函数只会进行浅拷贝(即复制指针的值而不是它指向的数据),这可能导致诸如资源共享或双重释放等问题。因此,如果类中有指针成员或需要特殊的复制逻辑,通常需要自定义拷贝构造函数以实现深拷贝,确保每个对象拥有自己独立的资源副本。(下面会详细的讨论这个问题)

        此外,根据C++的“规则三原则”,如果定义了拷贝构造函数,通常也应该定义赋值运算符和析构函数,以确保类在复制和资源管理方面的行为是一致和安全的。

基本语法

拷贝构造函数的典型声明形式是ClassName(const ClassName& other),其中ClassName代表类名,而other是对另一个同类型对象的引用,即:

class Entity
{
public:
    Entity(...)                         //构造函数
    
    Entity(const Entity& other)         //拷贝构造函数

    ~Entity()                           //析构函数
};

        使用引用 (ClassName& other) 而不是按值传递 (ClassName other) 避免了在函数调用过程中创建参数的额外副本。如果不使用引用,每次调用拷贝构造函数时,都会需要通过拷贝构造函数本身来创建一个临时对象,这会导致无限递归和程序崩溃。另一方面,使用 const 修饰符 (const ClassName& other) 确保了在拷贝构造过程中不会修改被复制的对象。这是一种良好的编程实践,因为拷贝构造的目的是创建一个新的副本,而不是修改现有对象。const 修饰符还允许拷贝构造函数接受临时对象或那些仅能以只读方式访问的对象作为参数。

此外的一个建议是,在进行对象传递的时候,最好使用const &进行,下面也会谈到这个问题。

二、为什么需要拷贝构造函数

        上面也谈到过,在进行类对象复制的时候,默认情况下是进行浅拷贝,因此当类成员中有指针的时候会出现问题。下面举一个简单的例子:

#pragma once
#include<iostream>

class String
{
private:
	char* m_String;						//字符串
	unsigned int m_Size;				//记录字符串的大小
public:
	String(const char* input)
	{
		m_Size = strlen(input);					//计算字符串的大小
		m_String = new char[m_Size + 1];		//开辟空间,这里+1是因为需要考虑'\n'停止符。这里需要理解一个字符串的工作原理
		memcpy(m_String, input, m_Size + 1);	//将输入的input字符串拷贝到m_String中	
		m_String[m_Size] = 0;					//在最后的末尾加上 停止符
	}

	~String()
	{
		delete[] m_String;						//释放内存
	}

	
	friend std::ostream& operator<<(std::ostream& stream, const String& input)
	{
		stream << input.m_String;
		return stream;
	}
};


void run()
{
	String string("window");
	String second = string;
	std::cout << string << std::endl;

	return;
}

int main()
{
	run();

	return 0;
}

这是一个自定义string类,使用的是最原始的方式进行的。当然你可以使用智能指针进行自动内存的管理,但是这样是为了更好的进行说明和展示,方便知道C++究竟干了什么。

        首先这样创建一个string对象是没有问题的。下面再创建一个对象second进行复制,再将它们打印,这个时候就回出问题了。

void run()
{
	String string("window");
	String second = string;
	std::cout << string << std::endl;
	std::cout << second << std::endl;

	return;
}

        程序就直接奔溃。出现这个问题的原因就是因为对同一个地址进行了两次delete释放。在second进行拷贝的时候,默认情况下进行的是浅拷贝。在程序结束的时候,第一个string类调用了析构函数对m_String进行的释放,但是由于string中的一个成员是指针,因此second在进行拷贝的时候实际上是对string中的每一个成员进行了复制,因此这个时候second中的m_String指针实际上和string中m_String指向的是同一个地址,所以second在调用析构函数的时候会报错。

        我们想要的效果是直接复制一整个string,这个就是拷贝构造函数存在的价值。

三、拷贝构造函数

下面是对代码的改进

class String
{
private:
	char* m_String;						//字符串
	unsigned int m_Size;				//记录字符串的大小
public:
	String(const char* input)
	{
		m_Size = strlen(input);					//计算字符串的大小
		m_String = new char[m_Size + 1];		//开辟空间,这里+1是因为需要考虑'\n'停止符。这里需要理解一个字符串的工作原理
		memcpy(m_String, input, m_Size + 1);	//将输入的input字符串拷贝到m_String中	
		m_String[m_Size] = 0;					//在最后的末尾加上 停止符
	}

	//拷贝构造函数
	String(const String& copy)
		:m_Size(copy.m_Size)
	{
		m_String = new char[m_Size + 1];
		memcpy(m_String, copy.m_String, m_Size + 1);
	}

	~String()
	{
		delete[] m_String;						//释放内存
	}

	
	friend std::ostream& operator<<(std::ostream& stream, const String& input)
	{
		stream << input.m_String;
		return stream;
	}
};

首先先对m_Size进行初始化,然后重新分配m_String的内存。当调用这个拷贝构造函数的时候,会对新的second中的m_String进行内存的分配。现在再执行就没有什么问题了。

四、传参时的问题

        在定义了拷贝构造函数后,如果进行传参,直接值传递也会发生问题。

#pragma once
#include<iostream>

class String
{
private:
	char* m_String;						//字符串
	unsigned int m_Size;				//记录字符串的大小
public:
	String(const char* input)
	{
		m_Size = strlen(input);					//计算字符串的大小
		m_String = new char[m_Size + 1];		//开辟空间,这里+1是因为需要考虑'\n'停止符。这里需要理解一个字符串的工作原理
		memcpy(m_String, input, m_Size + 1);	//将输入的input字符串拷贝到m_String中	
		m_String[m_Size] = 0;					//在最后的末尾加上 停止符
	}

	//拷贝构造函数
	String(const String& copy)
		:m_Size(copy.m_Size)
	{
		std::cout << "Start Copy Function" << std::endl;
		m_String = new char[m_Size + 1];
		memcpy(m_String, copy.m_String, m_Size + 1);
		std::cout << "End Copy Function" << std::endl;
	}

	~String()
	{
		delete[] m_String;						//释放内存
	}

	
	friend std::ostream& operator<<(std::ostream& stream, const String& input)
	{
		stream << input.m_String;
		return stream;
	}
};

//进行String类的打印
void StringPrint(String input)
{
	std::cout << input << std::endl;
}

//shejing
void run()
{
	String string("window");
	String second = string;
	
	StringPrint(string);
	StringPrint(second);

	return;
}

 我们使用一个函数对String的进行打印,在拷贝函数中进行输出标记,发现输出结果为:

拷贝构造函数被多次的调用了。因为在进行传参的时候进行的值传参,是一个复制的过程,因此拷贝构造函数被反复的调用,要解决这个问题就是使用const & 进行传参。        

这样的话,拷贝构造函数就只会在second进行复制的时候被调用。

五、完整代码

#include<iostream>

class String
{
private:
	char* m_String;						//字符串
	unsigned int m_Size;				//记录字符串的大小
public:
	String(const char* input)
	{
		m_Size = strlen(input);					//计算字符串的大小
		m_String = new char[m_Size + 1];		//开辟空间,这里+1是因为需要考虑'\n'停止符。这里需要理解一个字符串的工作原理
		memcpy(m_String, input, m_Size + 1);	//将输入的input字符串拷贝到m_String中	
		m_String[m_Size] = 0;					//在最后的末尾加上 停止符
	}

	//拷贝构造函数
	String(const String& copy)
		:m_Size(copy.m_Size)
	{
		std::cout << "Start Copy Function" << std::endl;
		m_String = new char[m_Size + 1];
		memcpy(m_String, copy.m_String, m_Size + 1);
		std::cout << "End Copy Function" << std::endl;
	}

	~String()
	{
		delete[] m_String;						//释放内存
	}

	
	friend std::ostream& operator<<(std::ostream& stream, const String& input)
	{
		stream << input.m_String;
		return stream;
	}
};

//进行String类的打印
void StringPrint(const String& input)
{
	std::cout << input << std::endl;
}

void run()
{
	String string("window");
	String second = string;
	
	StringPrint(string);
	StringPrint(second);

	return;
}

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

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

相关文章

计算机网络第一课

先了解层级&#xff1a; 传输的信息称为协议数据单元&#xff08;PDU&#xff09;&#xff0c;PDU在每个层次的称呼都不同&#xff0c;见下图&#xff1a;

(1)(1.13) SiK无线电高级配置(一)

文章目录 前言 1 监控链接质量 2 诊断范围问题 前言 本文提供 SiK 遥测无线电(SiK Telemetry Radio)的高级配置信息。它面向"高级用户"和希望更好地了解无线电如何运行的用户。 &#xff01;Tip 大多数用户只需要 SiK Radio v2 中提供的基本指南和功能概述。 1 …

提前应对威胁

通过新的《2023-2028 年荷兰国际网络安全战略》&#xff0c;荷兰政府在面对国家和犯罪分子持续构成的网络威胁时展现了责任和机构。它渴望将民主、人权和规范放在首位&#xff0c;并寻求维护全球开放、自由和安全的互联网。该战略明确了政府在国内实施打击的意愿和能力&#xf…

Revit各版本安装指南

Revit下载链接 https://pan.baidu.com/s/1dVqJhV07emS-p-zIxTG7kw?pwd0531 1.鼠标右击【Revit2024(64bit)】压缩包&#xff08;win11及以上系统需先点击“显示更多选项”&#xff09;【解压到 Revit2024(64bit)】。 2.打开解压后的文件夹&#xff0c;双击打开【Setup】文件夹…

数据库开发之子查询的详细解析

1.4 子查询 1.4.1 介绍 SQL语句中嵌套select语句&#xff0c;称为嵌套查询&#xff0c;又称子查询。 SELECT * FROM t1 WHERE column1 ( SELECT column1 FROM t2 ... ); 子查询外部的语句可以是insert / update / delete / select 的任何一个&#xff0c;最常见…

论文速递|Management Science 11月文章合集(下)

添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 编者按 在本系列文章中&#xff0c;我们梳理了运筹学顶刊Management Science11月份发布的47篇文章的基本信息&#xff0c;旨在帮助读者快速洞察行业最新动态。本文为第三部分。 文章1 ● 题目&#xff1a;…

软件测试/测试开发丨Linux 三剑客与管道使用

1、 程序运行环境输入与输出 标准输入 0 read a;echo $a标准输出 1 echo ceshiren.com错误输出 ls not_exist_dir 2、 管道重定向 管道与管道之间可以重定向管道与文件之间可以重定向 echo 11 > /tmp/1 read var </tmp/1错误输出&#xff1a; ls not_exist_dir > /…

Python实现【亚马逊商品】数据采集

前言 亚马逊公司&#xff0c;是美国最大的一家网络电子商务公司&#xff0c;位于华盛顿州的西雅图 是网络上最早开始经营电子商务的公司之一&#xff0c;亚马逊成立于1994年 今天教大家用Python批量采集亚马逊平台商品数据&#xff08;完整代码放在文末&#xff09; 地址&#…

《数据库开发实践》之触发器【知识点罗列+例题演练】

一、什么是触发器&#xff1f; 1.概念&#xff1a; 简单来说触发器就是一种特殊的存储过程&#xff0c;在数据库服务器触发事件的时候会自动执行其SQL语句集。 2.构成四要素&#xff1a; &#xff08;1&#xff09;名称&#xff1a;要符合标识符命名规则 &#xff08;2&am…

在线课程学习管理

### 起步1. 使用 IDEA 导入项目 2. 执行 sql 目录下的online_study_system.sql 文件 3. 修改 mysql.properties 中数据库连接信息 4. 运行程序| 用户名| 密码 | | ------------- |:-------------| | admin | 123 | | 李老师 | 123 | | 张老师 | 123 | | 刘老师 | 123 | | 曹老师…

【Redis技术专区】「原理分析」探讨Redis6.0为何需要启用多线程

探讨Redis 6.0为何需要启用多线程 背景介绍开启多线程多线程的CPU核心配置IO多线程模式单线程处理方式多线程处理方式 为什么要开启多线程&#xff1f;充分利用多核CPU提高网络I/O效率响应现代应用需求 多线程实现启用多线程 最后总结 背景介绍 在Redis 6.0版本中&#xff0c;…

Qt+Opencv:人脸检测

话接上一篇&#xff0c;我们仍使用在上篇《QtOpencv&#xff1a;Qt中部署opencv》创建的Qt项目来测试opencv提供的sample。 在正式开始本篇之前&#xff0c;我们先说做一下准备工作&#xff1a; 一、opencv官方文档 学习最权威和最可靠的方式&#xff0c;就是阅读官方文档和…

扩散式过滤器 水泵角通除污器 0阻力过滤器直角过滤器工作原理

​ 1&#xff1a;扩散式除污器过滤器介绍 扩散除污器是一种在多个领域都有应用的设备&#xff0c;例如在泵站中用于拦截介质中的杂质&#xff0c;净化介质&#xff0c;保护管路&#xff0c;提高水泵效率&#xff0c;延长水泵寿命等。它还可以方便地进行变径处理&#xff0c;可以…

互联网大厂面试题目

阿里篇 1.1.1 如何实现一个高效的单向链表逆序输出&#xff1f; 1.1.2 已知sqrt(2)约等于1.414&#xff0c;要求不用数学库&#xff0c;求sqrt(2)精确到小数点后10位 1.1.3 给定一个二叉搜索树(BST)&#xff0c;找到树中第 K 小的节点 1.1.4 LRU缓存机制 1.1.5 关于epoll和…

【大数据Hive】hive 运算符使用详解

目录 一、前言 二、hive 运算符分类 三、hive 运算符操作演示 3.1 数据准备 创建表dual 加载一个文件dual.txt到dual表中 模拟测试 3.2 关系运算符 is null空值判断 is not null 非空值判断 like使用 3.3 算术运算符 取整操作 取余操作: % 位与操作: & …

ES应用_ES原理

1 ES简介 Elasticsearch&#xff1a;基于Apache Lucene并使用Java开发的分布式开源搜索和分析引擎。是 Elastic Stack 的核心&#xff0c;它集中存储您的数据。 Elastic Stack&#xff1a;包括 Elasticsearch、Logstash 、 Kibana 和Beats &#xff08;也称为 ELK Stack&…

next.js 开发网站的hello world

本文介绍建立一个简单的next.js 工程&#xff0c;以及简单修改。然后也简单说了2种路由方式的选择。 开始next.js工程前需要node.js &#xff0c; 还需要编辑器&#xff0c;我这里选择的是visual code。如果没有安装node.js 请参考下&#xff1a; visual code 下的node.js的he…

redis容灾的方案设计

背景 今年各个大厂的机房事故频繁&#xff0c;其中关键组件Redis是重灾区&#xff0c;本文就来看下怎么做Redis的多机房容灾 Redis多机房容灾方案 1.首先最最直观的是直接利用Redis内部的主从数据同步来进行灾备&#xff0c;但是由于Redis内部的主从实现对机房间的网络延迟等…

归并算法排序

目录 归并排序 逆序对的数量 归并排序 题目如下&#xff1a; 给定你一个长度为 n 的整数数列。 请你使用归并排序对这个数列按照从小到大进行排序。 并将排好序的数列按顺序输出。 输入格式&#xff1a; 输入共两行&#xff0c;第一行包含整数 n。 第二行包含 n 个整数&…

16.综合项目实战

一、基础演练&#xff1a; 1、建库、建表 # 创建数据库 create database mysql_exampleTest; use mysql_exampleTest; # 学生表 CREATE TABLE Student( s_id VARCHAR(20), s_name VARCHAR(20) NOT NULL DEFAULT , s_birth VARCHAR(20) NOT NULL DEFAULT , s_sex VARC…