【C++】string类的模拟实现

目录

一、前言

二、模拟实现

1、构造函数

2、拷贝构造函数

3、operator=

4、operator[]

5、迭代器

6、string类的比较

7、string类的扩容

7.1、reserve

7.2、resize

8、string类的尾插

8.1、push_back 与 append

8.2、operator+=

9、string类的insert

9.1、插入字符

9.2、插入字符串

10、string类的erase

11、string类的swap

12、string类的find

12.1、查找字符

12.2、查找字符串

13、string类的流

13.1、流插入

13.2、流提取

三、不同编译器下的string类


一、前言

 我在上篇文章《string类的使用》中已经对string类进行了简单的介绍,大家学习之后已经可以正常的使用string类了。本篇文章主要针对string类的底层进行较为深入的剖析,以方便同学们更加深刻的理解这部分内容。

二、模拟实现

1、构造函数

 string类的构造函数的大致格式如下:

 在构造函数的初始化列表中,只对成员变量 _size 进行了初始化,这是因为初始化列表中的初始化顺序是以成员变量的声明顺序为准的,为了防止有些情况下把 _capacity 声明在 _size 之前,造成 _capacity 被初始化为随机值的问题,就把 _capacity 的初始化工作放到了函数体中进行。当 _capacity 被初始化完成后,在函数体内使用关键字 new 开辟空间,空间大小为 _capacity + 1 (_capacity为string类最多存放字符串的个数,不包括 '\0' ),最后把字符串拷贝到对象中。

 而在默认构造函数的第 11 行中,我们 new 了一个 char 类型大小的空间,注意一定要带上方括号 '[]' ,这是为了以后写析构函数时使用 delete[] 更加统一。

 以上为string类的构造函数与析构函数的整体框架,现在对这个框架可以做一些优化。我们知道,对于一个类,最好提供一个缺省的默认构造函数,那么该如何缺省呢?

 1、第一种写法

 对构造函数的参数进行缺省,缺省值为 nullptr ,这种写法是错误的,因为在初始化列表中使用了 strlen 函数,对空指针进行了解引用,造成程序崩溃。

 2、第二种写法

 

 对构造函数的参数进行缺省,缺省值为 '\0' ,这种写法也是错误的,因为类型不匹配, '\0' 被转换成了 int 类型,同样被当作了空指针,造成程序崩溃。

 3、第三种写法

 对构造函数的参数进行缺省,缺省值为常量字符串 "\0" ,这种写法才是正确的,也可以写成:

2、拷贝构造函数

当我们使用默认拷贝构造函数来实例化对象时,会出现以下错误:

 这就是由于浅拷贝问题引起的。

 浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。这方面内容我曾在文章《类和对象(二)》中有过较为详细的讲解。

 为了避免出现浅拷贝问题,我们需要自己写一个深拷贝的拷贝构造函数:

3、operator=

与拷贝构造函数相同,string类的赋值运算符重载同样要考虑深拷贝的问题,具体操作如下:

 首先进行 if 判断,防止自己给自己赋值,造成资源浪费与不必要的错误,再在条件语句中先 new 出指定大小的空间,并把字符串拷贝进来之后,再 delete[] 掉被复制对象原有的空间,以防止申请空间失败,造成了对原有对象的破坏。

4、operator[]

 const 非const 函数构成重载,以满足不同情况时的调用需求。

5、迭代器

我们这里模拟实现的是原生指针实现的迭代器:

 begin() 指向字符串第一个字符的位置, end() 指向字符串最后一个字符的下一个位置。

为了满足不同情况下的调用需求,分别设置了普通类型的迭代器和 const 类型的迭代器。

 由于迭代器已经被我们模拟实现了,所以 范围for 也已经被实现了:

6、string类的比较

 通过多个复用,可以较低成本实现所有的比较逻辑。需要注意的是,所有在内部不用修改成员变量数据的成员函数,最好都写成 const 类型

7、string类的扩容

7.1、reserve

  在写 reserve 时,考虑到有可能指定大小 n 还要小于对象原本空间大小,造成在使用 strcpy 进行字符串拷贝时出现越界的错误,所以需要使用 if 语句判断一下。

 需要注意的是,我们 new 出新空间的大小一定要比指定大小 n 1 , 用于存放 '\0' 。最后把字符串最大有效字符数目 _capacity 赋值为 n

