【C++初阶】模拟实现string的常见操作

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


前言

一、准备工作

为了方便管理代码,分两个文件来写:

  • Test.cpp - 测试代码逻辑
  • string.h - 模拟实现string
    在这里插入图片描述

二、string的结构

string是一个管理字符数组的类,底层其实就是一个支持动态增长的字符数组,就像数据结构的动态顺序表。

在这里插入图片描述

string类的成员变量有三个

  1. 字符指针_str指向开辟的动态数组
  2. 有效数据个数_size
  3. _capacity记录容量的大小

还需要注意的是:这里新命名了命名空间域wj,就是避免和库中的string产生冲突。

三、string底层容量是否包含\0(capacity函数)

在模拟实现之前,首先要考虑底层的capacity是否包含\0,这里以VS 2019为例(不同的编译器底层实现可能不同)

在这里插入图片描述

通过以上验证发现:

  • vs 2019对于string底层实现的默认容量是15
  • 容量包含'\0'
  • 大约是以2倍扩容

【函数原型】

在这里插入图片描述

【代码实现】

在这里插入图片描述

四、常见构造函数

在这里插入图片描述

4.1 默认构造空字符串

无参构造默认是有'\0'

在这里插入图片描述

以上代码有个易错点:要注意初始化列表的顺序,是按照成员变量的顺序来赋值的!

4.2 用C字符串构造

在这里插入图片描述

五、 拷贝构造

如果类中有动态分配内存的指针变量,则需要手动编写深拷贝的拷贝构造函数。编译器生成的默认只会完成浅拷贝。

在这里插入图片描述

六、 reserve函数

  • reserve一般很少会缩容,一般都是扩容
  • 扩容原理:1. 开一块新的空间。2. 拷贝数据到新的空间。3. 释放旧空间然后指向新空间

在这里插入图片描述

七、验证构造函数 - c_str()

为了验证代码的正确性,需要打印出结果。由于自己模拟实现的string,还没有实现重载流插入<< ,所以不能直接打印string对象,而流插入<<是可以自动识别内置类型的。因此string是有提供转为内置类型的接口c_str

在这里插入图片描述

在这里插入图片描述

为什么会在函数后加个const?在往期博客我们讲过:只要成员函数内部不修改成员变量,都应该加上const

接下来来测试代码:

  • 无参构造

在这里插入图片描述

  • 用C字符串构造

在这里插入图片描述

  • 拷贝构造

在这里插入图片描述

八、析构函数

由于成员变量含有动态内存开辟的空间,因此要手动写出析构函数

在这里插入图片描述

九、普通遍历操作 (size接口 + [] 接口)

在这里插入图片描述

对于普通遍历,最基本的是要实现size接口和[]接口。

  • size接口

在这里插入图片描述

【代码实现】

在这里插入图片描述

  • []接口

在这里插入图片描述

【代码实现】

在这里插入图片描述

【测试结果】

在这里插入图片描述

十、迭代器

  • 迭代器iterator本质上就是一个容器的内嵌类型,这个迭代器可以是个自定义的类、也可以是typedef的类型。string的迭代器本质就是一个char*类型的指针,因此直接typedef即可。
  • 但是要注意:typedef要写在public段中,因为迭代器本身就是要给类外用的。

在这里插入图片描述

【代码实现】

在这里插入图片描述

【测试结果】

在这里插入图片描述

除此之外,范围for的底层就是迭代器

在这里插入图片描述

十一、尾插

11.1 push_back - 尾插一个字符

在这里插入图片描述

在尾插字符之前,需要考虑容量是否足够

在这里插入图片描述

【测试结果】

在这里插入图片描述

11.2 append - 尾插字符串

在这里插入图片描述

append有很多函数形式,这里只实现常用的

【代码实现】

在这里插入图片描述

【测试结果】

在这里插入图片描述

11.3 operator+= – push_back和append升级版

在这里插入图片描述
+=运算符重载既可以尾插一个字符,还可以尾插字符串。因此,直接复用push_backappend即可。

在这里插入图片描述

【测试结果】

在这里插入图片描述

十二、插入insert

在这里插入图片描述

string的插入接口设计有点冗余,我们直接实现最常见的即可

  • . pos位置插入n个字符

【思路】

  1. 首先要判断下标的合法性。
  2. 其次还要判断插入的字符加上原有的字符是否超过当前容量,超过就扩容。
  3. 然后就是挪动数据和插入数据。注意挪动数据一定要从最后一个字符'\0'开始挪动n次;不能从pos位置开始挪,否则后面的内容就被覆盖了。以下是动图展示
    在这里插入图片描述

在这里插入图片描述

通过思路分析,不难可以写出以上代码。但是以上代码有一个bug,当头插时,程序就如下图一直在闪烁光标。

在这里插入图片描述

通过走读代码我们发现:posend的类型是都是无符号类型size_t。因此end最后自减到-1,由于类型是size_t,而无符号的-1是一个相当大的数,循环条件成立就会一直死循环下去。

因此这里有两种方法:

第一种:posend的类型全部改为int这种方法虽然可以,但是和库里提供的参数类型还是有所差别的,因此还是有些不好。

