(C++) 拷贝构造函数

news2024/11/29 0:55:55

目录

一、基本介绍

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

三、拷贝构造函数

四、传参时的问题

五、完整代码


一、基本介绍

        拷贝构造函数是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;
}

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

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

相关文章

深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈

深入浅出图解C#堆与栈 C# HeapingVS Stacking第一节 理解堆与栈 [深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈](https://mp.csdn.net/mdeditor/101021023)[深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基本工作原理](https://mp.csdn.n…

Avalonia学习(十六)-Mapsui

今天开始继续Avalonia练习。 本节&#xff1a;Mapsui 1.引入 Mapsui.Avalonia 2.项目引入 前台代码 <Window xmlns"https://github.com/avaloniaui"xmlns:x"http://schemas.microsoft.com/winfx/2006/xaml"xmlns:vm"using:MapsuiAvalonia.Vi…

C语言之分支与循环【附6个练习】

文章目录 前言一、什么是语句&#xff1f;1.1 表达式语句1.2 函数调用语句1.3 控制语句1.4 复合语句1.5 空语句 二、分支语句&#xff08;选择结构&#xff09;2.1 if语句2.1.1 悬空else2.1.2 练习&#xff08;1. 判断一个数是否为奇数 2. 输出1-100之间的奇数&#xff09; 2.2…

postman win7 低版本 postman7.0.9win64 postman7.0.9win32

百度网盘&#xff1a; postman7.0.9win64&#xff1a; 链接: https://pan.baidu.com/s/18ck9tI0r9Pqoz36MOwwnnQ 提取码: rkf7 postman7.0.9win32&#xff1a; 链接: https://pan.baidu.com/s/1HrpGPrgvVzyAcjdHuwVOpA 提取码: ke5k win7系统安装postman&#xff0c;可能会…

侯捷C++ 2.0 新特性

关键字 nullptr and std::nullptr_t auto 一致性初始化&#xff1a;Uniform Initialization 11之前&#xff0c;初始化方法包括&#xff1a;小括号、大括号、赋值号&#xff0c;这让人困惑。基于这个原因&#xff0c;给他来个统一&#xff0c;即&#xff0c;任何初始化都能够…

实验六——cache模拟器实验

前言 本次实验的主要目的是熟悉cache的原理。加深对cache的映像规则、替换方法、cache命中与缺失的理解。通过实验对比分析映像规则对cache性能的影响。 实验内容一&#xff1a;熟悉模拟程序 阅读给出的cache模拟程序&#xff08;cachesimulator.cpp&#xff09;&#xff0c;…

空间域图像增强之直方图均衡的python代码实现——冈萨雷斯数字图像处理

原理 直方图&#xff1a; 图像的直方图是一个图像中像素强度值分布的图表。 对于灰度图像&#xff0c;直方图展示了每个灰度级出现的频率。 直方图均衡步骤&#xff1a; 计算累积分布函数&#xff08;CDF&#xff09;&#xff1a;首先&#xff0c;计算图像的直方图&#xff0…

oracle物化视图

物化视图定义 视图是一个虚拟表&#xff08;也可以认为是一条语句&#xff09;&#xff0c;基于它创建时指定的查询语句返回的结果集&#xff0c;每次访问它都会导致这个查询语句被执行一次&#xff0c;为了避免每次访问都执行这个查询&#xff0c;可以将这个查询结果集存储到…

C语言与人生:数组交换和二分查找

少年们&#xff0c;大家好。我是博主那一脸阳光&#xff0c;今天和分享数组交换和二分查找。 前言&#xff1a;探索C语言中的数组交换操作与二分查找算法 在计算机编程领域&#xff0c;特别是以C语言为代表的低级编程语言中&#xff0c;对数据结构的理解和熟练运用是至关重要的…

C# 如何读取Excel文件

当处理Excel文件时&#xff0c;从中读取数据是一个常见的需求。通过读取Excel数据&#xff0c;可以获取电子表格中包含的信息&#xff0c;并在其他应用程序或编程环境中使用这些数据进行进一步的处理和分析。本文将分享一个使用免费库来实现C#中读取Excel数据的方法。具体如下&…

