【C++】STL---string类的模拟实现

news/2024/9/16 22:22:16/

目录

  • 前言
  • 类的属性
  • 构造函数
    • 构造函数
    • 析构函数
    • 拷贝构造函数
  • 操作符重载
    • 赋值操作符重载
    • 比较操作符重载
  • 获取字符串长度
  • 下标访问
  • 迭代器
  • 尾插一个字符
  • 追加字符串
  • 字符串清空
  • 字符串交换
  • 以C的方式返回字符
  • 判断字符串是否为空
  • 修改长度
  • 指定大小扩容
  • 查找字符
  • 查找子串
  • 指定位置插入字符
  • 指定位置插入字符串
  • 删除指定区间
  • 总结:

前言

  string类,想必大家都不陌生,这是一个用来管理字符串的类。让我们使用字符串时更方便,更遍历。所以今天我们就来简单的实现一下string类。


类的属性

我们要管理一个字符串,那么首先得有一个字符串。所以我们用char* 类型的指针,来指向一个块存储字符串的地址。_size则记录字符串的长度,_cacpcity 为字符串的空间容量

private:char* _str;size_t _size;size_t _cacpcity;

构造函数

构造函数

我们在实例化一个string对象的时候,可以string s("hello world"); 直接创建,也可以string s(); 使它初始时为空,所以我们可以用缺省的构造函数。如果不传参数,那么默认初始化为空。

	//缺省的构造函数 string(const char* str = ""): _size(strlen(str)),_cacpcity(_size){//开辟一块内存_str = new char[_cacpcity + 1];//有效容量是cacpcity,要多一个用来存放\0strcpy(_str, str);}

析构函数

析构函数我们只需要释放 _str指向的空间即可。

		//析构函数~string(){delete[] _str;_str = nullptr;_size = _cacpcity = 0;}

拷贝构造函数

拷贝构造,就是拷贝一个字符串 例如:string s1("hello world"); string s2(s1); 。所以我们开辟一块与要拷贝字符串一样大小的空间,再把它一一复制给新字符串即可。

//拷贝构造string(const string& s):_size(s._size), _cacpcity(s._cacpcity){//开辟一块和s一样的空间_str = new char[_cacpcity+1];strcpy(_str, s._str);}

操作符重载

赋值操作符重载

如果我们想用 = 操作符来给字符串赋值。那我们可以重载 = 操作符。

	//赋值操作符重载string& operator=(const string& s){//如果不是自己给自己赋值if (this != &s){//创建一块新空间char* tmp = new char[s._cacpcity+1];//拷贝strcpy(tmp, s._str);//销毁旧空间delete[] _str;_str = tmp;_size = s._size;_cacpcity = s._cacpcity;}return *this;}

当然也可以复用拷贝构造函数。

比较操作符重载

<重载

		//字符串比较函数重载bool operator<(const string& s){return strcmp(_str, s._str) < 0;}

== 重载

	bool operator==(const string& s){return strcmp(_str, s._str) == 0;}

<= 重载

		bool operator<=(const string& s){return (*this < s) || (*this == s);}

  >重载

		bool operator>(const string& s){return !((*this) <= s);}

 >=重载

		bool operator>=(const string& s){return !(*this < s);}

!=重载

		bool operator!=(const string& s){return !(*this == s);}

获取字符串长度

直接返回_size 即可

		//获取长度size_t size(){return _size;}

下标访问

字符串也可以进行下标访问,所以我们重载[]即可

		//下标访问char& operator[](size_t pos){return _str[pos];}

当然,如果访问的是const修饰的字符串,那我们只能读,不能写。

	//只读const char& operator[](size_t pos) const{return _str[pos];}

迭代器

		//定义2个迭代器,一个是可读可写,一个是只读typedef char* iterator;typedef const char* const_iterator;
//迭代器开始位置iterator begin(){return _str;}const_iterator begin() const{return _str;}//迭代器末尾位置iterator end(){return _str + _size;}const_iterator end()const{return _str + _size;}

尾插一个字符

就是在字符串末端插入一个字符,我们只需要在_size的位置插入,随后_size++即可。不过要保证容量必须充足。

		//尾插一个字符void push_back(char c){//检查容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}_str[_size] = c;_size++;_str[_size] = '\0';}

扩容函数

