当前位置: 首页 > web >正文

C++初阶-string类的增删的模拟实现

目录

1.string::reserve(size_t n)的模拟实现

2.string::push_back(char ch)的模拟实现

3.string::append(const char* str)的模拟实现

4.string& operator+=(char ch)和string& operator+=(const char* str)的模拟实现

5.string::insert(size_t pos,size_t n,char ch)的模拟实现

问题分析和最终版本

6.string::insert(size_t pos,const char* str)的模拟实现

7.string::npos的模拟实现

8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现

问题分析和代码改进

9.总结



1.string::reserve(size_t n)的模拟实现

这个函数我们只实现扩容的版本,缩容的我们用得不多,所以这里就不实现了。

如何实现?

我们通过比较n与capacity来判断是否要扩容,若n>_capacity则我们先开辟一块新的空间(n+1)个char类型的空间(存储n+1个数据(因为不包含\0所以要+1)),然后再把内容拷贝到新空间上,最后释放原空间,并把_str指向新空间,_capacity置为n。则最终代码如下:

//.cpp
void string::reserve(size_t n)
{if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}
}

我们通过该测试代码:

//.cpp
void test1()
{string s("Hello world");s.reserve(20);
}

调试发现结果:

地址是已经改变了的,并且把_capacity变化的代码是正确的。

2.string::push_back(char ch)的模拟实现

这个函数实现比较简单,先看一下_size是否等于_capacity或者判断是否_size+1>_capacity来判断是否需要库容,然后用二倍扩容的知识来进行扩容(之后的那些insert、append、+=都差不多),因为如果用1.5倍扩容还要涉及到其他的如四舍五入的知识,很麻烦。则代码如下:

//.cpp
void string::push_back(char ch)
{if (_size == _capacity){//扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;//注意!!!_str[_size] = '\0';
}

用以下的函数来测试:

//.cpp
void test1()
{string s("Hello world");s.push_back('a');
}

则调试发现:

则代码无误。

3.string::append(const char* str)的模拟实现

这个函数实现起来比之前复杂一些:

首先我们需要计算出str的长度,用strlen来计算;

其次,我们需要用_size+len是否大于_capacity来判断是否需要扩容;

如果需要扩容,我们需要把newCapacity置为2*_capacity;

但是如果_size+len还是大于newCapacity呢么我们就需要把newCapacity置为len+_size了,最后再扩容至newCapacity。

而且我们还不能在插入字符串时用strcat(_str,str)来插入,因为效率太低了,要从头开始找\0。我们用新的方式:strcpy(_str+_size,str);我们就从这个_str的_size位置即之后的字符串替换为str,这个方式比之前的方式难想到,故也需要很强的思维性!

最后不要忘记把_size+=len了!

那么我们最终代码如下:

//.cpp
void string::append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){//扩容size_t newCapacity = 2 * _capacity;//不确定二倍扩容后的空间是否足够if (_size + len > 2 * _capacity){newCapacity = _size + len;}reserve(newCapacity);}strcpy(_str + _size, str);_size += len;
}

用以下代码进行测试:

//.cpp
void test1()
{string s("Hello world");s.append(" Hello CSDN");
}

那么结果:

则代码无误。

4.string& operator+=(char ch)和string& operator+=(const char* str)的模拟实现

这两个函数实现都是前面两个函数实现的复用,所以就只展示代码,就不测试了。

//.cpp
string& string::operator+=(char ch)
{push_back(ch);return *this;
}
string& string::operator+=(const char* str)
{append(str);return *this;
}

5.string::insert(size_t pos,size_t n,char ch)的模拟实现

这个函数实现起来基本和之前一样,这里讲一下思路:

先判断pos是否小于等于_size,这个可以用assert来判断,也可以手动判断,因为不能越界,所以我们要加上一个判断;

然后判断是否要扩容,这个和之前一样,就不做多解释了;

然后我们要把第pos位置后的字符移动n个位置,方法是从后往前移动;

最后插入n个字符。

代码如下:

//.cpp
void string::insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size;while (end >= pos){_str[end + n] = _str[end];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}

用以下代码进行测试:

//.cpp
void test1()
{string s("Hello");//尾部插入s.insert(5, 5, 'x');cout << s.c_str() << endl;//中间插入s.insert(2, 3, 'y');cout << s.c_str() << endl;//头部插入s.insert(0, 3, 'z');cout << s.c_str() << endl;
}

但是运行结果为:

也就是说头部插入有问题,原因是什么?

问题分析和最终版本

因为size_t end=_size;虽然end会--,但end是无符号整数,不会小于0所以end>=pos这个条件是一直都成立的,所以我们不能用这个来判断。

