ʕ • ᴥ • ʔ

づ♡ど

 🎉 欢迎点赞支持🎉

个人主页:励志不掉头发的内向程序员;

专栏主页:C++语言;


文章目录

前言

一、基本框架

二、构造函数

三、析构函数

四、拷贝构造

五、运算符重载

5.1、赋值重载

5.2、[]符重载

5.3、operator/>=/==/!=重载

5.4、operator<>重载

六、增删查改

6.1、push_back函数

6.2、append函数

6.3、operator+=函数

6.4、insert函数

6.5、erase函数

6.6、find函数

七、其他成员函数

7.1、c_str函数

7.2、size函数

7.3、capacity函数

7.4、reserve函数

7.5、substr函数

7.6、clear函数

八、迭代器

8.1、begin函数

8.2、end函数

总结


前言

我们上一章节讲解了有关string的函数的用法,本章节就来对string函数的重要函数进行模拟实现,让大家更好的了解我们的string函数的同时对类和对象的认识进一步加深。一起来看一下吧。


一、基本框架

我们先建立一个string.h的头文件,在里面建立string的基本框架。

#include <iostream>
#include <assert.h>// 建立命名作用域,防止和库容器命名冲突
namespace zxl
{class string{public:private:char* _str;size_t _size;size_t _capacity;};
}

我们string中有4个成员变量,有一个是字符串数小于16个时存在栈上的字符串数组buffer,这里不模拟实现,剩下的就是一个存字符串的指针,一个记录数量的size和一个记录开辟空间的capacity。有了这些基础的成员变量,接下来我们就来实现成员函数了。

二、构造函数

我们string的构造函数主要分为两种,其一是无参构造,也就是默认构造,其二就是带参构造。

无参构造我们让我们的指针指向一个'\0'字符,size和capacity默认为0即可。

string(): _str(nullptr),_size(0),_capacity(0)
{}

而我们的带参构造用初始化列表比较麻烦,因为先用size构建出参数的数量再去开辟空间好一点,但是我们初始化列表的初始化顺序是按照我们的声明的顺序的,而更改声明顺序代码看着就很怪,所以为了方便一点就直接在函数内部初始化了。

string(const char* s)
{_size = strlen(s);_capacity = _size;// 多一个位置给'\0'_str = new char[_size + 1];strcpy(_str, s);
}

当然,我们可以把这两个函数用缺省参数变成一个函数

// 默认会有\0		
string(const char* s = "")
{_size = strlen(s);_capacity = _size;// 多一个位置给'\0'_str = new char[_size + 1];strcpy(_str, s);
}

三、析构函数

析构函数只要把我们指向的资源释放了就行,很容易就不过多赘述。

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

四、拷贝构造

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

五、运算符重载

5.1、赋值重载

先销毁在拷贝。

string& operator=(const string& s)
{delete[] _str;_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;return *this;
}

5.2、[]符重载

我们这个符号重载的作用就是为了让我们的字符串可以像数组那样去查看和修改,所以我们这个函数主要就是传字符下标后返回引用来实现的。

char& operator[](size_t pos)
{// 这里加断言可以防止我们访问越界assert(pos < _size);return _str[pos];
}

同时还可以实现一个const版本。

const char& operator[](size_t pos) const
{assert(pos < _size);return _str[pos];
}

5.3、operator</<=/>/>=/==/!=重载

这里和库中保持一致,重载成全局函数吧。

同理,我们一般搞两个函数,后面的全部函数复用即可

<:

bool operator<(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) < 0;
}

==:

bool operator==(const string& s1, const string& s2)
{return strcmp(s1.c_str(), s2.c_str()) == 0;
}

<=:

bool operator<=(const string& s1, const string& s2)
{return s1 < s2 || s1 == s2;
}

>:

bool operator>(const string& s1, const string& s2)
{return !(s1 <= s2);
}

>=:

bool operator>=(const string& s1, const string& s2)
{return !(s1 < s2);
}

!=:

bool operator!=(const string& s1, const string& s2)
{return !(s1 == s2);
}