7.2、resize

 我们知道string类成员函数 resize 的功能是指定对象占据空间的大小,并把空余的部分填充成指定字符。那么就可以分为如下三种情况: n <= _size _size < n <= _capacity n > _capacity ,需要分别讨论:

注意, resize 是不支持缩容操作的,即不会更改 _capacity 的值。因为缩容操作相当于另外开辟一块更小的空间,并把原有空间前 n 个数据拷贝到这个更小的空间中,如果以后又要插入数据,还要重新扩容,代价太大。

8、string类的尾插

8.1、push_back 与 append

 对于 push_back 仅在字符串尾部插入一个字符,可以直接扩容二倍,但是对于 append 在字符串尾部插入一个字符串时,则需要按照需求进行扩容,以防止二倍扩容不够用。

 注意,在 push_back 函数最后,要记得加上 '\0' 。在拷贝字符串时,使用的函数是 strcpy ,而不是 strcat ,这是因为 strcat 需要自己从头向后寻找 '\0' ,效率太低。而用 strcpy 可以直接指定字符串结尾的位置,不用重新寻找。

8.2、operator+=

 通过函数重载与复用,实现了单个字符和字符串的尾插。

9、string类的insert

9.1、插入字符

我们先来看一种错误的写法:

 我们使用这种代码,当 pos 0 时,就会出现报错。这是因为 end 的类型是 size_t ,即无符号整型,所以当 pos == 0  end 0 时,依然可以进入循环,并且 end 的值经过 -- 后变为了最大的整型数值,从而陷入无限的循环。

 为了避免这种错误,我们可以采取如下写法:

9.2、插入字符串

与插入字符的思想相似,具体代码如下:

 需要注意的是,while 循环的条件是 end > pos + len - 1,要把 pos 的内容也拷贝到后面。使用函数 strncpy 来拷贝字符串,防止 '\0' 对字符串造成影响。

10、string类的erase

具体实现如下:

11、string类的swap

string类的swap只交换对象所指向的空间以及对应的成员变量信息,逻辑图如下:

实现代码: 

12、string类的find

12.1、查找字符

实现代码如下:

12.2、查找字符串

实现代码如下:

13、string类的流

我们首先需要知道在类中,流插入与流提取的重载不能写成类的成员函数,因为 this 指针会抢占第一个参数的位置,在《类和对象(三)》中,我们使用的解决方法是通过设置友元函数的方式来使类外函数可以访问类中的 private 成员变量。但是并不是所有的流插入、流提取操作都要设置友元函数的,只要不需要访问 private protect 成员变量就无需设置。

13.1、流插入

实现代码如下:

13.2、流提取

我们首先来看一下一种错误的写法:

 如果采用这种写法,那么我们在输入字符 ' ' '\n' 时,会因为C++默认该字符是多个字符之间的间隔而不读取该字符,从而出现不符合预期的结果。

 为了避免这种错误,我们应该采用函数 get 来读取字符:

 解决了这个问题后,其实还有一个问题,那就是如果该字符串中本来就有字符,再使用我们重载的流插入后出现的结果与库中string类的流插入不符:

 库中string类的流提取直接刷新了对象中字符串的内容,而我们自己实现的流提取是在尾部追加,所以我们可以在流提取函数最前面增加一个 clear 函数:

 又考虑到如果一次性提取的字符数量过多,导致对象进行多次扩容,造成资源的浪费,为了避免这种情况,我们可以开辟一个缓冲数组,用于存储字符,数组满了之后再进行一次性扩容。

 当读取到字符 ' ' '\n' 时,如果 buff 中还有字符,就把这些字符再尾插到对象的字符串中。

三、不同编译器下的string类

我们可以写出下面代码观察在vs下string类对象的大小:

 可以发现我们模拟实现的string类与库中的string类的大小有所区别,这是因为库中的string类里有一个 16 和字符大小的数组, 这方面的内容我曾在文章《string类的使用》中有过讲解。

 而在 Linux 的 g++ 编译器中,string类的大小只有 4 个字节(32位) 或 8 个字节(64位),即一个指针的大小。这与他们的底层实现有关,g++编译器下的string类的结构如下图所示:

 string类中只有一个指针 _ptr ,其他属性以及数据都存放在堆区开辟的空间中,如果后面进行了拷贝构造,则直接实例化出一个新的对象,并把指针指向同一块位置,同时更改 _refcount 的数值:

 在析构时,如果 _refcount 的值大于 1 ,则进行 -- 操作,等于 1 ,就把该空间释放掉。

