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

c++STL-string的模拟实现

c++STL-string的模拟实现

  • string的模拟实现
    • string的模拟线性表的实现
      • 构造函数
      • 析构函数
      • 获取长度(size)和获取容量(capacity)
      • 访问 [] 和c_str
      • 迭代器(iterator)
      • 交换swap
      • 拷贝构造函数
      • 赋值重载(=)
      • 扩容(reserve和resize)
      • 插入(insert)
      • 尾插(push_back、+=、append)
      • 比较运算符重载
      • 删除(erase)
      • 清空(clear)
      • 流插入(operator<<)和流提取(operator>>)
      • 查找(find)
      • 取字符串(substr)
    • string模拟参考程序

string的模拟实现

建议先看c++STL-string的使用-CSDN博客

这里的模拟实现是帮助自己更好地理解string。不排除以后觉得库里的string不好用,自己亲自写一个更好的。

并且c++的标准并没有规定怎么实现,而是给定了指标,程序员达到这个标准即可。

这里贴一个功能文档,后期自己想复习了,就照着这个文档搓一遍string

string的模拟线性表的实现

这里参考顺序表的概念和实现-CSDN博客:

class string{
public://const static size_t npos=-1;//特例,不建议用特例const static size_t npos;
private:char* _str;size_t _size;size_t _capacity;
}
const size_t string::npos=-1;

_size表示\0的位置。

_capacity表示数组的大小。数组需要预留1个空间给\0,因此_capacity会比实际情况小1。

const静态整型变量可以给缺省值。类的成员变量给的缺省值是给初始化列表使用,但静态成员变量不会通过构造函数的初始化列表,所以按照标准应该是不可以的。

猜测写c++语法的人或写编译器的人为了个人方便,为const静态整型变量开了一个特例,甚至只能是整型,别的类型比如double不允许。

因为string的成员函数很多而且功能大都是重复的,所以只选几个重要的功能模拟实现。

也不做basic_string的类模板,就只做basic_string<char>

不确定的功能也可以用库中的作对比。

构造函数

对比string的构造函数接口:

请添加图片描述

这里选择形参为char*的构造函数来模拟实现。

#include<iostream>
#include<string>
using namespace std;int main() {string st;cout << st;//调用默认构造函数也不会越界string st2("abc");cout << st2;return 0;
}

所以构造函数满足不给初始值也不会越界(即至少有1个\0)。

目前能想到的方案是构造函数的形参给全缺省,用全缺省的形参去初始化成员变量,用全缺省的形参也能省去无参构造函数的定义。

几种常见的不合理的构造函数:

//1
//成员指针_str可修改,str经过const修饰不可通过str修改内存
//_str也不能加const修饰。
string(const char* str):_str(str)//,_size(strlen(str))
{//拷贝字符串
}//2
//初始化列表按照成员变量在类中声明的顺序。
//所以_capacity
string(const char* str):_size(strlen(str)),_capacity(strlen(str)),_str(new char[_capacity+1])
{//拷贝字符串
}

析构函数

析构函数如果不在意效率,可以按照顺序表的方式去释放空间。如果在意效率,后文会介绍引用计数和写时拷贝。

获取长度(size)和获取容量(capacity)

size_t size() const;
size_t capacity() const;

获取长度:返回_size,并保证不会修改*this

获取容量:返回_capacity,并保证不会修改*this

这样可以方便查看对象的情况。

访问 [] 和c_str

请添加图片描述

请添加图片描述

重载operator[],返回类型char&。需要注意判断下标是否合理。

要提供2个[],一个不加const,另一个返回值和this指针都加const

c_str返回_str的地址。

迭代器(iterator)

      iterator begin();
const_iterator begin() const;iterator end();
const_iterator end() const;

迭代器的功能和指针类似,但不一定是指针。在模拟实现阶段用指针代替。

请添加图片描述

请添加图片描述

其中范围for要求类至少有普通版本和const版本的begin()end()(迭代器但凡更换一个名字都不支持,例如Begin,范围for就不支持),并且这些迭代器能正常访问,以及解引用(*)、前置递增(++)以及相等比较(==)和不等比较(!=)。

对于内置类型的数组来说,指针本身就可以作为迭代器使用,因为指针支持这些操作。

其中迭代器使用基础的beginend即可,反向迭代器需要用到适配器模式,在初学阶段暂时用不上。但可以通过其他迭代器类间接调用已有的beginend,以及相关的操作来实现。

