首页 > 其他分享 >string的模拟实现

string的模拟实现

时间:2023-11-07 12:32:04浏览次数:32  
标签:字符 string 实现 重载 字符串 模拟 函数

前言

string在C++里是一个已经被封装好的类,类中实现了各种功能的函数,可以让我们更轻松的对字符串进行操作。在此通过对string类的模拟实现来进一步了解string类函数的使用。

一、string类简单了解

1.string的大小

定义一个库中的string类的对象s1,并计算其大小。如下图

string的模拟实现_C++

可以看到,string类的大小为28

2.string类的成员变量

类的大小仅和成员变量有关。

按照我们的理解,string的成员变量应该和顺序表一样,存储有三个变量。

一个char*的指针,用来存放字符数据,size_t 类型的size,用来表示字符的个数,size_t类型的capacity,用来表示开辟空间大小,按照这样来计算,在32位平台下,string的大小应该是12。可真实的结果却是28。

通过调试,我们可以直观的看到,在string类变量中,size和capacity确实存在,但确新增了另外两个我们不懂的东西,alloctor和原始视图,而他们就是string类大小变化的原因。

string的模拟实现_编程_02

如果不断展开原始视图,最终会发现一个叫做_Buf的数组。

实际上,在vs平台下,string类的成员变量中存在一个16字节大小数组_Buf,当string对象存储的字符数据没有超过它的大小时,就会存储在_Buf中,而存储的字符数据超过它的上限,就不会在其内存储。

例如,对于字符个数没有超过_Buf数组大小的string对象s1,_Buf中存储数据。如下图

string的模拟实现_string类_03

但是,对于初始化存储18个字符的string类对象s2,不会在_Buf数组中存储添加的数据

string的模拟实现_编程_04

并且,当s1存储数据超过_Buf数组的上限后,_Buf数组也不会在存储。如下图

string的模拟实现_string类_05


也就是说,除了我们猜测的char*指针,size,capacity,string的成员变量还要一个16字节大小的字符数组,这也是为什么string类的大小是28的原因,但实际上,string还有一个静态成员变量npos,它的类型是size_t ,对于静态成员变量,在计算类大小时,因为是被所有该类对象共有,因此不计算在内。下图是库中对npos的定义。

string的模拟实现_string类的模拟实现_06

3.string类内的函数

string类实现的函数有很多,除了成员函数等对字符串进行操作的函数,最重要的是引入了迭代器Iterator。

3.1Member functions部分函数

构造函数

string的模拟实现_string类_07

string的模拟实现_string类的模拟实现_08

对于string类的构造函数,实现了不同的重载。但本质上,构造函数的功能只是初始化,根据不同的初始化需求,所以有多个重载的构造函数。

其中(2)是拷贝构造,(7)则是利用模板,通过迭代器进行初始化

析构函数

string的模拟实现_string类的模拟实现_09

对于string的析构函数而言,想要完成清理任务很简单,简单的释放动态申请的空间即可。

赋值运算符重载

string的模拟实现_string类的模拟实现_10

自定义类型的赋值必须进行重载,因为其内可能有多个成员变量,因此需要对每个成员变量依次赋值

3.2Iterators部分函数

string的模拟实现_string类_11

关于迭代器,在string类中,我们将其当作指针来进行使用即可。该部分函数的功能一目了然,需要注意的是,end指向的位置是最后一个字符的下一个位置以及const迭代器的实现

string的模拟实现_string类_12

3.3Capacity部分函数

string的模拟实现_string类_13

该部分的重点是扩容,reserve和resize,具体内容会在模拟实现中提及

3.4Element access部分函数

string的模拟实现_编程_14

operator[]使得可以通过下标来访问对于的字符,而at可以返回对应下标位置的字符

3.5Modifiers部分函数

string的模拟实现_string类_15

增删操作等等

3.6String operations部分函数

string的模拟实现_string类的模拟实现_16

