Cherno C++学习笔记 P45 拷贝与拷贝构造函数

news2025/1/25 1:27:35

在这一篇文章当中,我们会学习一下C++当中的复制以及复制构造函数,当然,还会用一个相对比较完整的代码类作为例子。这也是我们第一次写一个相对比较长的和完整的代码。

对于C++来说,理解复制是非常重要的,因为整个C++变成可以说就是对于内存的操作,而复制就是有关于将内存中的数据重新复制一份并粘贴到一个新的内存地址上的过程。但是我们需要注意的是,复制这件事情是非常花费时间和内存的,所以在没有必要的情况下,我们应该尽可能避免不必要的复制,尤其是如果我们只是读取,或者说是希望能够影响输入的变量本身的情况下,就更不希望是复制了。

通常来讲,我们的赋值过程都是把变量赋值了一遍,比如下面最简单的:

int a = 2;
int b = a;

只要我们这么写,那么a和b就是完全两个不同的变量了,有着不同的存储地址,修改其中一个不会对另一个造成任何影响。但是如果我们复制粘贴的对象是指针的话:

int* ptr = &a;
int* ptr2 = ptr;

ptr2和ptr也是两个不同的指针变量,它们也有着不同的地址,但是问题在于,它们指向了相同的一块内存。所以如果我们对其中一个指针指向的内容进行了修改,那么另一个指针指向的内容也会被影响,因为这是相同的内容;但是如果我们单纯修改指针本身,那么不会影响另一个指针的值。

引用是最特殊的,因为我们只要修改了引用,就一定会影响到被引用对象,所以引用不存在复制之后与你无瓜的情况。

这篇文章当中,我们选择以string类为例,我们自己动手写自己的string类,然后看看我们需要什么,会出现什么问题。

首先我们需要原始的char指针和统计string长度的size变量:

#include<iostream>
#include<memory>
#include<cstdlib>


class String {
private:
	char* m_String;
	unsigned int m_Size;
}

同时,我们需要一个构造函数,在我们给它输入一个const char*类型变量的时候,可以做到对这个对象进行赋值,方式是C风格的拷贝内存函数memcpy,同时存储它的长度信息。需要注意的一点是,因为我们需要尾部有一个终止符,所以我们需要留出更长的一段,然后为它手动添加终止符,不然的话我们就又会面临各种烫烫烫了。

String(const char* str) {
	m_Size = strlen(str);
	m_String = new char[m_Size + 1];
	memcpy(m_String, str, m_Size);
	m_String[m_Size] = 0;
}

因为我们将长度和字符串信息设置为private,所以可以考虑添加两个Get函数,虽然其实没啥必要

unsigned int GetLen() const {
	return m_Size;
}
char* GetString() const {
	return m_String;
}

为了方便我们输出结果,所以我们需要重载一下<<运算符:

static std::ostream& operator<<(std::ostream& stream, String str) {
	std::cout << str.m_String;
	return stream;
}

当然我们这么写的话,涉及到了使用private变量,所以我们需要声明一下这个函数是类String的友元函数,方式就是直接加一个前缀friend,然后声明在类的定义里面。

friend static std::ostream& operator<<(std::ostream& stream, String str);

或者我们可以直接使用GetString函数,也没有什么问题。

好了,现在我们可以用一个字符串实例化这个类的对象并进行输出啦:

String name = "Cherno";
std::cout << name << std::endl;

还有,如果我们想要通过查找序号来修改字符串当中的某一个值,那么就可以采用这样的方式,重载[],与此同时我们还可以做一个越界检查:

char& operator[](unsigned int m) const {
	if (m > m_Size - 1)
		std::cout << "The index exceed the max length!";
	abort();
	return m_String[m];
}

好了,我们可以进行如下修改且不会出错了:

name[2] = 'a';
std::cout << name << std::endl;

那么接下来,我们可以尝试一下赋值操作,将一个String类对象赋值到另一个String类对象上去看看会发生什么:

String name = "Cherno";
String other = name;
std::cout << name << std::endl;
std::cout << other << std::endl;

看起来是没有任何问题的。但是如果我们进入到调试模式,会发现这样一个问题:

name和other两个本应没有关系的变量,居然成员m_String拥有着完全相同的地址!这个看起来是很不符合常理的,因为这意味着一旦其中一个的内存被释放,另一个也会马上被释放。如果我们写一个析构函数,这一点会体现得更加明显。

这是因为什么?因为我们在复制的时候,确实是复制了全部的类当中的变量,包括指针,也就意味着两个实例化的对象当中指针所指向的内容是完全一样的!那么当我们在删除的时候,会将同一块内存释放两次,从而造成错误。

那么如何解决这个问题?实际上我们需要分配一块新的内存,然后指向这块新的内存,这样才会不至于出现刚才这种错误。这种方式我们被称为深拷贝,这样才能复制整个变量,而为了实现深拷贝,我们需要使用的是拷贝构造函数,在我们使用一个变量去赋值给另一个变量的时候,就会被调用。而我们刚才出现错误的拷贝方式就被称为浅拷贝。