这里不用函数复用也蛮简单的,但是如果我们以后想要改一下条件时函数复用就只要该两个函数即可,所以在这里函数复用还是优于每个都写的。

5.4、operator<</>>重载

这两个函数也得是在全局域中实现才行

流输出:<<

ostream& operator<<(ostream& out, const string& s)
{for (auto ch : s){cout << ch;}return out;
}

流输入:>>

我们不能这样写

istream& operator>>(istream& in, string& s)
{// cin和scanf默认空格和换行是分隔符,会自动跳过,// 如果这样写就永远不会停下来了。char ch;in >> ch;while (ch != ' ' && ch != '\n'){s += ch;in >> ch;}return in;
}

而应该调用我们cin里的get函数,它是一个字符一个字符的读的,就没有分隔符了。

	istream& operator>>(istream& in, string& s){char ch;ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;}

但是由于我们原版流输入时会清空我们的字符串,所以我们也得清空一下。

istream& operator>>(istream& in, string& s)
{s.clear();char ch;ch = in.get();while (ch != ' ' && ch != '\n'){s += ch;ch = in.get();}return in;
}

这就是流输入和输出了。

但是如果我们一次输入太多的字符了,我们的空间不够的情况下会频繁的扩容,我们有没有什么办法去解决这一问题降低编译器损耗呢?我们可以创建一个字符串当缓冲区,先给它开辟一个空间,如果它的空间满了我们再一次性写入我们的string中,这样可以大大降低我们内存的扩容次数。

优化:

istream& operator>>(istream& in, string& s)
{s.clear();const int N = 256;// 缓冲字符串char buffer[N];int i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buffer[i++] = ch;if (i == N - 1){buffer[i] = '\0';s += buffer;i = 0;}ch = in.get();}if (i > 0){buffer[i] = '\0';s += buffer;}return in;
}

访问私有才需要友元,这里没有访问私有,所以就不用访问。

六、增删查改

6.1、push_back函数

我们上一章节知道我们string函数的主要作用就是再字符串后面插入一个字符。这个函数实现十分简单,无非就是str[size] = 字符后size++即可,但是我们得考虑我们的开辟的空间是否足够的问题,我们可以先写一个扩容函数reserve(下面有怎么实现),先判断够不够,不够就扩容即可。

void push_back(char c)
{// 如果相等就是空间不足if (_capacity == _size){// 扩容,防止容量初始值是0reserve(_capacity == 0 ? 4 : _capacity * 2);}// 插入_str[_size] = c;_size++;// 注意,不然打印就会有问题_str[_size] = '\0';
}

6.2、append函数

我们append函数可以插入字符串,那我们的扩容也得注意不能像push_back一样2倍扩容,不然可能会扩容后也不够用,所以得按需扩容。