简单函数的应用,精华在于find和substr

3.7Non-member function overloads部分函数

string的模拟实现_string类的模拟实现_17

关键是重载输入和输出,重载后可以实现对自定义类型(自己写的)的输入和输出。

二、对string类的简单模拟实现

1.string类的成员变量

因为是模拟,因此没有实现_Buf数组,但是不会有什么影响

string的模拟实现_string类的模拟实现_18

对于static成员变量,必须在类中声明,类外定义。

2.string类的成员函数

2.1构造函数的实现

string的模拟实现_C++_19

注意事项:

缺省参数的”“没有空格,但是会存在一个字符‘\0’,并不需要再添加。

初始化列表的顺序和成员变量的定义顺序保持一致。

多开一个空间是留给字符串的结束标志\0的。

构造函数的形参类型是char* ,因此缺省参数只能是”“,而不能是‘’(单个字符)。

常量字符串的类型是const char*,  const char* 到char* 涉及权限的放大,所以形参在接收时也加了const。

2.2拷贝构造

string的模拟实现_string类的模拟实现_20

拷贝构造也只是构造函数的重载,也是用来初始化对象的,代码实现大同小异。

注意事项:

关于_arr的初始化,strcpy和memcpy都可以,只要在初始化_arr时,不是写成_arr(str)的直接赋值即可。

_arr(str)的初始化方式会让两个指针指向同一块空间,因为拷贝构造是用一个对象初始化另一个对象,这样就会导致两个对象中的_arr指向同一块空间,会引发同一块空间要析构两次的错误

函数效果如下图

string的模拟实现_string类_21


2.3operator=

string的模拟实现_string类_22

此处的赋值运算符重载的实现复用了拷贝构造,形参中的string没有添加引用,在接收参数的时候会先发生拷贝构造,再利用swap进行交换,产生的临时变量s出了作用域就会销毁。

因为是复用的拷贝构造,而实现的拷贝构造已经解决了浅拷贝的问题,所以没有问题。

2.4析构函数

string的模拟实现_string类_23

回到最初的模样,四大皆空。

3.capacity部分函数的实现

3.1扩容函数reserve和resize

reserve和resize的区别

reserve只负责开辟新空间并将旧空间的数据拷贝过来,

而resize还要再reserve的基础上,将剩余的空间全部填充

reserve函数实现

string的模拟实现_string类的模拟实现_24

注意事项:

_arr的真实空间大小是n+1,但最后是用n来更新_capacity,最后一个空间始终是用来存放\0的,也就是说,_capacity表示的含义应该是能够存储的字符个数

memcpy和strcpy都可以用。

reserve的功能仅仅是开辟新的空间,并将旧空间的数据拷贝过来,所以

reserve开空间之后,改变的是整个空间_capacity的大小,_size的大小并不会改变。所以只需要更新_capacity。

只有先将旧空间的数据拷贝一份后,才可以释放旧空间。

resize函数实现

string的模拟实现_C++_25

注意事项:

resize复用了reserve函数。在对剩余空间进行填充字符时,直接用下标进行访问填充。

实际上,string也实现了对[]的重载,使得string对象也可以用下标来访问字符。如下图

string的模拟实现_编程_26

可以看到,下标访问是被_size限制的,只有_size下标内的数据才可以用下标访问,那为什么resize可以用下标的方式来进行访问填充字符呢?

这是因为,resize中的下标访问并不是调用重载的[]函数,重载的[]对象是string类。

函数后的const修饰的是隐含的this指针,该函数只有加了const,才能被const对象使用,并且也能够被普通对象使用。

3.2其余简单函数的实现

string的模拟实现_string类的模拟实现_27

4.Modifires部分函数的实现

4.1push_back

函数实现步骤

1.判断是否需要扩容

2.访问尾部,插入数据

3.更新_size,放置\0

实现代码:

string的模拟实现_string类_28

