C++之深拷贝进阶

2023-12-23 02:52
文章标签 c++ 进阶 拷贝 之深

本文主要是介绍C++之深拷贝进阶,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

拷贝构造函数的深拷贝进阶版本

赋值运算符重载的深拷贝进阶

总结


上期我们学习了C++中深拷贝的传统版本,今天我们将学习更为高效的版本。

拷贝构造函数的深拷贝进阶版本

传统版本代码如下:

string(string& s):_str(new char[strlen(s._str) + 1])
{strcpy(_str, s._str);
}

进阶版本代码如下:

string(string& s):_str(nullptr)
{string tmp(s._str);std::swap(_str, tmp._str);
}

注意:因为是初始化一个刚创建的对象,我们一定要哦将_str置空,否则其指向了一个未知的空间,等到刚创建的对象的_str成员变量和tmp对象的成员变量_str交换之后,因为未知的空间不是new出来的,所以无法进行释放,也就就会导致tmp._str在最终调用析构函数时会出错。 

图示如下:

解析:不难发现,进阶版本相比较于传统版本,进阶版本并没有像传统版本一样,申请空间之后调用和strcpy函数去进行字符串的拷贝,而是调用构造函数创建并初始化了一个对象tmp, 最终交换了成员变量的指针,从而使新创建的对象的成员指针指向了tmp._str指向的空间。

运行截图如下:

我们发现s1的值成功拷贝构造给了s2。 

赋值运算符重载的深拷贝进阶

传统版本代码如下:

string& operator=(string& s)
{if (this != &s){char* tmp = new char[strlen(s._str) + 1];strcpy(tmp, s._str);delete[]_str;_str = tmp;}return *this;}

进阶版本代码如下:

进阶版本1:

string& operator =(string &s){//为了避免自己给自己进行赋值if (this != &s){string tmp(s);std::swap(_str, tmp._str);}return *this;}

解析:进阶版本的区别是调用拷贝构造函数初始化了一个对象tmp,然后对这tmp对象和我们要进行赋值的对象的成员变量_str进行了交换,最终完成了赋值。 

 进阶版本2:

string& operator =(string s)
{std::swap(_str, s._str);return *this;
}

版本2和版本1的区别是什么?

版本1要考虑是否给自己赋值,但是版本2避开了这种情况,版本2的形参就是一个中间变量,我们通过拷贝构造函数用实参对其进行了初始化,此时的形参s和被赋值的对象根部就不可能是同一个对象,但是第1个版本的形参是引用类型,所以就会存在自身给自身赋值的情况。 

图示如下:

解析:通过图示我们可以看出来,赋值运算符重载的深拷贝的进阶版本实质上也是创建了一个中间对象,最终通过交换函数实现了两个对象的成员变量_str的交换从而实现了赋值,与拷贝构造函数的深拷贝的进阶版本不同的是,赋值运算符重载是针对两个已经存在的对象进行赋值,两个对象的指针交换之后,最终都会调用各自的析构函数完成资源的清理。 

运行截图如下: 

我们发现s1的值成功赋值给了s3。 

注意:不管是拷贝构造函数的进阶版本还是赋值运算符重载的进阶版本,其实本本质都是对函数的复用,拷贝构造函数深拷贝的进阶在创建对象时复用了构造函数。赋值运算符重载的深拷贝进阶复用了拷贝构造函数的深拷贝进阶,因为拷贝构造函数的深拷贝进阶又复用了构造函数,所以本质上,拷贝构造函数的赋值运算深拷贝进阶和符重载的深拷贝进阶都是复用了构造函数。 

总结

到了这里我们自己创建的string类的深拷贝和浅拷贝的全部知识已经全部学习完成了,目前的string类整个代码为:

namespace yjd
{class string{public://构造函数string(const char* str):_str(new char[strlen(str) + 1]){strcpy(_str, str);}//拷贝构造函数的深拷贝传统版本string(string& s):_str(new char[strlen(s._str) + 1]){strcpy(_str, s._str);}//赋值运算符重载的深拷贝传统版本string& operator=(string& s){if (this != &s){char* tmp = new char[strlen(s._str) + 1];strcpy(tmp, s._str);delete[]_str;_str = tmp;}return *this;}//拷贝构造函数的深拷贝进阶版本string(string& s):_str(nullptr){string tmp(s._str);std::swap(_str, tmp._str);}//赋值运算符重载的深拷贝进阶版本1string& operator =(string &s){//为了避免自己给自己进行赋值if (this != &s){string tmp(s);std::swap(_str, tmp._str);}return *this;}//赋值运算符重载的深拷贝进阶版本2string& operator =(string s){std::swap(_str, s._str);return *this;}~string(){delete [] _str;_str = nullptr;}private:char* _str;};
}

注意:以上整体代码有两个个需要注意的地方:

1.首先我们自定义了命名空间,不然就会和库中的string类产生冲突。

2.构造函数的形参的类型为const char*,因为const char*不仅可以接收常量字符串的地址,也可以接收字符数组类型的字符串地址,因为权限不变和权限缩小是可以的,而char*只可以接收数组类型的字符串,因为权限放大是不允许的,string类的字符串我们就可以定性为数组字符串,本质上就是一个数组。所以一般情况下我们用const char*类型去接收各种字符串的地址。

本期内容到此结束^_^ 

这篇关于C++之深拷贝进阶的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/526460

相关文章

深入理解C++ 空类大小

《深入理解C++空类大小》本文主要介绍了C++空类大小,规定空类大小为1字节,主要是为了保证对象的唯一性和可区分性,满足数组元素地址连续的要求,下面就来了解一下... 目录1. 保证对象的唯一性和可区分性2. 满足数组元素地址连续的要求3. 与C++的对象模型和内存管理机制相适配查看类对象内存在C++中,规

Java中ArrayList的8种浅拷贝方式示例代码

《Java中ArrayList的8种浅拷贝方式示例代码》:本文主要介绍Java中ArrayList的8种浅拷贝方式的相关资料,讲解了Java中ArrayList的浅拷贝概念,并详细分享了八种实现浅... 目录引言什么是浅拷贝?ArrayList 浅拷贝的重要性方法一:使用构造函数方法二:使用 addAll(

在 VSCode 中配置 C++ 开发环境的详细教程

《在VSCode中配置C++开发环境的详细教程》本文详细介绍了如何在VisualStudioCode(VSCode)中配置C++开发环境,包括安装必要的工具、配置编译器、设置调试环境等步骤,通... 目录如何在 VSCode 中配置 C++ 开发环境:详细教程1. 什么是 VSCode?2. 安装 VSCo

C++11的函数包装器std::function使用示例

《C++11的函数包装器std::function使用示例》C++11引入的std::function是最常用的函数包装器,它可以存储任何可调用对象并提供统一的调用接口,以下是关于函数包装器的详细讲解... 目录一、std::function 的基本用法1. 基本语法二、如何使用 std::function

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

【C++ Primer Plus习题】13.4

大家好,这里是国中之林! ❥前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。有兴趣的可以点点进去看看← 问题: 解答: main.cpp #include <iostream>#include "port.h"int main() {Port p1;Port p2("Abc", "Bcc", 30);std::cout <<

C++包装器

包装器 在 C++ 中,“包装器”通常指的是一种设计模式或编程技巧,用于封装其他代码或对象,使其更易于使用、管理或扩展。包装器的概念在编程中非常普遍,可以用于函数、类、库等多个方面。下面是几个常见的 “包装器” 类型: 1. 函数包装器 函数包装器用于封装一个或多个函数,使其接口更统一或更便于调用。例如,std::function 是一个通用的函数包装器,它可以存储任意可调用对象(函数、函数

C++11第三弹:lambda表达式 | 新的类功能 | 模板的可变参数

🌈个人主页: 南桥几晴秋 🌈C++专栏: 南桥谈C++ 🌈C语言专栏: C语言学习系列 🌈Linux学习专栏: 南桥谈Linux 🌈数据结构学习专栏: 数据结构杂谈 🌈数据库学习专栏: 南桥谈MySQL 🌈Qt学习专栏: 南桥谈Qt 🌈菜鸡代码练习: 练习随想记录 🌈git学习: 南桥谈Git 🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈�

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象