交换swap

两个string对象完全可以只交换成员变量的值,这样可以减少多余的拷贝带来的额外开销。

拷贝构造函数

  • 用另一个对象初始化*this,需要另外开辟空间。

  • 还可以利用形参的_str去调用构造函数来初始化临时对象,之后*this和临时对象交换_str的地址即可。但需要注意的是,新生成的临时对象不会去调用原始的构造函数,因此需要额外初始化
    之后临时对象调用析构函数,和*this没有关系。

毕竟也是构造函数,需要对成员变量进行初始化。

拷贝构造函数还会面临另外的问题:浅拷贝问题。

浅拷贝问题是因为类的默认成员函数会自动运行做一些可能出问题的事(比如两个对象中的指针同时指向堆区的某块内存,连续进行两次释放的行为)。如果能花费较小的代价阻止默认成员函数的部分行为,则可以让浅拷贝成为一种优化方式。详细见引用计数写时拷贝

赋值重载(=)

请添加图片描述

char*可以通过调用构造函数间接实现,因此只需要再实现一个char为形参的即可。

进行赋值重载前先进行容量检测(reserve)。

  • 先进行判断,防止自己给自己赋值做多余的工作。

    后检查容量,容量不够时需要扩容,否则直接拷贝即可。

  • 还可以利用形参的_str去调用构造函数来初始化临时对象,之后*this和临时对象交换_str的地址即可。

  • 甚至还可以用传值传参,系统自己调用拷贝构造函数,再交换数据即可。

赋值重载也会涉及浅拷贝问题。详细见引用计数和写时拷贝

三种选择都可以,更推荐后面两个。

扩容(reserve和resize)

请添加图片描述

string的操作中,增加字符串中的内容需要扩容。因此推荐先实现扩容。

首先实现reserve。检测容量是否足够,不够的话扩容,size不变。同时注意初始capacity

如果是缩容,reserve什么也不做。

c++尽量不用realloc扩容,而是另外new另一个空间,因为要扩容的空间可能是自定义类型,需要调用其他类的构造函数和析构函数。

如果是resize

  • _size小于要扩容的大小,则在reserve的基础上更改_size的值,并用指定的符号去填充即可。
  • _size大于要扩容的大小,则相当于缩容,只需要更改_size的值并赋值\0即可。

根据实际经验,MSVC和mingw64的扩容方式不同(MSVC是1.5倍,mingw64是2倍扩容)。

用哪个都可以。

扩容时统一使用新开空间。虽然可以使用realloc,但很多时候realloc也是开辟新空间,释放旧空间。

插入(insert)

请添加图片描述

插入函数选择形参是(size_t,string&)的实现即可。

首先先检查容量是否支持插入新的字符。

然后下标pos后的字符串整体后移若干个单位,并在pos处插入一个字符(串)。可能涉及扩容。

请添加图片描述

而且需要特别注意头插和尾插的情况。若用于表示下标的整型用的是无符号(size_t),则要注意在无符号数的情况下负数的情况。特别是 -1 反而是最大值的情况。

尾插(push_back、+=、append)

请添加图片描述

因为原版的stringappend的设计和+=的功能重复。因此仅实现append+=其中之一,另一个用代码复用即可。

也可以只实现insert,之后凡是和插入有关的都复用insert即可。

往字符串结尾插入字符,如果容量不够需要扩容。push_back是插入1个,+=可以插入1个,也可以插入整个字符串。

扩容需要留意_capacity是否为0。可以用c语言自带的strcpy,将\0顺带拷贝回去。

而且有必要的话需要提供形参为const的另一个版本的成员函数。

比较运算符重载

请添加图片描述

比较运算符重载有两个操作数,可以做成全局函数,将这些比较运算符设置为友元,或直接通过operator[]访问。

可以用c语言的库函数strcmp代替。但为了增强代码的独立性,尽量选择尽量不使用c语言自带的库函数。

而且在这个地方函数复用会很多,需要注意形参的权限。比如const修饰的形参不可作为实参上传给非const修饰的形参的函数(权限放大)。

删除(erase)

string& erase(size_t pos = 0, size_t len = npos);

从下标pos开始删除长度为len的字符。

pos>=_size则应阻止删除。

_size<=pos+len(或len==npos),则_str[pos]='\0',更新_size即可。

_size>pos+len,则需要挪动数据并更新size

清空(clear)