void append(const char* str)
{size_t len = strlen(str);if (_size + len > _capacity){// 大于2倍要多少开多少,小于2倍开2倍reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}strcpy(_str + _size, str);_size += len;
}

6.3、operator+=函数

函数复用即可。

插入字符:

string& operator+=(char ch)
{push_back(ch);return *this;
}

插入字符串:

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

6.4、insert函数

这个函数的作用就是在字符串的任意位置插入,主要涉及字符的移动

插入字符:

void insert(int pos, const char ch)
{// 防止pos比字符数多assert(pos <= _size);if (_capacity == _size){reserve(_capacity == 0 ? 4 : _capacity * 2);}// 挪动字符for (int i = _size; i >= pos; i--){_str[i + 1] = _str[i];}// 插入_str[pos] = ch;++_size;
}

插入字符串:

void insert(int pos, const char* str)
{assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){// 大于2倍要多少开多少,小于2倍开2倍reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);}for (int i = _size; i >= pos; i--){_str[i + len] = _str[i];}for (int i = 0; i < len; i++){_str[i + pos] = str[i];}_size += len;}

6.5、erase函数

这个函数就是用来删除指定位置指定长度的字符串的,如果我们不写长度就默认从指定位置后面全部删除,所以我们要创建一个缺省参数,这个参数就用npos来替换,因为我们原来的string在类外也可以调用npos,所以这里决定创建一个静态成员函数。

static const size_t npos;

静态成员要在类外进行定义,同时这里得在不同文件中定义,不然就会重定义。

const size_t zxl::string::npos = -1;

同时erase函数就得分为我们要删的长度是不是超过了字符串长度的两种情况。

void erase(size_t pos, size_t len = npos)
{assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{for (int i = pos; i < _size - len; i++){_str[i] = _str[i + len];}_size -= len;_str[_size] = '\0';}}

6.6、find函数

此函数的作用就是查找字符或字符串,找到了就返回字符串下标,否则就返回npos值。

查找字符:

size_t find(const char ch, size_t pos = 0)
{for (int i = pos; i < _size; i++){if (_str[i] == ch) return i;}return npos;
}

查找字符串:

size_t find(const char* str, size_t pos = 0)
{assert(pos < _size);// 这里就用比较容易的办法解决啦,等后面有时间会讲具体怎么整。// 这个函数就是返回第一次匹配上的指针。const char* ptr = strstr(_str, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}
}

七、其他成员函数

7.1、c_str函数

我们上一章节说过,我们的c_str函数就是返回我们str的指针的,实现起来很简单。

const char* c_str() const
{return _str;
}

7.2、size函数

这个函数就是用来返回我们size的,实现起来也很容易。

const size_t size() const
{return _size;
}

7.3、capacity函数

同理

const size_t capactiy() const
{return _capacity;
}

7.4、reserve函数

这个函数的主要作用就是扩容,我们string插入字符时有的时候可能capacity会不够,此时就应该自动扩容,所以我们就可以先实现这个函数,然后再在插入中插入使其自动扩容。

手动扩容就是先开辟一个更大的空间后把原来空间的数据拷贝到新空间后释放原来的空间的操作

void reserve(size_t n)
{// 只扩容不缩容if (n > _capacity){// 多开辟一个存'\0'char* tmp = new char[n + 1];// 拷贝数据到新空间strcpy(tmp, _str);// 释放旧空间delete[] _str;_str = tmp;_capacity = n;}
}

7.5、substr函数

这个函数是返回我们想要的字符串的一部分的

string substr(size_t pos, size_t len = npos)
{assert(pos < _size);// 让我们的len变成有效长度if (len > _size - pos){len = _size - pos;}string sub;sub.reserve(len);for (int i = 0; i < len; i++){sub += _str[pos + i];}return sub;
}

我们要注意我们传值返回要调用拷贝构造,如果没有实现就是浅拷贝,会出问题。

7.6、clear函数

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

八、迭代器

这里的迭代器就简单实现一个iterator,我们实现的iterator就是将我们的char*改名一下而已。

typedef char* iterator;

8.1、begin函数

我们的iterator是char*,那我们的begin函数就是返回我们str的首地址啦。

iterator begin()
{return _str;
}

8.2、end函数

我们的end函数也就是我们str的最后一位地址的下一位了。

iterator end()
{// 首位加总数就是末尾的下一位return _str + _size;
}

此时我们就可以实现我们自己写的迭代器的遍历了。虽然可能还有一点小问题,但是勉强能用。

int main()
{zxl::string s1("hello world");zxl::string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";it++;}cout << endl;return 0;
}

同时我们的范围for也可以使用了。

int main()
{zxl::string s1("hello world");for (auto ch : s1){cout << ch << " ";}cout << endl;return 0;
}

这也更好的说明了我们的范围for底层就是迭代器。当然我们范围for想要实现必须我们迭代器和原来的一模一样,就比如如果我们的begin变成Begin了,我们的迭代器还是正常的,但是范围for就不行了。

当然,除了普通的迭代器还有const迭代器,const迭代器就是用我们的const char*来实现的

typedef const char* const_iterator;

只要将我们刚才写的begin和end的返回值改成const_iterator在增加一个const限制即可,不是说一定要把我们的begin变成cbegin、end变成cend才可以。

const_iterator begin() const
{return _str;
}const_iterator end() const
{return _str + _size;
}

反向迭代器比较复杂,我们后面再讲。


总结

以上便是我们string的一些重要代码的实现啦,希望大家能够仔细研究,下一章节我们继续往后学下一个容器vector,我们下一章节再见。

🎇坚持到这里已经很厉害啦,辛苦啦🎇

ʕ • ᴥ • ʔ

づ♡ど

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/96251.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/96251.shtml
英文地址,请注明出处:http://en.pswp.cn/diannao/96251.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux I/O 多路复用实战:深入剖析 Select 与 Poll

## 引言:从“阻塞”的餐厅到“事件驱动”的盛宴 想象一下,你是一家小餐馆的服务员。餐厅只有5张桌子。你的工作流程是这样的:走到1号桌,问他们是否要点菜,然后站在那里等他们决定;等他们点完,再去2号桌,同样站在那里等... 如果1号桌的客人看菜单看了半个小时,那么其他…

【clion】cmake脚本1:调试脚本并构建Fargo项目win32版本

调试脚本并构建 【clion】visual studio的sln转cmakelist并使用clion构建32位 报错 "D:\Program Files\JetBrains\CLion 2022.3.1\bin\cmake\win\x64\bin\cmake.exe" --debugger --debugger-pipe=\\<

VS2005里的快捷键

VS2005是微软在2005发布的一款支持C、C#、.net开发语言的集成开发工具&#xff0c;它支持的C版本为C03&#xff0c;但不支持C11&#xff0c;到VS2013才支持大部分的C11(简称C11)&#xff0c;到VS2015 update3才完全支持C11。既然VS2005不支持C11&#xff0c;而智能指针是C11才引…

前后端联合实现文件下载,实现 SQL Server image 类型文件下载

1、前端 Vue3QualityFile.vue<script setup lang"ts" name"QualityFile"> ...... // 下载&#xff0c;实现 SQL Server image 类型文件下载 const onDownloadClick async (fileNo: string) > {// const result await qualityFileDownloadFileWi…

【OneAI】使用Rust构建的轻量AI网关

LLM网关 统一大模型API入口&#xff0c;使用一个令牌调用多家模型&#xff0c;无需切换API Key兼容OpenAI输入输出规范内置10提供商和50模型&#xff0c;开箱即用支持自动负载、限流、IP限制、Token用量限制等功能支持钉钉、飞书、企微消息预警支持对不同提供商设置代理支持主…

Jenkins服务器配置SSH

1. 创建Jenkins用户的SSH配置ssh-keygen -t rsa -b 4096 -f /tmp/jenkins_ssh_key -N ""2. 在Jenkins服务器上执行以下命令# 切换到root用户 sudo su -# 创建Jenkins用户的SSH目录 mkdir -p /var/lib/jenkins/.ssh chown jenkins:jenkins /var/lib/jenkins/.ssh chmo…

nginx-下载功能-状态统计-访问控制

nginx-下载功能-状态统计-访问控制一、利用nginx做网站提供下载功能1. 进入nginx存放配置文件目录2. 编辑nginx.conf文件&#xff0c;开启下载功能3. 检查nginx.conf主配置文件是否正确4. 重启nginx服务5. 修改首页文件index.html6. 访问首页7. 去网页根目录下新建download目录…

GitLab CI/CD、Jenkins与GitHub Actions在Kubernetes环境中的方案对比分析

GitLab CI/CD、Jenkins与GitHub Actions在Kubernetes环境中的方案对比分析 随着容器化和微服务的普及&#xff0c;基于Kubernetes的部署已经成为主流。在实际的生产环境中&#xff0c;如何选择合适的CI/CD流水线方案以实现自动化构建、测试、部署和发布&#xff0c;直接关系到团…

tcp会无限次重传吗

tcp作为面向连接的&#xff0c;可靠的&#xff0c;字节流。最重要的特点就是可靠&#xff0c;其中重传又是保证可靠的重要前提。那么当tcp发送数据之后&#xff0c;收不到ack的情况下&#xff0c;会无限次重传吗。不会。# cat /proc/sys/net/ipv4/tcp_retries1 3 # cat /proc/s…

EasyAIoT平台部署

EasyAIoT官方文档专注于 AIoT 智能硬件与工业软件解决方案&#xff0c;提供从设备接入到云端管理的全栈服务http://pro.basiclab.top:9988/

功能测试相关问题

1.功能测试流程&#xff08;工作流程&#xff09;需求分析 -- 测试点分析&#xff08;xmind&#xff09;-- 编写测试计划/用例及评审 -- 执行测试用例&#xff08;开发提交测试&#xff09;-- 发现缺陷通过缺陷管理工具提交 -- 回归测试及bug验证&#xff08;开发提测新版本&am…

微服务网关中数据权限传递的那些坑:从 Feign 兼容性问题到解决方案

在微服务架构中&#xff0c;网关作为流量入口&#xff0c;常常需要承担身份认证、权限校验等职责。其中&#xff0c;用户数据权限的传递看似简单&#xff0c;却隐藏着不少兼容性陷阱。本文将结合实际项目经验&#xff0c;聊聊如何解决 Feign 调用时请求头中 JSON 数据的传递问题…

基于SpringBoot的旅游攻略系统网站【2026最新】

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏&#xff1a;…

thingsboard 服务器在2核CPU、2G内存资源配置下如何调优提速,适合开发/演示

物联网设备管理平台致力于为客户提供高效、可靠的物联网解决方案。基于开源物联网平台进行深度二次开发&#xff0c;我们打造了功能强大、灵活易用的物联网平台&#xff0c;广泛应用于智能家居、智能工厂、智能城市等多个领域 一、标准资源要求 CPU&#xff1a;建议至少 8 vCP…

C#多线程学习—主子线程,Invoke与begininvoke

一、为什么需要多线程操作&#xff1f;在 WinForms 应用程序中&#xff0c;主线程&#xff08;UI 线程&#xff09;负责处理用户交互和界面更新。当执行耗时操作&#xff08;如网络请求、文件读写、复杂计算&#xff09;时&#xff0c;如果直接在 UI 线程执行&#xff0c;会导致…

Vue 核心知识点总结

Vue 作为国内最普及的前端框架,是面试中考察概率最高的技术之一。本文将系统梳理 Vue 的核心知识点,包括 Vue3 与 Vue2 的区别、组件通信、生命周期、性能优化等关键内容。 🔥 Vue3 和 Vue2 的主要区别 Vue 3 提供了更现代化、更高性能的架构,通过 Composition API 和 P…

Python脚本每天爬取微博热搜-升级版

主要优化内容&#xff1a; 定时任务调整&#xff1a; 将定时任务从每小时改为每10分钟执行一次 调整了请求延迟时间&#xff0c;从1-3秒减少到0.5-1.5秒 缩短了请求超时时间&#xff0c;从10秒减少到8秒 性能优化&#xff1a; 移除了广告数据的处理&#xff0c;减少不必要的处理…

win11兼容运行远古游戏

游戏<远古战争>属于win7时代的游戏&#xff0c;在win11系统中运行&#xff0c;当鼠标移动立马卡住 解决方案&#xff1a; 最优&#xff1a;采用wmware虚拟机安装win7系统 最简单&#xff1a;使用 DxWnd 模拟老游戏运行环境 DxWnd官网下载 附录&#xff1a;游戏下载网址…

Docker小游戏 | 使用Docker部署人生重开模拟器

Docker小游戏 | 使用Docker部署人生重开模拟器 前言 项目介绍 项目简介 项目预览 二、系统要求 环境要求 环境检查 Docker版本检查 检查操作系统版本 三、部署人生重开模拟器小游戏 下载镜像 创建容器 检查容器状态 检查服务端口 安全设置 四、访问人生重开模拟器 五、总结 前言…

从依赖到自研:一个客服系统NLP能力的跃迁之路

前言&#xff1a;七年磨一剑的技术突围2015年在某平台上线初期&#xff0c;智能客服系统即采用行业通用的第三方NLP解决方案。在随后的八年发展历程中&#xff0c;系统虽历经三次重大版本迭代&#xff0c;但始终未能突破核心语义识别能力的外部依赖。这种依赖带来了三重困境&am…