那我们如果把end改为int类型呢?

结果没有改变,为什么?

因为在C语言中两个操作数不一样的时候会把类型进行转换,通常是范围小转换成范围大的数。但是如果是无符号数与有符号数进行比较时,就会把有符号转换为无符号去比较。那我们把pos也改为int类型吗?

不行,我们要与string类的这个函数的参数保持一致,我们不能改变它的定义。

我们可以通过强制类型转换,如:end<=(int)pos,这个pos仍然为size_t类型。或者我们把end初始化为_size+n,循环改为while(end>(pos+n-1)){_str[end]=_str[end-n];--end;}我们也可以把循环的条件改为end>=(pos+n)这个需要自己画图去理解了,要么就因为开始的时候加了n,这个时候也要加n。

最终代码如下:

void string::insert(size_t pos, size_t n, char ch)
{assert(pos <= _size);if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n) ){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;
}

那么最终运行结果如下:

代码没有问题!

6.string::insert(size_t pos,const char* str)的模拟实现

这个函数前面和刚刚那个函数一样,后面的只要把字符串拷贝至pos位置即可,则代码如下:

//.cpp
void string::insert(size_t pos, const char* str)
{assert(pos <= _size);size_t n = strlen(str);if (n == 0){return;}if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n)){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = str[i];}_size += n;
}

测试函数如下:

void test1()
{string s("Hello");//尾部插入s.insert(5, " World");cout << s.c_str() << endl;//中间插入s.insert(5, " Beautiful");cout << s.c_str() << endl;//头部插入s.insert(0, "I Love ");cout << s.c_str() << endl;
}

运行结果如下:

7.string::npos的模拟实现

这个npos是一个静态的且不可以被修改的成员变量,而且它是size_t类型的,但是它的值为-1,而且我们是在.cpp里面定义npos的值,而不是在.h文件中定义,这个你可以试一下在.h文件中定义会有问题的!

那么我分为两个文件来讲解:

//.h
#define _CRT_SECURE_NO_WARNINGS 1
#include<string.h>
#include<assert.h>
#include<iostream>
using namespace std;
namespace td
{class string{public:string(const char* str = " ");const char* c_str() const{return _str;}~string();void reserve(size_t n);void push_back(char ch);void append(const char* str);string& operator+=(char ch);string& operator+=(const char* str);void insert(size_t pos, size_t n, char ch);void insert(size_t pos, const char* str);private:char* _str;size_t _size;size_t _capacity;const static size_t npos;};void test1();
}
//.cpp
#include"test.h"
namespace td
{const size_t string::npos = -1;string::string(const char* str):_size(strlen(str)){_capacity = _size;_str = new char[_size + 1];strcpy(_str, str);}string::~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}void string::reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void string::push_back(char ch){if (_size == _capacity){//扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;//注意!!!_str[_size] = '\0';}void string::append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){//扩容size_t newCapacity = 2 * _capacity;//不确定二倍扩容后的空间是否足够if (_size + len > 2 * _capacity){newCapacity = _size + len;}reserve(newCapacity);}strcpy(_str + _size, str);_size += len;}string& string::operator+=(char ch){push_back(ch);return *this;}string& string::operator+=(const char* str){append(str);return *this;}void string::insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n) ){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;}void string::insert(size_t pos, const char* str){assert(pos <= _size);size_t n = strlen(str);if (n == 0){return;}if (_size + n > _capacity){size_t newCapacity = _capacity * 2;if (_size + n > newCapacity){newCapacity = _size + n;}reserve(newCapacity);}size_t end = _size + n;while (end >= (pos + n)){_str[end] = _str[end - n];--end;}//插入n个字符for (size_t i = 0; i < n; i++){_str[pos + i] = str[i];}_size += n;}void test1(){string s("Hello");//尾部插入s.insert(5, " World");cout << s.c_str() << endl;//中间插入s.insert(5, " Beautiful");cout << s.c_str() << endl;//头部插入s.insert(0, "I Love ");cout << s.c_str() << endl;}
}

这个主要是为了你们了解一下每个函数的定义和声明,之后有些函数是要复用的,所以先总结一下。

const在最前面,static在中间,最后再是size_t类型,这个我们是一定要记得顺序的!

8.string::erase(size_t pos = 0,size_t len=npos)的模拟实现

首先,我们要设置缺省值在声明时,即在.h文件中要设置缺省值,而在.cpp文件中也就是在定义中我们不能加缺省值;

然后,如果pos+len大于_size,所以我们需要先把_str[pos]='\0'然后把_size=pos,最后return;

否则,我们就要设置一个end初始化为pos+len,然后再加上一个循环,循环条件是end<=_size,循环体内是_str[end-len]=_str[end];++end我们是从前往后赋值的,所以要这样写。

最后再把_size-=len即可。

所以最终代码如下:

//.cpp
void string::erase(size_t pos, size_t len)
{assert(pos < _size);if (pos + len >= _size){//删完了_str[pos] = '\0';_size = pos;return;}size_t end = pos + len;while (end < _size){_str[end - len] = _str[end];++end;}_size -= len;_str[_size] = '\0';
}

测试函数如下:

void test1()
{string s1("Hello world");string s2("Hello world");string s3("Hello world");string s4("Hello world");cout << s1.c_str() << endl;//删除从中间到最后的元素s1.erase(5);cout << s1.c_str() << endl;//删除从头到中间的元素s2.erase(0, 5);cout << s2.c_str() << endl;//删除从中间一个位置到另外一个中间的位置s3.erase(3, 5);cout << s3.c_str() << endl;//直接删完s4.erase();cout << s4.c_str() << endl;
}

那么这个代码运行结果就会报错,为什么?

问题分析和代码改进

如果len==npos 或 pos + len 远超 _size会直接截断字符串到 pos 位置,这个思路是不行的,所以我们要么就把前面的if语句改为如下形式:

len = std::min(len, _size - pos);

这样保证了我们不会越界了,也简化了我们的代码长度,或者我们把前面的if语句改为:

if ( len >= _size - pos)
{//删完了_str[pos] = '\0';_size = pos;return;
}

因为这样就不会有len为npos时再加pos而报错了,但是我们建议还是第一种方式来进行修改,因为第二种方式最后还可能会报错的,所以最终改变后的代码为:

void string::erase(size_t pos, size_t len) 
{if (pos >= _size) return;         // 检查 pos 是否合法len = std::min(len, _size - pos); // 限制 len 的范围size_t end = pos + len;while (end < _size) {_str[end - len] = _str[end];++end;}_size -= len;_str[_size] = '\0'; // 确保字符串以 \0 结尾
}

至于这个std::min函数就是查找两者之间的最小值,我们也可以手动判断。

则运行结果如下:

当然,我们也可以把循环改变成如下形式(和之前的insert方式一样):

void string::erase(size_t pos, size_t len) 
{if (pos >= _size) return;         // 检查 pos 是否合法len = std::min(len, _size - pos); // 限制 len 的范围size_t end = pos + len;strcpy(_str + pos, _str + pos + len);//while (end < _size) //{//	_str[end - len] = _str[end];//	++end;//}_size -= len;_str[_size] = '\0'; // 确保字符串以 \0 结尾
}

这样运行起来还是可以的!

9.总结

这一节内容还是比较多的,而且理解起来比之前的提升了不只一个档次,下一讲的内容难度可能还会有过之而不及,所以说string类这个部分也是比较考验理解的。下一讲的内容快得话明天就可以发出来,喜欢的可以一键三连哦。下讲再见。

http://www.xdnf.cn/news/5470.html

相关文章:

  • C# 通过ConfigurationManager读写配置文件App.Config
  • 如何实现并运用责任链模式
  • 英语时态--中英文对“时间”的不同理解
  • 抽奖系统-基本-注册
  • Redis从基础到高阶应用:核心命令解析与延迟队列、事务消息实战设计
  • JVM 监控
  • 【Java学习笔记】多态
  • HTML5中的Microdata与历史记录管理详解
  • 安装typescript时,npm install -g typescript报错
  • .Net HttpClient 处理响应数据
  • 每日一题洛谷P8615 [蓝桥杯 2014 国 C] 拼接平方数c++
  • 被一个人影响情绪是爱吗?这 3 个真相越早明白越好
  • AI面经总结-试读
  • 深度解析六大AI爬虫工具:crawl4ai、FireCrawl、Scrapegraph-ai、Jina、SearXNG、Tavily技术对比与实战指南
  • COT思维链:SequentialChain 方法有哪些参数;优化后的提示词
  • ES面试题系列「一」
  • MySQL的索引分类
  • 软件体系结构(Software Architecture)
  • IDEA:如何设置最上面菜单栏一直显示出来
  • 图片转ICO图标工具
  • 一个网球新手的学习心得
  • 单链表设计与实现
  • 锁相放大技术:从噪声中提取微弱信号的利器
  • C PRIMER PLUS——第9节:动态内存分配、存储类别、链接和内存管理
  • 程序中的内存从哪里来?
  • arctan x 导数推理
  • Java 1.8(也称为Java 8)
  • 4.4 os模块
  • MySql事务索引
  • 图灵奖获得者经典论文系列(1969):迈向人工智能的步伐(马文·明斯基)