将首字符设置为空字符\0并修改_size;为0即可。

可以释放空间,但没必要。

流插入(operator<<)和流提取(operator>>)

这里的流插入不一定要写成友元。string还可以通过访问operator[]来访问数据。

若流插入的形参用const修饰,则流插入调用的一些相关的函数比如迭代器也需要用const修饰this

流提取的形参不能用const修饰,因为流提取需要修改后台的数据。

输入的内容通过流提取尾插到string对象中,需要注意的是系统自带的流提取(cinoperator默认将空格和换行作为不同数据的分界,所以需要将所有的字符包括空格和换行符也读取。

读取所有字符的方法:scanf("%c",&st[i]);istream::get()getchar()

既然选择了c++,所以更推荐用istream::get

在读取之前先将string对象清空clear)。

流提取还可以用申请在函数作用域内的数组优化,将数组通过operator+=尾插给对象。这样做可以减少扩容次数。

查找(find)

请添加图片描述

实现的时候可以用暴力查找,时间复杂度为 O ( n × m ) O(n\times m) O(n×m)。如果觉得找匹配串的效率太低还可以尝试kmp算法(理论效率很高,实际效率看情况)或字符串BM算法。

如果是从某个位置开始找,则用_str+pos即可。

取字符串(substr)

string substr (size_t pos = 0, size_t len = npos)const;

因为要返回临时对象,所以需要先实现拷贝构造函数。

生成临时对象,再从*this中取指定长度尾插给临时对象,最后返回临时对象(不能串引用返回)。

如果要取的长度为npos或大于_size,则一直取到尾即可。

取字符串的时候需要注意浅拷贝问题。

string模拟参考程序

模拟实现时将自己的string放在另一个命名空间mystd里,这样想用库里的string就可以加域名std::,想用自己的string就加域名mystd::

最终参考程序(无反向迭代器)的某 .h 文件:

#pragma once
#include<cassert>
#include<iostream>
using std::ostream;
using std::istream;namespace mystd {template<class T>void swap(T& a, T& b) {T tmp = a;a = b;b = tmp;}class string {public:const static size_t npos;//构造函数string(const char* s = "") {//求s的长度size_t len = 0;const char* tmps = s;while (*tmps) {++len;++tmps;}//初始化长度、容量等信息_size = len;_capacity = len;_str = new char[_capacity + 1]{ '\0' };//拷贝tmps = s;char* tmps2 = _str;while (*tmps2++ = *tmps++);}//析构函数~string() {delete[] _str;_str = nullptr;_size = _capacity = 0;}//返回c风格字符串的地址const char* c_str() const {return _str;}//用[]进行索引访问char& operator[](size_t pos) {assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const {assert(pos < _size);return _str[pos];}//返回当前字符串的长度size_t size()const {return _size;}//返回容量size_t capacity()const {return _capacity;}//迭代器typedef char* iterator;typedef const char* const_iterator;iterator begin() {return _str;}iterator end() {return _str + _size;}const_iterator begin() const {return _str;}const_iterator end() const {return _str + _size;}//交换,独立于namespace std中的swapvoid swap(string& a) {//两个对象:*this,amystd::swap(_str, a._str);mystd::swap(_size, a._size);mystd::swap(_capacity, a._capacity);}//拷贝构造函数string(const string& str):_str(nullptr)//临时对象不经过构造函数会产生随机数, _size(0), _capacity(0) {string tmp(str._str);//生成第2个临时对象mystd::string::swap(tmp);}//赋值重载:string& operator=(string tmp) {mystd::string::swap(tmp);return *this;}string& operator=(char c) {char ch[2] = { c,'\0' };string tmp(ch);mystd::string::swap(tmp);return *this;}//扩容void reserve(size_t n = 0) {if (n > _capacity) {char* tmp = new char[n + 1]{ '\0' };char* aa = tmp, * bb = _str;while (*aa++ = *bb++);delete[]_str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0') {if (n <= _size) {_str[n] = '\0';_size = n;while (n < _capacity)_str[n++] = '\0';return;}reserve(n);while (_size < n) {_str[_size] = ch;++_size;}}//插入string& insert(size_t pos, const string& s) {assert(pos <= _size);reserve(_size + s._size);size_t end = _size - 1;while (end >= pos && end != -1) {_str[end + s._size] = _str[end];--end;}for (size_t i = 0; i < s._size; i++) {_str[pos + i] = s[i];}_size += s._size;return *this;}//尾插string& operator+=(const string& s) {insert(_size, s);_str[_size] = '\0';return *this;}string& operator+=(char s) {push_back(s);return *this;}void push_back(char c) {char ch[2] = { c,'\0' };insert(_size, ch);}//比较运算符重载bool operator==(const string& s) const {if (_size != s._size)return 0;for (size_t i = 0; i < s._size; i++) {if (_str[i] != s._str[i]) {return 0;}}return 1;}bool operator<(const string& s) const {for (size_t i = 0; i < s._size && i < _size; i++) {if (_str[i] > s._str[i]) {return 0;}}if (_size > s._size)return 0;return 1;}bool operator>(const string& s) const {return !(*this == s || *this < s);}bool operator>=(const string& s) const {return !(*this < s);}bool operator<=(const string& s) const {return !(*this > s);}bool operator!=(const string& s) const {return !(*this == s);}//删除string& erase(size_t pos = 0, size_t len = npos) {assert(pos < _size);if (len == npos || pos + len >= _size) {_str[pos] = '\0';_size = pos;return *this;}while (pos + len < _size) {_str[pos] = _str[pos + len];++pos;}_str[pos] = '\0';_size -= len;return *this;}//清空void clear() {_str[0] = '\0';_size = 0;}//查找size_t find(const string& s, size_t pos = 0)const {for (size_t i = 0, ti; i <= _size - s._size; i++) {ti = i;for (size_t j = 0; j < s._size; j++) {if (s[j] == _str[ti])++ti;elsebreak;}if (ti == i + s._size)return i;}return npos;}size_t find(char c, size_t pos = 0)const {for (size_t i = 0; i < _size; i++) {if (_str[i] == c)return i;}return npos;}//提取片段string substr(size_t pos = 0, size_t len = npos)const {string tmp;if (len == npos || pos + len > _size) {while (tmp += _str[pos++], pos < _size);return tmp;}while (tmp += _str[pos++], tmp._size < len);return tmp;}private:char* _str;size_t _size;size_t _capacity;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& st) {for (const auto& x : st)out << x;return out;}istream& operator>>(istream& in, string& s) {s.clear();char buff[129];size_t i = 0;char ch;//连同空格一起接收while (ch = in.get(), ch != ' ' && ch != '\n') {buff[i++] = ch;if (i == 128) {buff[i] = '\0';s += buff;i = 0;}}if (i != 0) {buff[i] = '\0';s += buff;}return in;}
}

相关文章:

  • Python OpenCV性能优化与部署实战指南
  • wordpress自学笔记 第三节 独立站产品和类目的三种展示方式
  • RabbitMQ--进阶篇
  • AI Agent(9):企业应用场景
  • 【Bootstrap V4系列】学习入门教程之 组件-巨幕(Jumbotron)和列表组(List group)
  • Java中的JDK7和JDK8时间类详解
  • 数字电子技术基础(五十七)——边沿触发器
  • Qt 窗口部件(2)输入部件详解
  • Canvas基础篇:虚线操作setLineDash和lineDashOffset详解
  • 前端性能指标及优化策略——从加载、渲染和交互阶段分别解读详解并以Webpack+Vue项目为例进行解读
  • 空战数据链基础术语解析:从概念到实战应用的入门指南
  • 联合类型的逻辑或关系与类型保护
  • 分享一个可以用GPT打标的傻瓜式SD图片打标工具——辣椒炒肉图片打标助手
  • 第26节:卷积神经网络(CNN)-数据增强技术(PyTorch)
  • 网络安全设备配置与管理-实验5-p150虚拟防火墙配置
  • Agent杂货铺
  • Linux-Ubuntu安装Stable Diffusion Forge
  • qt 布局管理
  • Java开发经验——阿里巴巴编码规范经验总结2
  • [强化学习的数学原理—赵世钰老师]学习笔记01-基本概念
  • 美国务卿鲁比奥将前往土耳其参加俄乌会谈
  • 《蛮好的人生》:为啥人人都爱这个不完美的“大女主”
  • 三亚通报救护车省外拉警报器开道旅游:违规违法,责令公司停业整顿
  • 习近平致电祝贺阿尔巴尼斯当选连任澳大利亚总理
  • 字母哥动了离开的心思,他和雄鹿队的缘分早就到了头
  • 缺字危机:一本书背后有多少“不存在”的汉字?