【C++】STL——string类的模拟实现
创始人
2024-05-30 03:08:49
0

文章目录

  • 👉string类👈
    • 📕 概念
    • 📕 成员变量
    • 📕 构造函数、析构函数
    • 📕 size() 、getstr() 函数
    • 📕 拷贝构造
    • 📕 赋值重载
    • 📕 迭代器
    • 📕 运算符重载
    • 📕 尾插
    • 📕 insert() 、erase() 函数
    • 📕 流插入、流提取
  • 👉源代码👈

👉string类👈

📕 概念

string是C++标准库的一个重要的部分,主要用于字符串处理。该类提供了上百个成员函数,本文并不逐一实现,只是实现部分 function 的功能。

📕 成员变量

如下,_str 标识字符串的指针,_size标识的是字符串长度,_capacity表示当前对象的最大存储容量,npos是在erase函数(删除单个字符或者字符串)里使用的,和 string 类的标准用法一样。

	private:char* _str;int _size;      // 目前的长度,不包括 \0int _capacity;  // 最大容量,不包括最后一个 \0static size_t npos; 

📕 构造函数、析构函数

如下,要使用初始化列表。同时,在这里需要注意几个点。

  • 传参,使用缺省参数,如果无参数则为 “”
  • 初始化列表只初始化 _size ,如果同时初始化 _capacity,根据初始化列表的规则,是按照成员变量声明的顺序来初始化的,所以如果成员变量的声明顺序改变,会导致一些错误(某个变量为随机值等等),所以在函数体内部对其他的变量进行赋值。
  • _str 要开辟 _capacity+1 的空间,因为要拿多出的一个空间来存储 ‘\0’ 。

同时,_str 开辟 _capacity+1 长度的空间还有一个原因,就是为了方便析构,因为析构是使用delete[ ] 的,如果 _str = new char; 会导致 new 和 delete 不匹配。

		string(const char* s = ""):_size(strlen(s))    {_capacity = _size == 0 ? 3 : _size + 1;_str = new char[_capacity+1];strcpy(_str, s);}

析构函数应该不需要过多解释,如下。

		~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}

📕 size() 、getstr() 函数

这两个函数要使用 const 修饰成员函数,一方面,该函数不需要改变对象里的任何值;另一方面,如果静态成员函数内部 需要调用该函数,要保证可以被调用(静态成员函数内部 不可以 调用非静态的成员函数和成员变量)。

		int size()const{return _size;}char* getstr()const{return _str;}

📕 拷贝构造

如下,strcpy 会自动复制 \0 ,所以不需要担心最后的 \0 问题。

		string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(s._str, _str);}

📕 赋值重载