void AddCacpcity(size_t newCacpcity){//末尾位置留一个\0,所以+1char* str = new char[newCacpcity + 1];//旧字符串的内容拷贝到新字符串strcpy(str, _str);//销毁旧字符串delete[] _str;_str = str;_cacpcity = newCacpcity;}

追加字符串

如果想在尾部追加字符串的话,我们可以实现一个 append()函数。

		//追加一个字符串void append(const char* str){//检查容量if (_cacpcity < (_size)+strlen(str)){AddCacpcity(_size + strlen(str));}//直接在末尾的位置加上strstrcpy(_str + _size, str);_size += strlen(str);}

然后我们可以重载 +=运算符,拿来复用 append()函数。

		string& operator+=(const char* str){append(str);return *this;}

然后我们重载 +运算符,但需要注意的是,+运算符不改变当前字符串,所以我们要值返回。

		//+重载string operator+(const char* str){string s(*this);s += str;return s;}

字符串清空

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

	//清空void clear(){_str[0] = '\0';_size = 0;}

字符串交换

我们用std命名空间里面的swap函数,对每个成员进行交换。

		//字符串交换void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_cacpcity, s._cacpcity);}

以C的方式返回字符

c语言的字符串其实就是一个char*指针,遇到\0结束,所以我们直接返回str就可以了。

		//以c的方式返回字符串const char* c_str()const{return _str;}

判断字符串是否为空

直接判断第一个元素是否是 \0

		//判断字符串是否为空bool empty()const{return _str[0] == '\0';}

修改长度

修改长度我们要分多种情况,一般长度减少时,我们不改变现有容量。容量不够时,我们才增容。 如果 长度减少,或者不变,我们只需要把 减少的新长度给_size,然后把当前位置变成\0。如果是增加长度,我们要考虑容量,不够的话需要增容,然后memset函数把增容的部分改成你指定的字符(默认\0)。

		//修改长度void resize(size_t n, char c = '\0'){//如果修改的值比当前长度小if (n <= _size){//截断_size = n;_str[_size] = '\0';}//如果修改的值比当前长度大else{//扩容if(n > _cacpcity){AddCacpcity(n);}//从size位置到n的位置设置为cmemset(_str + _size, c, n - _size);//最后位置填上\0_size = n;_str[_size] = '\0';}}

指定大小扩容

之前写过一个扩容函数,直接把指定的大小传过去即可。还是老规矩,减少不处理。

	//指定容量。只增加,减少不处理void reserve(size_t n){if (n > _cacpcity){AddCacpcity(n);}}

查找字符

该查找只会找到从指定位置开始,第一个出现的字符。如果要查找第二个,那么就在第一个字符的后面开始查找。

		// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const{for (int i = pos; i < _size; i++){if (_str[i] == c)return i;}return nops;}

nops是一个无符号的数,代表找不到返回的值。

static const size_t nops = -1;

查找子串

C语言中有strstr函数,我们可以复用。

		// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const{char* tmp = strstr(_str, s);if (tmp == NULL)return nops;return tmp - _str;}

指定位置插入字符

只需要把pos位置后面的字符都往后移动一格,随后把字符放进pos位置。

	// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c){//判断容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}//pos位置后往后移size_t end = _size + 1;while (pos < end){_str[end] = _str[end - 1];end--;}_str[pos] = c;_size++;return *this;}

指定位置插入字符串

插入字符是都往后移动一格,插入字符串那就是把pos位置后面的字符都像后 移动字符串的长度格。然后把字符串从pos位置开始写入。

//插入字符串string& insert(size_t pos, const char* str){size_t len = strlen(str);//判断容量if (_cacpcity < (len + _size)){AddCacpcity(len + _size);}//移动len格size_t end1 = _size+1;size_t end2 = _size + len ;while (pos < end1 ){_str[end2] = _str[end1-1]  ;end1--;end2--;}int i = pos;while (*str){_str[i++] = *str++;}_size += len;return *this;}

删除指定区间

从pos位置开始,删除len个空间。那么我们需要先判断 len是否大于pos后面的长度,如果大于那就是后面全部删除,那么我们只需要把pos位置置空成\0即可。如果不大于就说明在中间删除,那么就从pos的第len个位置开始往pos后面的位置覆盖,覆盖到\0结束。

		// 删除string& erase(size_t pos, size_t len){//如果要删除的长度大于后面的剩余长度if (len >= _size - pos){len = _size - pos;_size -= len;_str[pos] = '\0';return *this;}//把后面的往前移,覆盖式删除size_t begin = pos+len;while (_str[begin]){_str[begin-len] = _str[begin];begin++;}_size -= len;return *this;}

全部代码:

namespace wyl
{class string{public:typedef char* iterator;typedef const char* const_iterator;//缺省的构造函数 string(const char* str = ""): _size(strlen(str)),_cacpcity(_size){//开辟一块内存_str = new char[_cacpcity + 1];strcpy(_str, str);}//析构函数~string(){delete[] _str;_str = nullptr;_size = _cacpcity = 0;}//拷贝构造string(const string& s):_size(s._size), _cacpcity(s._cacpcity){//开辟一块和s一样的空间_str = new char[_cacpcity+1];strcpy(_str, s._str);}//赋值操作符重载string& operator=(const string& s){//如果不是自己给自己赋值if (this != &s){//创建一块新空间char* tmp = new char[s._cacpcity+1];//拷贝strcpy(tmp, s._str);//销毁旧空间delete[] _str;_str = tmp;_size = s._size;_cacpcity = s._cacpcity;}return *this;}//获取长度size_t size(){return _size;}//下标访问char& operator[](size_t pos){return _str[pos];}//只读const char& operator[](size_t pos) const{return _str[pos];}//迭代器开始位置iterator begin(){return _str;}const_iterator begin() const{return _str;}//迭代器末尾位置iterator end(){return _str + _size;}const_iterator end()const{return _str + _size;}// 扩容void AddCacpcity(size_t newCacpcity){char* str = new char[newCacpcity + 1];strcpy(str, _str);delete[] _str;_str = str;_cacpcity = newCacpcity;}//尾插一个字符void push_back(char c){//检查容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}_str[_size] = c;_size++;_str[_size] = '\0';}string& operator+=(char c){push_back(c);return *this;}//追加一个字符串void append(const char* str){if (_cacpcity < (_size)+strlen(str)){AddCacpcity(_size + strlen(str));}strcpy(_str + _size, str);_size += strlen(str);}string& operator+=(const char* str){append(str);return *this;}//+重载string operator+(const char* str){string s(*this);s += str;return s;}string operator+(const string& str){string s(*this);s += str._str;return s;}//清空void clear(){_str[0] = '\0';_size = 0;}//字符串交换void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_cacpcity, s._cacpcity);}//以c的方式返回字符串const char* c_str()const{return _str;}//判断字符串是否为空bool empty()const{return _str[0] == '\0';}//修改长度void resize(size_t n, char c = '\0'){//如果修改的值比当前长度小if (n <= _size){//截断_size = n;_str[_size] = '\0';}//如果修改的值比当前长度大else{//扩容if(n > _cacpcity){AddCacpcity(n);}//从size位置到n的位置设置为cmemset(_str + _size, c, n - _size);//最后位置填上\0_size = n;_str[_size] = '\0';}}//指定容量。只增加,减少不处理void reserve(size_t n){if (n > _cacpcity){AddCacpcity(n);}}//字符串比较函数重载bool operator<(const string& s){return strcmp(_str, s._str) < 0;}bool operator<=(const string& s){return (*this < s) || (*this == s);}bool operator>(const string& s){return !((*this) <= s);}bool operator>=(const string& s){return !(*this < s);}bool operator==(const string& s){return strcmp(_str, s._str) == 0;}bool operator!=(const string& s){return !(*this == s);}// 返回c在string中第一次出现的位置size_t find(char c, size_t pos = 0) const{for (int i = pos; i < _size; i++){if (_str[i] == c)return i;}return nops;}// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const{char* tmp = strstr(_str, s);if (tmp == NULL)return nops;return tmp - _str;}// 在pos位置上插入字符c/字符串str,并返回该字符的位置string& insert(size_t pos, char c){//判断容量if (_size == _cacpcity){AddCacpcity(_cacpcity == 0 ? 15 : _cacpcity * 2);}//pos位置后往后移size_t end = _size + 1;while (pos < end){_str[end] = _str[end - 1];end--;}_str[pos] = c;_size++;return *this;}//插入字符串string& insert(size_t pos, const char* str){size_t len = strlen(str);//判断容量if (_cacpcity < (len + _size)){AddCacpcity(len + _size);}//移动len格size_t end1 = _size+1;size_t end2 = _size + len ;while (pos < end1 ){_str[end2] = _str[end1-1]  ;end1--;end2--;}int i = pos;while (*str){_str[i++] = *str++;}_size += len;return *this;}// 删除string& erase(size_t pos, size_t len){//如果要删除的长度大于后面的剩余长度if (len >= _size - pos){len = _size - pos;_size -= len;_str[pos] = '\0';return *this;}//把后面的往前移,覆盖式删除size_t begin = pos+len;while (_str[begin]){_str[begin-len] = _str[begin];begin++;}_size -= len;return *this;}private:char* _str;size_t _size;size_t _cacpcity;static const size_t nops = -1;};
}

总结:

string类的细节还有很多,在这里只能简单实现一下。在实现的过程中需要注意的几点。

1.避免内存越界,否则析构时销毁会出错。
2.释放new出来的内存,避免内存泄漏。
3.需要深拷贝,否则会出现析构多次的情况。


http://www.ppmy.cn/news/8736.html

相关文章

大数据面试题Spark篇(1)

1.spark数据倾斜 数据倾斜俩大直接致命后果&#xff1a;Out Of Memory&#xff0c;运行速度慢。这主要是发生在Shuffle阶段。同样Key的数据条数太多了。导致了某个key所在的Task数据量太大了&#xff0c;远远超过其他Task所处理的数据量。 数据倾斜一般会发生在shuffle过程中…

4366. 上课睡觉

Powered by:NEFU AB-IN Link 文章目录4366. 上课睡觉题意思路代码4366. 上课睡觉 题意 有 N 堆石子&#xff0c;每堆的石子数量分别为 a1,a2,…,aN。 你可以对石子堆进行合并操作&#xff0c;将两个相邻的石子堆合并为一个石子堆&#xff0c;例如&#xff0c;如果 a[1,2,3,4,5…

聊聊接口文档的事儿

1、前言 大家好&#xff0c;欢迎来到我的吉鹿&#xff08;记录&#xff09;空间。 最近在做一个前后端分离的项目时&#xff0c;由于后端提供的 API 接口文档实在是一言难尽&#xff0c;导致了开发的效率大大降低。于是我出手了&#xff0c;我决定薅完我20几年的头发来肝一下…

谈谈SpringBoot

1. Spring Boot 简介 简化Spring应用开发的一个框架&#xff1b; 整个Spring技术栈的一个大整合&#xff1b; J2EE开发的一站式解决方案&#xff1b; 2. HelloWorld 功能&#xff1a;浏览器发送hello请求&#xff0c;服务器接受请求并处理&#xff0c;响应Hello World字符串&a…

【闲来无聊写个几个小特效——五角星,小光圈,探照灯】

五角星&#xff0c;见过吧&#xff0c;如果是你&#xff0c;你如何使用代码写一个五角星呢&#xff1f;思考一下&#xff0c;你会说&#xff0c;先这样在那样就好啦&#xff0c;可是真正上手的时候却修修改改磕磕绊绊来看一下今天的五角星如何用几行代码实现 1.绘制五角星 四行…

【ESP 保姆级教程】疯狂毕设篇 —— 案例:基于ESP8266和App的炫酷rgb ws2812彩灯控制

忘记过去,超越自己 ❤️ 博客主页 单片机菜鸟哥,一个野生非专业硬件IOT爱好者 ❤️❤️ 本篇创建记录 2023-01-01 ❤️❤️ 本篇更新记录 2022-01-01 ❤️🎉 欢迎关注 🔎点赞 👍收藏 ⭐️留言📝🙏 此博客均由博主单独编写,不存在任何商业团队运营,如发现错误,请…

C++string类介绍

目录 一、介绍 二、string类对象的构造 string类有如下构造方法&#xff1a; 类对象的容量操作 类对象访问及遍历 string对象的修改操作&#xff1a; std::string::insert std::string::erase std::string::c_str std::string::find std::string::substr 一、介绍…

将Android进行到底之内容提供者(ContentProvider)

文章目录前言一、ContentProvider是什么&#xff1f;二、使用示例1.为应用创建内容提供者2.使用内容提供者2.1 内容URI2.2 Uri参数解析2.2 使用内容URI操作数据3.ContentProvider妙用4 内容URI对应的MIME类型5.ContentProvider重点注意6 演示demo源码总结前言 随着现在的应用越…

[ASIS 2022 last CTF] 2022最后一赛

这个比赛太难了&#xff0c;就作了4个题 Crypto Bedouin 题目非常短&#xff0c;就是先生成一个小素数&#xff0c;然后堆到一起l次再补个1&#xff0c;比如235就变成2352352351这样&#xff0c;一开始以为是2进制&#xff0c;一直没作出来&#xff0c;方式也没错。后来发现原…

Unsupported conversion from LONG to java.sql.Timestamp

使用mybatisplus查询实体时报错Unsupported conversion from LONG to java.sql.Timestamp 先说结论&#xff1a; mybatis建议实体类上带上无参构造,当然java类虽然默认提供无参构造&#xff0c;但是现在都会用Data注解简化开发&#xff0c;里面会有 有参构造 所以默认的无参构造…

python自动化编程--正则表达式

目录 一.创建正则表达式 1.re模块 2.匹配Regex对象 二.正则表达式匹配更多模式 1.用括号分组 2.用管道匹配多个分组 3.用问号表示可选 4.用星号匹配零次或多次 5.用加号表示匹配一次或多次 6.用花括号匹配特定次数 三.贪心和非贪心匹配 四.字符分类 五.自定义字符…

QML教程(七) JavaScript

目录 一、对属性值使用 JavaScript 表达式 二、在 QML 中添加 JavaScript 函数 三、使用 JavaScript 文件 四、属性绑定中的 JavaScript 五、信号处理程序中的 JavaScript 六、将信号连接到 JavaScript 函数 七、启动执行 JavaScript QML 提供的 JavaScript 主机环境可以…

系统管理员喜欢 systemd 的 5 个理由

导读systemd 的速度和易用性使其成为管理现代 Linux 系统的流行方式。 系统管理员知道&#xff0c;在一台运行着的现代计算机上会发生很多事情&#xff1a;应用程序在后台运行、预定事件等待在特定时间被触发、事件写入日志文件、发送状态报告。在以前&#xff0c;不同的进程可…

2022年终总结与展望

2022年终总结 自2019年3月13日入驻CSDN&#xff0c;已经三年零九个月了。截至2022年12月31日&#xff0c;CSDN博客已发原创博文112篇&#xff0c;粉丝3616个&#xff0c;访问量超过157万次。 2019年12月31日数据情况&#xff1a; 2020年12月31日数据情况&#xff1a; 2021年1…

JAVA练习8

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 目录 前言 一、题目1- 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 二、题目2- 1.题目描述 2.思路与代码 2.1 思路 2.2 代码 总结 前言 提示&#xff1a;这里可以…

数据结构(一)

单链表 // head存储链表头&#xff0c;e[]存储节点的值&#xff0c;ne[]存储节点的next指针&#xff0c;idx表示当前用到了哪个节点 int head, e[N], ne[N], idx; // 初始化 void init() { head -1; idx 0; } // 在链表头插入一个数a void insert(int a) { e[idx] a, ne[i…

mysql事务一致性,原子性,持久性实现以及锁区别

Mysql事务一致性&#xff0c;原子性是如何实现的? 首先是通过锁和mvcc实现了执行过程中的一致性和原子性 其次是在灾备方面通过Redo log实观&#xff0c;Redo log会把事务在执行过程中对数据库所做的所有修改都记录下来&#xff0c;在之后系统崩溃重启后把事务所做的任何修改都…

Faster RCNN网络源码解读(Ⅸ) --- ROIAlign、TwoMLPHead、FastRCNNPredictor部分解析

目录 一、回顾以及本篇博客内容概述 二、代码解析 2.1 FasterRCNNBase类 2.1.1 forward正向传播 2.2 FasterRCNN类 2.2.1 roi_heads定义 2.3 TwoMLPHead类&#xff08;faster_rcnn_framework.py&#xff09; 2.4 FastRCNNPredictor类 2.5 RoIHeads类&#xff08;roi_…

四【Servlet基础】文件配置及环境搭建(重要)

文章目录4.1 Servlet概念4.2 Servlet作用4.3 Servlet开发步骤4.3.1 搭建开发环境4.3.2 创建项目4.3.3 部署Servlet4.3.4 配置Servlet4.3.5 测试运行4.1 Servlet概念 &#xff08;1&#xff09;Servlet&#xff1a;Server Applet的简称&#xff0c;是运行在Web服务器端的Java程…

2.0、Linux-基础了解

2.0、开机关机和基本目录介绍 开机登录&#xff1a; 开会机会启动许多程序&#xff1b;他们在Windows叫做 "服务" &#xff0c;在 Linux 中叫做 "守护进程"&#xff08;daemon&#xff09;&#xff1b; 开机成功后&#xff0c;他会显示一个文本登录…