string的模拟实现_C++_29

4.2insert

函数实现

string的模拟实现_C++_30

注意事项:

如果是在中间部位插入字符串,需要先从后向前依次挪动数据,再将要插入的字符串插入到挪开的位置上。

pos最小为0,因为end的类型为size_t ,为了防止end从0变为npos,所以在每次挪动数据的时候加以判断。

string的模拟实现_C++_31

4.3append

函数实现

string的模拟实现_string类_32

注意事项:

此处实现的函数是将整个字符串加在原本的字符串末尾,所以写法较为简单。如果要控制追加字符串的起始位置和追加的字符个数,还需另作判断。

4.4operator+=

函数实现

string的模拟实现_string类的模拟实现_33

复用的append,函数效果和append完全相同,都是在原本字符串的基础上再追加字符串。

另外复用push_back函数实现每次追加一个字符

string的模拟实现_string类的模拟实现_34

4.5erase

函数实现

string的模拟实现_string类_35

对于erase来说,只有两种情况,一种是要删除的字符是字符串中间的某一部分,要么是从pos位置开始往后的字符全部删除。如果是后者,只需要更新_size后,放入字符串结束标志\0即可。如果是前者,需要从前向后移动数据,包括\0,这也是为什么end可以等于_size的原因。

5.String operations部分函数实现

5.1c_str

string的模拟实现_编程_36

5.2find

在一个字符串中查找某一个字符,这很简单,只需遍历一遍即可。

string的模拟实现_C++_37

如果是在一个字符串中找一个字符串,并且是第一次出现的字符串,又该怎么做呢

string的模拟实现_C++_38

复用了库中的字符串函数strstr,该函数的功能就是找在第一个字符串中找第一次出现第二个字符串的起始位置的字符。如果找不到就返回空。

5.3.substr

string的模拟实现_C++_39

该函数的功能是返回字符串的子串。需要注意的是字串长度如果是npos或者从pos开始的len个字符已经超出母串的长度,需要更新len的长度。

6.迭代器部分

string的模拟实现_string类的模拟实现_40

在string的模拟中,迭代器就是对指针的重命名。之所以添加const,是因为普通对象可以使用没有const修饰的,也可以使用有const修饰的,但对于const对象来说,只能使用有const修饰的函数。

有了迭代器后,我们就可以利用迭代器来进行初始化。如下图

string的模拟实现_C++_41


string的模拟实现_string类的模拟实现_42

7.operator>>和operator<<

首先要明白一点,无论是istream还是ostream都可以看做是库中的类,我们平常使用的cin就是istream类的对象,cout就是ostream的对象。之所以cin和cout可以支持输入和输出某些自定义类型,是因为库中实现了对这些自定义类型的operator>>和operator<<,也就是运算符重载。

如果是我们自己定义的自定义类型,要想cin和cout实现对自己自定义类型的输入和输出,也需要进行重载。

运算符重载的作用,就是让运算符两端的参数按照我们想要的方式去做出相应的操作。

也就是说,无论是istream还是ostream,cin还是cout都是库中本来就实现好的,对于我们自己写的自定义类型,编译器不清楚要以什么样的格式进行输入和输出,所以才需要我们进行运算符重载,运算符重载的关键在于函数体内部的实现,以及运算符两端的参数。

string的模拟实现_string类_43


string的模拟实现_C++_44


任何一个类内的函数都会有一个隐含的this指针,如果operator>>和operator<<的实现要在类内实现,那么只能再添加一个参数,并且默认的第一个参数就是隐含的this指针。

如下图,当我们在类内实现重载时,如果设置两个参数时,就会提醒参数过多

string的模拟实现_编程_45

string的模拟实现_C++_46

如果是在类内实现,相应的参数需要变为*this,并且左右操作数的顺序也会和日常使用的相反。如下图

string的模拟实现_C++_47

string的模拟实现_C++_48