Android 跨进程之间通信(IPC)方式之BroadcastReceiver

Android 跨进程之间通信 Android 跨进程之间通信(IPC)方式之BroadcastReceiverAndroid 跨进程之间通信(IPC)方式之ContentProvider 文章目录 Android 跨进程之间通信前言一、关于系统广播二、如何利用BroadcastReceiver跨进程通信1.创建广播接收器2.清单文件中声明注册3.发送广…

08-接口文档管理工具-项目集成knife4j__ev

2、knife4j快速入门 2.1 knife4j介绍 knife4j是为Java MVC框架集成Swagger生成Api文档的增强解决方案,前身是swagger-bootstrap-ui,取名kni4j是希望它能像一把匕首一样小巧,轻量,并且功能强悍! gitee地址&#xff1a;knife4j: Knife4j是一个集Swagger2 和 OpenAPI3为一体的增…

目标检测-Two Stage-Mask RCNN

文章目录 前言一、Mask RCNN的网络结构和流程二、Mask RCNN的创新点总结 前言 前文目标检测-Two Stage-Faster RCNN提到了Faster RCNN主要缺点是&#xff1a; ROI Pooling有两次量化操作&#xff0c;会引入误差影响精度 Mask RCNN针对这一缺点做了改进&#xff0c;此外Mask …

功能真强大!5个令人惊叹的 Jupyter 黑科技

Jupyter 是一种功能强大的交互式计算环境&#xff0c;被广泛应用于数据分析、机器学习、科学计算等领域。 除了常见的基本功能外&#xff0c;Jupyter还隐藏着许多令人惊叹的黑科技&#xff0c;这些功能可以帮助用户更高效地完成工作&#xff0c;提升工作体验。 在本文中&…

.NET Core中灵活使用反射

前言 前段时间有朋友问道一个这样的问题&#xff0c;.NET Core中如何通过Attribute的元数据信息来调用标记的对应方法。我第一时间想到的就是通过C#反射获取带有Custom Attribute标记的类&#xff0c;然后通过依赖注入&#xff08;DI&#xff09;的方式获取对应服务的方法并通…

Elasticsearch8集群部署

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 本文记录在3台服务器上离线搭建es8.7.1版本集群。 1. 修改系统配置 1.1 hosts配置 在三台es节点服务器加入hostname解析&…

电脑系统报错msvcr100.dll丢失的解决方法

本文将详细介绍msvcr100.dll的作用、丢失原因以及解决方法。 一、msvcr100.dll是什么文件&#xff1f; msvcr100.dll是Microsoft Visual C 2010 Redistributable Package的一部分&#xff0c;它是运行许多Windows应用程序所必需的动态链接库文件。它包含了C运行时库&#xff…

AI时代系列丛书(由北京大学出版社出版)

前言 在AI时代&#xff0c;程序员面临着新的机遇和挑战。为了适应这个快速发展的时代&#xff0c;掌握新技能并采取相应的应对策略是至关重要的。 对于办公人员或程序员来说&#xff0c;利用AI可以提高工作效率。例如&#xff0c;使用AI助手可以帮助自动化日常的重复性工作&a…

C++八股学习心得.1

1.面向对象程序设计 C 是一种静态类型的、编译式的、通用的、大小写敏感的、不规则的编程语言&#xff0c;支持面向过程编程、面向对象编程和泛型编程。 C 完全支持面向对象的程序设计&#xff0c;包括面向对象开发的四大特性&#xff1a;封装、抽象、继承、多态 2.标准库 …

车牌识别系统设计与实现

车牌识别系统设计与实现 项目概述 本项目旨在设计和实现一套车牌识别系统&#xff0c;通过使用车牌字符数据集进行训练&#xff0c;应用OpenCV、CNN&#xff08;卷积神经网络&#xff09;和PyQt5技术&#xff0c;实现车牌图像的预处理、位置选定、定位、字符分割和最终的车牌…