void insert(int pos, size_t n, char x) 
{
	// 判断下标pos的合法性
	assert(pos >= 0 && pos <= _size);

	// 可能存在扩容
	if (_size + n > _capacity)
	{
		reserve(_size + n);
	}

	// 挪动数据
	int end = _size;
	while (end >= pos)
	{
		_str[end + n] = _str[end];
		end--;
	}
	// 插入数据
	for (int i = 0; i < n; i++)
	{
		_str[pos + i] = x;
	}
	_size += n;
}

第二种:既然size_t类型的end自减到-1就会死循环,那么加个end != -1不就完事了。恰好,string库里提供了公共静态成员常量npos,这个常量使用值就是用-1定义。

在这里插入图片描述

所以,最终代码如下:

在这里插入图片描述

需要注意的是:静态成员需要在类外定义

【测试结果】

在这里插入图片描述

  • pos位置插入字符串

思路和以上类似

在这里插入图片描述

【测试结果】

在这里插入图片描述

十三、删除操作erase

在这里插入图片描述

【思路】

  • 首先要检查下标的合法性
  • 删除要分情况讨论:
    第一种:当前下标往后的字符全都需要删除
    在这里插入图片描述
    第二种:删除的字符是符合范围内的
    在这里插入图片描述

在这里插入图片描述

【测试结果】

在这里插入图片描述

十四、查找find

在这里插入图片描述

  • 查找字符

在这里插入图片描述

  • 查找字符串

查找子串可以有很多方法,最简便就是使用C语言中的strstr函数

在这里插入图片描述

十五、字符串截取substr

在这里插入图片描述

在这里插入图片描述

十六、改变有效字符个数resize

在这里插入图片描述

三种情况:

  • n小于size,相当于删除数据,保留n个字符
  • n等于size,则保留原数据
  • n大于size,则会增加字符,同时后面会补充n - size个字符(不指定默认是'\0'

在这里插入图片描述

十七、流插入<<

成员函数默认第一个形参都是对象的地址,也就是隐藏的this指针。由于cout抢占了对象的第一个位置,因此不能当做成员函数,就只能写在类外。

此外,这里需要需注意:

  • C形式字符串以字符数组的形式存储的,通过'\0'来确定字符串何时结束。
  • string字符串string类它可以自动追踪字符串的长度,并且不需要以'\0'结尾来表示字符串的结束。有多少字符就打印多少字符

在这里插入图片描述

【代码实现】

在这里插入图片描述

【测试结果】

在这里插入图片描述

十八、流提取>>

18.1 朴素版本

注意:>>读取string对象,默认读取到空格或者换行就不会往下读了

在这里插入图片描述

【测试结果】

在这里插入图片描述

如以上结果:当我输入完字符并且回车后,结果并没有显示出来;并且程序进入了阻塞状态。这里有一个输入的小细节:

  • cin在默认情况下不会读取空格和换行。当使用>>操作符读取字符串时,它会自动跳过开头的空白字符(包括空格、制表符和换行符),然后将连续的非空白字符作为一个字符串读取。

为了解决这个这个问题,istream类还提供一个接口get():读取一个字符(包括空格和换行)

在这里插入图片描述

【测试结果】

在这里插入图片描述

但以上代码还是不够完善,当多次对一个对象进行输入时,以上代码并没有对之前形成一次覆盖,可以对比库里的string

在这里插入图片描述

因此,在输入之前要清除对象的内容。

在这里插入图片描述

【测试结果】

在这里插入图片描述

还有一个问题,当一开始输入连续空格或者换行时,可以对比自己实现的和库里的:

在这里插入图片描述

因此要过滤前面的空格或者换行

在这里插入图片描述

【测试结果】

在这里插入图片描述

18.2 优化版本

但是以上代码的性能不够好,当输入好几个字符时,由于+=就会不断进行扩容,就会有损耗。

优化思路:提前开128个空间的字符数组,减少扩容的消耗(vs的底层也是这么实现的)

在这里插入图片描述

18.3 getline

getline可以读取到空格,但不能读取换行。

在这里插入图片描述

【代码实现】

在这里插入图片描述

十九、清空操作clear

直接将第一个字符改成'\0'即可

在这里插入图片描述

二十、比较操作

  • 字符串比较都是按照ASC码比

20.1 <

在这里插入图片描述

20.2 ==

在这里插入图片描述

20.3 <=

在这里插入图片描述

20.4 >

在这里插入图片描述

20.5 >=

在这里插入图片描述

20.6 !=

在这里插入图片描述

二十一、交换swap

在这里插入图片描述

十七、赋值运算符重载

17.1 为什么要写手动写赋值运算符重载

由于string类有动态开辟的成员变量。如果不写深拷贝,两个对象会同时指向动态开辟的空间,就会导致析构两次的问题。

17.2 法一:传统写法

  • 首先开一个和s2同样大的空间并且把s2的数据拷贝
  • 然后再释放掉s1指向的空间
  • 最后再让s1指向新拷贝的那个空间

在这里插入图片描述

17.3 法二:现代写法

  • s2拷贝构造tmp的对象,然后再让tmps1交换。(s1 = s2

在这里插入图片描述

17.4 法二延伸

在这里插入图片描述

十九、源码

本篇博客的代码仓库地址:点击跳转

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值