string的模拟实现_string类_49











标签:字符,string,实现,重载,字符串,模拟,函数
From: https://blog.51cto.com/u_15466618/8230253

相关文章

  • StringTokenizer
    Java中StringTokenizer的一个实例。 代码:StringTokenizerst=newStringTokenizer("thisisatest");while(st.hasMoreTokens()){System.out.println(st.nextToken());}打印出来之后是:thisisatest 接下来的实例阐明了Stirng.split方法如何将一个......
  • CSS3 实现动态旋转加载样式
    CSS3实现动态旋转加载样式要使用CSS3创建一个动态旋转加载样式,你可以使用CSS动画和旋转变换。下面是一个简单的示例:HTML:<divclass="loader"></div>CSS:.loader{width:50px;height:50px;border:4pxsolid#3498db;border-top:4pxsolidtransparent;......
  • 使用Python从零实现多分类SVM
    本文将首先简要概述支持向量机及其训练和推理方程,然后将其转换为代码以开发支持向量机模型。之后然后将其扩展成多分类的场景,并通过使用Sci-kitLearn测试我们的模型来结束。SVM概述支持向量机的目标是拟合获得最大边缘的超平面(两个类中最近点的距离)。可以直观地表明,这样的超......
  • 项目中难点-页面点击“取消”按钮实现无感刷新--reload
    1、在App.vue页面中注册provide中定义reloadprovide(){return{reload:this.reload}} 2、在App.vue页面中的methods中定义方法reload目的通过控制router-view的显示与隐藏进行重新加载页面,实现无感刷新。reload(){this.isRouterAlive=falsethis.$next......
  • python3-TK实现一个可视化界面,选中文件夹可以计算文件夹下文件的数量
    借助Python3中Tkinter库,实现一个可视化的界面,通过界面选择文件夹,可以计算文件夹下文件的数量,嵌套文件夹的情况依旧可以计算。importosimporttkinterastkfromtkinterimportfiledialogdefcount_files_in_folder(folder_path):file_count=0forroot,dirs,......
  • requests-mock:轻松模拟HTTP请求的利器
    一、简介requests-mock一个python库,用于单元测试中模拟HTTP请求的响应,它可以进行来模拟接口的各种场景。安装:pipinstallrequests-mock二、使用方法模拟post请求 importrequestsimportrequests_mockdeftest_01():withrequests_mock.Mocker()as......
  • 界面控件DevExpress WPF PDF Viewer,更快实现应用的PDF文档浏览
    DevExpressWPFPDFViewer控件可以轻松地直接在Windows应用程序中显示PDF文档,而无需在最终用户的机器上安装外部PDF查看器。P.S:DevExpressWPF拥有120+个控件和库,将帮助您交付满足甚至超出企业需求的高性能业务应用程序。通过DevExpressWPF能创建有着强大互动功能的XAML基础应......
  • Golang实现简单的后门程序
    packagemainimport( "io" "net" "os/exec")funcmain(){ var( listenernet.Listener errerror connnet.Conn ) listener,err=net.Listen("tcp",":8080") iferr!=nil{ panic(e......
  • JavaScript string对象(属性,方法)获取图片后缀案例 输入和输出结果转换形式案例
    一、创建string对象varstrOb=newString("abcefg");varstrOb=String("abcefg");varstrOb="abcefg";二、属性length  (字符串长度)varstr='hello';console.log(str.length)//5三、方法1、子字符串位置indexOf(string,[index])str......
  • 在NestJS应用程序中使用 Unleash 实现功能切换的指南
    前言近年来,软件开发行业迅速发展,功能开关(FeatureToggle)成为了一种常见的开发实践。通过功能开关,可以在运行时动态地启用或禁用应用程序的特定功能,以提供更灵活的软件交付和配置管理。对于使用NestJS框架构建的应用程序而言,实现功能开关也是一项重要的任务。而Unleash是一个......