如果想要修改 s1 s2 的内容,那么谁写谁就触发写时拷贝,再在堆区上另外开辟出一块空间,进行写操作:


 关于string类模拟实现的内容就讲到这里,希望同学们多多支持,如果有不对的地方欢迎大佬指正,谢谢!

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

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

相关文章

deepin15.11无法正常输入汉字问题的解决

1,起因 本来是sougou输入法 但是由于自己突发奇想 在那瞎折腾 一不小心把配置给弄坏了 就再也回不到之前可以正常打印汉字的状态 历经两个小时的折腾 总算是又能输入汉字啦 耗费两个多小时 对当下的我来说时间成本着实有点高 但是把问题给解决了 总算还是有点收获 平时的学习过…

注意力机制 | CNN-BiLSTM-Attention基于卷积-双向长短期记忆网络结合注意力机制多输入单输出回归预测(Matlab程序)

注意力机制 | CNN-BiLSTM-Attention基于卷积-双向长短期记忆网络结合注意力机制多输入单输出回归预测(Matlab程序) 目录 注意力机制 | CNN-BiLSTM-Attention基于卷积-双向长短期记忆网络结合注意力机制多输入单输出回归预测(Matlab程序)预测结果评价指标基本介绍程序设计参…

qt 编译器 调试器

电脑版本&#xff1a;win10 64位 qt版本&#xff1a;based on Qt 5.14.0&#xff08;msvc 2017&#xff0c; 32位&#xff09; Qt Creator 4.11.0 qt安装包&#xff1a;qt-opensource-windows-x86-5.9.9.exe 安装过程一路next&#xff0c;安装完成后&#xff0c;默认使用的…

Spring IoC循环依赖问题

什么是循环依赖 循环依赖其实就是循环引⽤&#xff0c;也就是两个或者两个以上的 Bean 互相持有对⽅&#xff0c;最终形成闭环。⽐如A依赖于B&#xff0c;B依赖于C&#xff0c;C⼜依赖于A。 注意&#xff0c;这⾥不是函数的循环调⽤&#xff0c;是对象的相互依赖关系。循环调…

一个服务端同学的Vue框架入门及实践

做为服务端同学&#xff0c;接触前端代码较少&#xff0c;刚毕业的时候用过 jQuery Bootstrap2/3&#xff0c;当时的感觉就是&#xff0c;容易上手&#xff0c;学习门槛相对较低&#xff0c;另外就是有一个非常成熟的 jQuery 插件库&#xff0c;在这里&#xff0c;几乎可以找到…

vue集成tui.calendar日历组件

vue集成tui.calendar日历组件前言一、简介、效果图二、vue简单集成(集成js版本,没有使用官方的vue2版本)1.引包2.简单示例三、自定义功能1.需求分析、效果展示2.实现思路前言 vue2的集成在git上官方已经给出了demo这里就不贴代码了。本次主要是vue3集成 最近有个功能需要一个日…

重发布实验

基础配置&#xff1a; [r1]int l0 [r1-LoopBack0]ip add 1.1.1.1 24 [r1-LoopBack0]int g0/0/0 [r1-GigabitEthernet0/0/0]ip ad 192.168.12.1 24 [r1-GigabitEthernet0/0/0]int g0/0/1 [r1-GigabitEthernet0/0/1]ip add 192.168.123.1 24 [r1]ospf 1 router-id 1.1.1.1 [r1-o…

自学大数据第12天~Hbase

先留个问题~ERROR: KeeperErrorCode ConnectionLoss for /hbase/master 稍后解决 找到了问题的根因: 查看报错日志 事关tmp文件夹的配置,所以去找一下hbase配置文件中关于这个文件夹的配置项 我的策略是将这个配置项注销掉 然后启动hbase ,之后hmaster就成功启动了; 接着s…

熟练Redis之无处不在的锁

为了保证并发访问的正确性&#xff0c;Redis提供了两种方法,分别是加锁和原子操作 Redis加锁两个问题:一个是&#xff0c;如果加锁操作多&#xff0c;会降低系统的并发访问性能;第二个是&#xff0c;Redis客户端要加锁时&#xff0c;需要用到分布式锁&#xff0c;而分布式锁实…

Coremail奇安信发布2022中国企业邮箱安全性研究:应对ChatGPT带来的安全挑战