拷贝构造函数的形式是什么样的?如下所示,这个函数会在赋值的时候被调用:

String(const String& other){
}

而我们之前做的事情相当于在里面这样写:

String(const String& other){
    memcpy(this, &other, sizeof(String);
}

如果我们不想允许拷贝,可以写成

String(const String& other) = delete;

想要真正的进行深拷贝,我们需要这样重新分配内存并用指针指向这部分内存:

String(const String& other)
    :m_Size(other.GetLen()){
	m_String = new char[m_Size + 1];
	memcpy(m_String, other.GetString(), m_Size);
	m_String[m_Size] = 0;
}

这样我们再进行拷贝的时候,进入调试模式就会看到m_String指向的地址不同了:

于是我们整个类的定义就如下所示了:

#include<iostream>
#include<memory>
#include<cstdlib>


class String {
private:
	char* m_String;
	unsigned int m_Size;
public:
	String(const char* str) {
		m_Size = strlen(str);
		m_String = new char[m_Size + 1];
		memcpy(m_String, str, m_Size);
		m_String[m_Size] = 0;
	}
	~String() {
		delete[] m_String;
		m_Size = 0;
	}
	String(const String& other)
	    :m_Size(other.GetLen()){
		m_String = new char[m_Size + 1];
		memcpy(m_String, other.GetString(), m_Size);
		m_String[m_Size] = 0;
	}
	unsigned int GetLen() const {
		return m_Size;
	}
	char* GetString() const {
		return m_String;
	}
	char& operator[](unsigned int m) const {
		if (m > m_Size - 1)
			std::cout << "The index exceed the max length!";
		abort();
		return m_String[m];
	}

	friend static std::ostream& operator<<(std::ostream& stream, const String& str);
};

static std::ostream& operator<<(std::ostream& stream, const String& str) {
	std::cout << str.m_String;
	return stream;
}


int main() {
	String name = "Cherno";
	String other = name;
	std::cout << name << std::endl;
	std::cout << other << std::endl;

	std::cin.get();
}

但是我们需要指出的一点是,如果函数参数只是变量本身,那么我们在调用函数的时候也会发生拷贝,而这在很多时候是会非常浪费时间的,因为我们在不想改变变量的值的情况下,复制是没有任何意义的,只会导致效率的降低。所以always pass value by const reference。即使我们想要复制,我们也可以在函数当中自己手动定义一个新变量然后进行复制。

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

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

相关文章

uniapp入门 01创建项目模版

0安装 hbuilder x 标准版 1.创建模版工程 2.创建官方 案例工程 index.uvuewen 文件解析 <!-- 模版 标签 --> <template><view></view></template><!-- 脚本 --> <script>export default {data() {return {}},onLoad() {},methods:…

kubeadm安装K8s高可用集群之集群初始化及master/node节点加入calico网络插件安装

系列文章目录 1.kubeadm安装K8s高可用集群之基础环境配置 2.kubeadm安装K8s集群之高可用组件keepalivednginx及kubeadm部署 3.kubeadm安装K8s高可用集群之集群初始化及master/node节点加入集群calico网络插件安装 kubeadm安装K8s高可用集群之集群初始化及master/node节点加入ca…

【NLP】序列到序列(seq2seq)建模工具fairseq使用详解

文章目录 一、fairseq简介二、安装方式2.1 pip安装2.2 源码安装 三、fairseq命令工具3.1 fairseq-preprocess3.2 fairseq-train3.3 fairseq-generate3.4 fairseq-interactivate3.5 fairseq-score3.6 fairseq-eval-lm 4. 常见报错报错1 参考资料 一、fairseq简介 fairseq 是 Fa…

28、论文阅读:基于像素分布重映射和多先验Retinex变分模型的水下图像增强

A Pixel Distribution Remapping and Multi-Prior Retinex Variational Model for Underwater Image Enhancement 摘要介绍相关工作基于模型的水下图像增强方法&#xff1a;无模型水下图像增强方法&#xff1a;基于深度学习的水下图像增强方法&#xff1a; 论文方法概述像素分布…

ArkTs组件的学习

一. AlphabetIndexer 可以与容器组件联动用于按逻辑结构快速定位容器显示区域的组件 参数名类型必填说明arrayValueArray<string>是字母索引字符串数组&#xff0c;不可设置为空selectednumber是初始选中项索引值若超出索引值范围则取默认值0 class Lxr{tImg:Resource…

Python读取Excel批量写入到PPT生成词卡

一、问题的提出 有网友想把Excel表中的三列数据&#xff0c;分别是&#xff1a;单词、音标和释义分别写入到PPT当中&#xff0c;每一张PPT写一个单词的内容。这种批量操作是python的强项&#xff0c;尤其是在办公领域&#xff0c;它能较好地解放双手&#xff0c;读取Excel表后…

百度面试手撕 go context channel部分学习

题目 手撕 对无序的切片查询指定数 使用context进行子协程的销毁 并且进行超时处理。 全局变量定义 var (startLoc int64(0) // --- 未处理切片数据起始位置endLoc int64(0) // --- 切片数据右边界 避免越界offset int64(0) // --- 根据切片和协程数量 在主线程 动态设…

Otsu 二值化算法:原理、实现与应用

摘要&#xff1a; 本文深入探讨了 Otsu 二值化算法&#xff0c;详细阐述其原理&#xff0c;包括类间方差的计算与阈值确定机制。分别给出了该算法在 C#、Python 和 C 中的实现代码示例&#xff0c;并对代码进行了详细注释与分析。此外&#xff0c;还探讨了 Otsu 二值化算法在图…

uniApp使用腾讯地图提示未添加maps模块

uniApp使用腾讯地图&#xff0c;打包提示未添加maps模块解决方案 这是报错信息&#xff0c;在标准基座运行的时候是没问题的&#xff0c;但是打包后会提示未添加&#xff0c;可以通过在mainfest里面把地图插件上腾讯地图的key更换高德地图的key&#xff0c;定位服务可以继续用腾…

Deepin/Linux clash TUN模式不起作用,因网关导致的问题的解决方案。

网关导致的问题的解决方案 查看路由 ip route寻找默认路由 默认路由应当为Mihomo default dev Mihomo scope link 如果不是&#xff0c;则 sudo ip route add default dev Mihomo在clash TUN开关状态发生变化时&#xff0c;Mihomo网卡会消失&#xff0c;所以提示找不到网卡…

scala中正则表达式的使用

正则表达式&#xff1a; 基本概念 在 Scala 中&#xff0c;正则表达式是用于处理文本模式匹配的强大工具。它通过java.util.regex.Pattern和java.util.regex.Matcher这两个 Java 类来实现&#xff08;因为 Scala 运行在 Java 虚拟机上&#xff0c;可以无缝使用 Java 类库&…

apache应用(客户机地址限制、用户授权限制、日志分割、AWStats日志分析)

目录 一、 客户机地址限制 二、 用户授权限制 三、 日志分割 使用rotatelogs分割工具 使用第三方工具cronolog 四、 AWStats日志分析 具体的apache软件安装可以阅读我之前的文章apache安装https://blog.csdn.net/m0_68472908/article/details/139348739?spm1001.2014.300…

护士资格实践题库(含解析)

1.患者女&#xff0c;30岁。诊断类风湿关节炎入院&#xff0c;经使用药物治疗后患者关节疼痛减轻&#xff0c;但出现体重增加、满月脸、向心性肥胖。提示存在何种药物的副作用&#xff08; &#xff09; A.泼尼松 B.环磷酰胺 C.硫唑嘌呤 D.吲哚美辛 E.阿司匹林 【答案】…

网络安全概论——防火墙原理与设计

一、防火墙概述 防火墙是一种装置&#xff0c;它是由软件/硬件设备组合而成&#xff0c;通常处于企业的内部局域网与 Internet 之间&#xff0c;限制 Internet 用户对内部网络的访问以及管理内部用户访问 Internet 的权限。换言之&#xff0c;一个防火墙在一个被认为是安全和可…

接口测试-Fidder及jmeter使用

一、接口测试的基础 1.接口的含义 也叫做API&#xff0c;是一组定义、程序及协议的集合&#xff0c;提供访问一组例程的能力&#xff0c;无需访问源码获理解内部工作细节 2.接口的分类 代码内部的接口&#xff0c;程序模块间的接口&#xff0c;对于程序接口测试&#xff0c;需…

postman设置cookie

postman发送请求的时候&#xff0c;如何顺带cookie? 示例&#xff1a; Cookie: locale_areazh; contryzh_cn;

Java-31 深入浅出 Spring - IoC 基础 启动IoC XML与注解结合的方式 配置改造 applicationContext.xml

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

jmeter连接mysql

查询mysql数据库版本 SELECT VERSION(); 下载jmeter mysql 驱动jar包&#xff0c;版本低于mysql版本&#xff0c;放在jmeter的lib 路径下 MySQL :: Download MySQL Connector/J (Archived Versions) 添加JDBC Connection Configuration 填写 variable name 及数据库信息 注意…

计算机网络基础(2):网络安全/ 网络通信介质

1. 网络安全威胁 网络安全&#xff1a;目的就是要让网络入侵者进不了网络系统&#xff0c;及时强行攻入网络&#xff0c;也拿不走信息&#xff0c;改不了数据&#xff0c;看不懂信息。 事发后能审查追踪到破坏者&#xff0c;让破坏者跑不掉。 网络威胁来自多方面&#xff1a…

数据分析实战—IMDB电影数据分析

1.实战内容 1.加载数据到movies_df&#xff0c;输出前5行&#xff0c;输出movies_df.info(),movies_df.describe() # &#xff08;1&#xff09;加载数据集&#xff0c;输出前5行 #导入库 import pandas as pd import numpy as np import matplotlib import matplotlib.pyplo…