赋值重载的注意点和代码如下。

  • 要先排除赋值重载某个对象本身的情况,用 if 判断即可。
  • 不能一开始就直接删除原来的数据,因为如果new失败,会导致错误。
		string operator=(const string& s){if (_str != s._str){//delete[] _str;   // 这样写并不好,可能会new失败//_str = new char[s._capacity];//_size = s._size;//_capacity = s._capacity + 1;char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);_str = tmp;_size = s._size;_capacity = s._capacity + 1;}return *this;}

📕 迭代器

如下,迭代器的设计可以使用函数重载,分别是静态和非静态。反向迭代器设计较为复杂,这里先不做介绍。

		typedef char* iterator;typedef const char* const_iterator;const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}iterator begin(){return _str;}iterator end(){return _str + _size;}

与此同时,迭代器中的 begin 设计完成之后,就可以使用范围 for 。范围 for 这个语法糖,必须要在类里面实现 begin ,函数名不能有错,也不能出现大写。

for (auto ch : s2) {cout << ch << " ";}

📕 运算符重载

如下,对于直接使用方括号取值,可以重载,因为涉及到能改变和不能改变所取的值。

同时,对于比较运算符的重载,可以直接复用。
但是也要用 const 修饰成员函数,可以看 >= 重载的函数体内部和注释。

		char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos)const{assert(pos < _size);return _str[pos];}bool operator>(const string& s)const{return strcmp(_str, s._str) > 0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}bool operator>=(const string& s)const{return (*this > s) || (s == *this); // 如果不设为const,这样会报错,因为 s 调用 == ,静态成员调用非静态成员函数}bool operator<(const string& s)const{return !(*this >= s);}bool operator<=(const string& s)const{return !(*this > s);}

📕 尾插

首先,reserve 函数是将对象进行扩容,使其可以存储更多的数据,但是并不会更改原有的数据,所以其函数内部不涉及 _size 的修改。
pushback,尾插一个字符。
append,尾插字符串,先将一部分字符串后移,再插入。

当然了,我们更喜欢使用的是 += ,可以直接调用 pushback 和 append。

		void reserve(int n) // 扩容,不改变数据{char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}void pushback(char ch){if (_size + 1 > _capacity){reserve(_capacity*2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){int len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);  // 如果使用strcat,这个函数需要自己去找 \0 ,比较浪费时间_size += len;}string& operator+=(const char ch){pushback(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}

📕 insert() 、erase() 函数

insert 函数重载,在任意位置插入单个字符或者字符串。
需要注意的是,insert是通过下标来访问和插入的,同时,存储下标的数据类型是 size_t ,无符号整形,该类型数据不会有负数,当取到理论上的 -1 时,实际上时它所能存储的最大正整数。如下图。
所以在 insert() 函数的第一个 while 循环语句上面,size_t end = _size +1 ; 这样子的话,最后end 的位置就是在下标为 1 的地方,不会变成 ffff (十六进制)而导致死循环。
那么为什么不直接用 int 类型的数据呢?因为参数 pos 是 size_t (STL标准里面也是这样),end 和 pos 比较,会把 end 当成 size_t 的类型和 pos 比,这里旨在模拟 STL 实现 string,所以要尽可能地贴合标准库。

请添加图片描述

erase 函数是在任意的位置删除任意长度的字符。

string& insert(size_t pos, const char ch){assert(pos <= _size);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);int len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size+len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}string& erase(size_t pos, size_t len = npos){assert(pos <= _size);// 要删除的内容大于等于pos之后的长度if (len == npos || pos + len >= _size) // 要判断 len == npos ,否则如果 pos+len 溢出就麻烦了{_str[pos] = '\0';_size = pos;}else  // 要删除的内容没有超过最后一个字符{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}

📕 流插入、流提取

在流插入的时候,不可以直接 in>>ch; 要当成一个字符一个字符输入,因为 cin 会把空格和换行当作是字符串之间的分割符,不会将其当作一个完整的字符串,所以要使用 istream 类里面的 get 函数,该函数可以拿到空格和换行,不会将其当作间隔处理 。

此外,如果输入字符串过长,每一次都 += ,可能会需要频繁扩容,空间消耗比较大,所以可以设计一个缓冲区,存储满之后将缓冲区里面的数据放到 s 里面。当然,while 循环结束后也要判断缓冲区是否为空,因为可能最后一次没有将缓冲区填满,而导致没有将最后一段字符串放到 s 里面。

istream& operator>>(istream& in, string& s){s.clear(); // 要输入之前先清空char ch = in.get();   char buff[128];       // 缓冲区int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127)   // 满了,最后一个位置放 \0 ,然后将缓冲区的数据放到s里面{buff[127] = '\0';s += buff;i = 0;}ch = in.get();  }if (i)  // 缓冲区还有数据没有存到s里面的情况{buff[i] = '\0';s += buff;}return in;}ostream& operator<<(ostream& out, const string& s){// 自己写了迭代器,就可以使用范围 for 了,可以不用友元函数for (auto ch : s){out << ch;}return out;}

👉源代码👈

如下,是 string 类的源代码,将它放到一个命名空间里面,为了防止和标准库里面的某些名字冲突。


#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include
#include
#includenamespace simulate
{class string{public:typedef char* iterator;typedef const char* const_iterator;const_iterator begin()const{return _str;}const_iterator end()const{return _str + _size;}iterator begin(){return _str;}iterator end(){return _str + _size;}// string(const char* s = nullptr) 是不可以的,传参 '\0' 也不可以。因为cout对于char*类型的,是一直读到 \0 为止string(const char* s = ""):_size(strlen(s))    // 如果同时初始化_capacity,会导致按照申明的顺序初始化,顺序改动会造成随机值{_capacity = _size == 0 ? 3 : _size + 1;_str = new char[_capacity+1];strcpy(_str, s);}string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(s._str, _str);}string operator=(const string& s){if (_str != s._str){char* tmp = new char[s._capacity + 1];strcpy(tmp, s._str);_str = tmp;_size = s._size;_capacity = s._capacity + 1;}return *this;}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];}int size()const{return _size;}bool operator>(const string& s)const{return strcmp(_str, s._str) > 0;}bool operator==(const string& s)const{return strcmp(_str, s._str) == 0;}bool operator>=(const string& s)const{return (*this > s) || (s == *this); // 如果不设为const,这样会报错,因为 s 调用 == ,静态成员调用非静态成员函数}bool operator<(const string& s)const{return !(*this >= s);}bool operator<=(const string& s)const{return !(*this > s);}void resize(size_t n,char ch='\0'){// 删除数据,保留前n个if (n < _size){_size = n;_str[_size] = '\0';}else if (n > _size)  // 不用考虑 n = _size 的情况,因为这种情况不需要改变任何值{if (n > _capacity){reserve(n);}size_t i = _size;while (i < n){_str[i] = ch;i++;}_size = n;_str[_size] = '\0';}}void reserve(size_t n) // 扩容,不改变数据{char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}void pushback(char ch){if (_size + 1 > _capacity){reserve(_capacity*2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){int len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);  // 如果使用strcat,这个函数需要自己去找 \0 ,比较浪费时间_size += len;}string& operator+=(const char ch){pushback(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}string& insert(size_t pos, const char ch){assert(pos <= _size);if (_size + 1 > _capacity){reserve(_capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;return *this;}string& insert(size_t pos, const char* str){assert(pos <= _size);int len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}size_t end = _size+len;while (end > pos + len - 1){_str[end] = _str[end - len];--end;}strncpy(_str + pos, str, len);_size += len;return *this;}string& erase(size_t pos, size_t len = npos){assert(pos <= _size);// 要删除的内容大于等于pos之后的长度if (len == npos || pos + len >= _size) // 要判断 len == npos ,否则如果 pos+len 溢出就麻烦了{_str[pos] = '\0';_size = pos;}else  // 要删除的内容没有超过最后一个字符{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}size_t find(const char ch,size_t pos=0){assert(pos <= _size);for (int i = pos; i < _size; i++){if (_str[ch] = i) return i;}return npos;}size_t find(const char* str, size_t pos = 0){assert(pos < _size);char* p = strstr(_str, str);if (p == nullptr){return npos;}return p - _str;}void clear(){_str[0] = '\0';_size = 0;}~string(){delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}private:char* _str;int _size;      // 目前的长度,不包括 \0int _capacity;  // 最大容量,不包括最后一个 \0static const size_t npos; };const size_t string::npos = -1;istream& operator>>(istream& in, string& s){s.clear(); // 要输入之前先清空char ch = in.get();   char buff[128];       // 防止频繁扩容,缓冲区int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127)   // 满了,最后一个位置放 \0 ,然后将缓冲区的数据放到s里面{buff[127] = '\0';s += buff;i = 0;}ch = in.get(); }if (i)  // 缓冲区还有数据没有存到s里面的情况{buff[i] = '\0';s += buff;}return in;}ostream& operator<<(ostream& out, const string& s){// 自己写了迭代器,就可以使用范围 for 了,可以不用友元函数for (auto ch : s){out << ch;}return out;}}

相关内容

热门资讯

安卓系统的地图怎样下载,下载与... 你有没有发现,现在不管去哪里,手机地图都成了我们的好帮手?尤其是安卓系统的地图,功能强大,用起来超级...
安卓9.0系统挂机游戏,轻松享... 你有没有发现,自从安卓9.0系统更新后,手机里的游戏体验简直就像坐上了火箭!今天,就让我带你一起探索...
安卓系统怎么用迅雷下载,安卓系... 你有没有想过,在安卓系统上下载文件竟然也能这么简单?没错,今天就要来给你揭秘,如何用迅雷在安卓系统上...
安卓手机刷成学生系统,探索全新... 你有没有想过,你的安卓手机其实可以变身成一个充满学习氛围的学生系统呢?没错,就是那种看起来简洁、功能...
ios能迁移安卓系统吗,iOS... 你有没有想过,你的iPhone里的那些宝贝应用,能不能搬到安卓手机上继续使用呢?这可是不少手机用户的...
荣耀10安卓11系统,畅享极致... 你知道吗?最近手机界可是热闹非凡呢!荣耀10这款手机,自从升级到了安卓11系统,简直就像脱胎换骨了一...
安卓系统pc版电脑配置,打造流... 你有没有想过,安卓系统竟然也能在电脑上运行呢?没错,就是那个我们手机上常用的安卓系统,现在也能在PC...
tcllinux系统刷安卓系统... 你有没有想过,你的TCL Linux系统竟然也能升级成安卓系统呢?没错,就是那个我们日常使用的安卓系...
安卓13系统更新蓝牙,蓝牙功能... 你有没有发现,最近你的安卓手机好像变得不一样了?没错,就是那个神秘的安卓13系统更新,它悄悄地来到了...
安卓系统钉钉打开声音,安卓系统... 你有没有遇到过这种情况?手机里装了钉钉,可每次打开它,那声音就“嗖”地一下跳出来,吓你一跳。别急,今...
理想汽车操作系统安卓,基于安卓... 你有没有想过,一辆汽车,除了能带你去你想去的地方,还能像智能手机一样,给你带来智能化的体验呢?没错,...
安卓系统越狱还能升级吗,升级之... 你有没有想过,你的安卓手机越狱后,还能不能愉快地升级系统呢?这可是不少手机爱好者关心的大问题。今天,...
安卓系统蓝牙耳机拼多多,畅享无... 你有没有发现,最近蓝牙耳机在市场上可是火得一塌糊涂呢!尤其是安卓系统的用户,对于蓝牙耳机的要求那可是...
安卓变苹果系统桌面,桌面系统变... 你知道吗?最近有个大新闻在科技圈里炸开了锅,那就是安卓用户纷纷转向苹果系统桌面。这可不是闹着玩的,这...
鸿蒙系统怎么下安卓,鸿蒙系统下... 你有没有想过,你的手机里那个神秘的鸿蒙系统,竟然也能和安卓世界来一场亲密接触呢?没错,今天就要来揭秘...
手机安卓系统流程排行,便捷操作... 你有没有发现,现在手机的世界里,安卓系统就像是个大舞台,各种版本层出不穷,让人眼花缭乱。今天,就让我...
安卓系统左上角hd,左上角HD... 你有没有发现,每次打开安卓手机,左上角那个小小的HD标识总是默默地在那里,仿佛在诉说着什么?今天,就...
安卓系统软件文件,架构解析与功... 你有没有发现,手机里的安卓系统软件文件就像是一个神秘的宝库,里面藏着无数的宝藏?今天,就让我带你一起...
安卓系统输入法回车,探索安卓输... 你有没有发现,在使用安卓手机的时候,输入法回车键的奇妙之处?它就像是我们指尖的魔法师,轻轻一点,文字...
安卓修改系统时间的软件,轻松掌... 你有没有想过,有时候手机上的时间不对劲,是不是觉得生活节奏都被打乱了?别急,今天就来给你揭秘那些神奇...