日前&#xff0c;广东盈世科技计算机有限公司与奇安信集团联合编写发布《2022中国企业邮箱安全性研究报告》。 报告数据显示&#xff1a;2022年&#xff0c;全国企业邮箱用户共收到各类钓鱼邮件约425.9亿封&#xff0c;相比2021年收到各类钓鱼邮件的342.2亿封增加了24.5%。 一…

华为OD机试用java实现 -【RSA 加密算法】

最近更新的博客 华为od 2023 | 什么是华为od,od 薪资待遇,od机试题清单华为OD机试真题大全,用 Python 解华为机试题 | 机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为od机试,独家整理 已参加机试人员的实战技巧本篇题解:RSA 加密算法 题目 RSA 加密…

【愚人节专场】Java实现定时发送小情话

首先&#xff0c;感谢大佬的帮助~附上大佬的博客以示尊敬https://blog.csdn.net/qq_38591577/article/details/128164308?spm1001.2014.3001.5502 功能实现&#xff1a; 在名为愚人节&#xff0c;实为告白/情人节的日子里&#xff0c;怎么样才能引起TA的关注呢&#xff1f;不…

49天精通Java,第21天,Java内部类,看看文心一言、ChatGPT怎么说

目录文心一言谈Java内部类ChatGPT谈Java内部类下面来聊聊哪吒的见解。一、为什么需要内部类&#xff1f;二、内部类分为四种三、成员内部类1、什么是成员内部类2、代码实例3、成员内部类进阶代码实例4、控制台显示5、外部类访问内部类四、局部内部类五、匿名内部类1、匿名内部类…

Dragonfly 最新正式版本 v2.0.9 已经发布!

作者&#xff1a;戚文博-蚂蚁集团 Dragonfly 最新正式版本 v2.0.9 已经发布&#xff01;感谢 Dragonfly 的贡献者们&#xff0c;同时也感谢默默支持 Dragonfly 项目的各个公有云团队。欢迎访问 d7y.io [ 1] 网站来了解详情&#xff0c;下面具体介绍 v2.0.9 版本带来了那些更新。…

【Redis】十大数据类型(下篇)

文章目录redis位图(bitmap) --- 底子还是string基本命令图示setbit key offset value setbit 键 偏移位 只能零或者1getbit key offset 查看获取字符串长度 strlen统计key中包含1的个数 bitcount keybitop 统计两个比特key是否都为1技术落地&#xff1a;打卡签到&#xff0c;频…

【C语言蓝桥杯每日一题】——等差数列

【C语言蓝桥杯每日一题】——等差数列&#x1f60e;前言&#x1f64c;等差数列&#x1f64c;解题思路分析&#xff1a;&#x1f60d;解题源代码分享&#xff1a;&#x1f60d;总结撒花&#x1f49e;&#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭&…

让ChatGPT帮我写shell脚本, 结局很感人

七问ChatGPT, 剑指shell脚本编写 step1: 初问step2: 再问step3: 三问step4: 四问step5: 五问step6: 问个derstep7: 解决问题step8: 小问一下关于ChatGPT思考昨天浏览一篇关于脚本的技术文章的时候, 偶然看见一篇文章中写道关于mysql备份的脚本. 但是这个脚本时基于本地的MySQL服…

Idea+maven+spring-cloud项目搭建系列--13 整合MyBatis-Plus多数据源dynamic-datasource

前言&#xff1a;对于同一个系统&#xff0c;不同的租户需要自己独立分隔的数据库&#xff08;每个数据库的表结构可以是相同的&#xff09;&#xff0c;同时也要支持跨数据源的查询&#xff1b;并且支持分布式事务&#xff0c;如果这里不使用分库分表插件&#xff0c;需要怎样…

使用dd复制将乌班图系统(Ubuntu22.04)完整迁移到新硬盘并扩容

我的折磨历程 开始的时候用乌班图的时候&#xff0c;不懂事&#xff0c;根目录太小了&#xff0c;后来就满了&#xff0c;就就感觉完全没法用&#xff0c;看着现在硬盘贼便宜&#xff0c;去狗东买了个新的硬盘。感觉挂载硬盘并不能解决我的问题&#xff0c;最后选择了保留系统数…

ython和PyTorch实现ChatGPT批量AI智能写作

怎么实现用chatgpt批量写作 ChatGPT是一种针对文本生成的自然语言处理工具&#xff0c;它可以用于生成大量的文本内容。但是&#xff0c;由于ChatGPT需要的计算资源较大&#xff0c;处理时间较长&#xff0c;因此在批量写作时需要考虑花费的时间和资源。 以下是一些步骤&…