『C++成长记』string模拟实现
🔥博客主页:小王又困了
📚系列专栏:C++
🌟人之为学,不日近则日退
❤️感谢大家点赞👍收藏⭐评论✍️
目录
一、存储结构
二、默认成员函数
📒2.1构造函数
📒2.2析构函数
📒2.3拷贝构造
📒2.4赋值重载
三、容量操作
📒3.1获取有效字符长度
📒3.2获取对象空间大小
📒3.3使用reserve扩容
四、字符串的遍历
📒4.1下标访问
📒4.2迭代器访问
五、修改操作
📒5.1尾插字符
📒5.2尾插字符串
📒5.3任意位置插入字符
📒5.4任意位置插入字符串
📒5.5+=重载
六、其他操作
📒6.1删除操作
📒6.2查找操作
📒6.3交换操作
📒6.4获取字符串
📒6.5运算符重载
📒6.6清理字符串
📒6.7流操作
🗒️前言:
在上一篇中我们对string类进行了简单的介绍,介绍了各个接口的作用和使用方法,今天我们将为大家介绍string常用接口的模拟实现。
一、存储结构
string本质上是一个char类型的顺序表,所以结构上和顺序表类似。
namespace bit
{class string{public:private:char* _str;size_t _size;size_t _capacity;const static size_t npos;};
}
结构上使用命名空间 bit 进行封装,防止与库冲突,其中:
- _str :指向存放字符串存空间的指针
- _size :表示当前所存储的有效字符个数
- _capacity :表示当前可存储的最大容量
- nops:此值设置为 -1,无符号整型转换就是42亿,且此值为const和静态参数具有全局效应,这个值常常被用来当作循环结束判断条件和查找时未找到的标志,某些函数设置其为缺省参数。
nops的初始化:
#include"string.h"namespace bit {const size_t string::nops = -1; }
小Tips:我们使用声明与定义分离实现,nops只能在CPP文件中定义,因为类里面的静态成员变量相当于全局变量,在.h文件中定义会出现链接错误。我们还要通过类名::成员(函数/变量) 定义和实现函数!
二、默认成员函数
📒2.1构造函数
string.h
string(const char* str = ""); //给缺省值 构造空串string.cpp
string::string(const char* str):_size(strlen(str))
{_str = new char[_size + 1];_capacity = _size;strcpy(_str, str);
}
构造函数的思路:
- 构造函数可以接收字符串,如果没有参数,默认构造一个空串
- 通过strlen先计算出字符串的长度,并通过初始化列表初始化_size
- 使用new开辟空间,这里我们要多开一个空间存放‘\0’
- 最终将字符串中的字符拷贝到我们所开的空间中
小Tips:因为_size的大小没有包含‘\0’,所以我们要多开辟一个空间。
📒2.2析构函数
我们开辟内存是使用 new[ ] 申请的,所以对应使用 delete[ ]释放。
string::~string()
{delete[] _str;_str = nullptr;_size = _capacity = 0;
}
📒2.3拷贝构造
拷贝构造函数,如果我们不写,编译器会默认生成一个,但是默认生成的拷贝构造函数只支持浅拷贝,新构造的对象只是拷贝了_str的指针地址,两个对象都指向同一块空间,最终两个对象析构时释放同一片空间的资源势必会导致程序崩溃!
我们需要新构造的对象通过拷贝构造开辟一片新的空间将数据复制过去,也就是深拷贝,需要我们自己写一个拷贝构造。
🌟传统写法:
string::string(const string& s) {_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity; }
🌟现代写法:
string::string(const string& s) {string tmp(s._str);swap(tmp); } //string s2(s1);
通过复用构造函数,构造出tmp对象,在将两个对象进行交换,就可以实现拷贝构造。
📒2.4赋值重载
赋值重载需要注意自己给自己赋值这种冗余的行为,同时也要控制空间大小
🌟传统写法:
string& string::operator=(const string& s) {if(this != &s){char* tmp = new char[s._size + 1];strcpy(tmp, s._str);delete[] _str;_str = tmp;_size = s._size;_capacity = s._capacity;}return *this; }
🌟现代写法:
string& string::operator=(const string& s) {if (this != &s){string tmp(s._str);swap(tmp);}return *this; }string& string::operator=(string tmp) {swap(tmp);return *this; }
三、容量操作
📒3.1获取有效字符长度
size_t string::size() const
{return _size;
}
小Tips:
- 这个函数比较小,可以写在类中形成内联函数。
- 对于不涉及对字符串的增删查改的函数,使用const修饰this增强安全性。
📒3.2获取对象空间大小
与size函数规则一致。
size_t string::capacity() const
{return _capacity;
}
📒3.3使用reserve扩容
void string::reserve(size_t n)
{char* tmp = new char[n + 1];strcpy(tmp, _str); //将原字符串的数据拷贝到新空间上delete[] _str; //释放原字符串的空间_str = tmp;_capacity = n;
}
四、字符串的遍历
📒4.1下标访问
下标访问是通过重载 [ ] 运算符实现的,在下标pos正确的情况下,返回当前下标字符的引用,否则assert报错。
char& string::operator[](size_t pos)
{assert(pos < _size);return _str[pos];
}
📒4.2迭代器访问
现在我们可以简单认为迭代器是指针对象,就是对指针的封装。
//迭代器的声明
typedef char* iterator;
typedef const char* const_iterator; //对数据无法修改
迭代器的begin返回字符串的地址,end返回字符串末端的下一个即‘\0’。
string::iterator string::begin()
{return _str;
}string::iterator string::end()
{return _str + _size;
}string::const_iterator string::begin()const
{return _str;
}string::const_iterator string::end()const
{return _str + _size;
}
五、修改操作
📒5.1尾插字符
在插入字符前,先要判断是否需要扩容。
void string::push_back(char ch)
{if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4: _capacity * 2;reserve(newcapacity);}_str[_size] = ch;_str[_size + 1] = '\0';_size++;
}
📒5.2尾插字符串
void string::append(char* s)
{size_t len = strlen(s);if (_size +len > _capacity){reserve(_size + len);}strcpy(_str + _size, s);_size += len;
}
📒5.3任意位置插入字符
我们要考虑头插的位置,end和pos的类型都是size_t,代码会陷入死循环,这里我们提供两种解决方法。
🌟方法一:
将end的类型定为int,同时将pos强转为int型。
void string::insert(size_t pos, char ch) {assert(pos <= _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}int end = _size;while (end >= (int)pos){_str[end + 1] = _str[end];--end;}_str[pos] = ch;++_size; }
🌟方法二:
将end定位到‘\0’的下一位。
void string::insert(size_t pos, char ch) {assert(pos <= _size);if (_size == _capacity){size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newcapacity);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;++_size; }
我们可以通过复用来实现尾插
void string::push_back(char ch)
{insert(_size, ch);
}
📒5.4任意位置插入字符串
void string::insert(size_t pos, const char* s)
{assert(pos <= _size);size_t len = strlen(s);if (_size + len > _capacity){reserve(_size + len);}//方法一://int end = _size;//while (end >= (int)pos )//{// _str[end + len] = _str[end];// --end;//}//方法二:size_t end = _size+len;while (end > pos+len-1){_str[end] = _str[end - len];--end;} memcpy(_str + pos, s, len);_size += len;
}
我们也可以通过复用来实现尾插字符串
void string::append(const char* str)
{insert(_size, str);
}
📒5.5+=重载
+=运算符可以在当前字符串尾部追加字符或字符串,我们可以通过复用push_back和append函数来实现。
//追加字符
string& string::operator+=(char ch)
{push_back(ch);return *this;
}//追加字符串
string& string::operator+=(const char* str)
{append(str);return *this;
}
六、其他操作
📒6.1删除操作
erase从pos下标开始删除len个字符,其中len是一个缺省参数,默认是npos。如果没有传值或len超过从pos位置开始到字符串尾部的字符个数则默认从pos位置开始删除后面的所有字符,且不允许在空串的情况下进行删除!
void string::erase(size_t pos, size_t len)
{assert(pos < _size);if (len >= _size - pos){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}
}
📒6.2查找操作
查找函数是find,是从pos位置开始查找,可以查找一个字符或一个子串,查找到后字符返回下标,字符串返回首字符的地址,如果有多个重复的字符或字符串,返回查找到的第一个字符的下标或字符串首的下标;如果没找到则返回npos。
🌟查找一个字符:
size_t string::find(char ch, size_t pos) {for (size_t i = pos; i < _size; i++){if (_str[i] == ch)return i;}return npos; }
🌟查找一个子串
size_t string::find(const char* str, size_t pos ) {char* p = strstr(_str + pos, str);return p - _str; }
📒6.3交换操作
void string::swap(string& s)
{std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capaicty, s._capaicty);
}
📒6.4获取字符串
string string::substr(size_t pos, size_t len)
{assert(pos < _size);if (len > _size - pos){string sub(_str + pos);return sub;}else{string sub;sub.reserve(len);for (size_t i = 0; i < len; i++){sub += _str[pos + i];}return sub;}
}
📒6.5运算符重载
逻辑判断运算符只需要实现两个,其余的通过复用就可以全部实现。
bool string::operator<(const string& s) const
{return strcmp(_str, s._str) < 0;
}bool string::operator>(const string& s) const
{return !(*this <= s);
}bool string::operator<=(const string& s) const
{return *this < s || *this == s;
}bool string::operator>=(const string& s) const
{return !(*this < s);
}bool string::operator==(const string& s) const
{return strcmp(_str, s._str) == 0;
}bool string::operator!=(const string& s) const
{return !(*this == s);
}
📒6.6清理字符串
clear函数支持清空一个字符串,但不是释放对象,区别于析构函数。clear函数清理字符串并不会引起缩容,只是在下标0位置置为 \0 ,_size置为0即可。
void string::clear()
{_str[0] = '\0';_size = 0;
}
📒6.7流操作
流操作属于iostream中的对象,所以不需要定义在类中作为成员函数,也不需要声明为友元,因为使用流体去和流插入需要ostream和istream对象作为左操作参数。
🌟流插入
ostream& operator<< (ostream& os, const string& str) {for (size_t i = 0; i < str.size(); i++){os << str[i];}return os; }
🌟流提取
istream& Mystring::operator>>(istream& in, string& s) {s.clear(); char buff[256] = {0}; char ch = in.get(); size_t sub = 0; while (ch != ' ' && ch != '\n') //当缓冲区中有空格和换行就结束提取{buff[sub++] = ch; if (sub == 255) {buff[sub] = '\0'; s += buff; sub = 0; }ch = in.get(); }if (sub != 0) {buff[sub] = '\0'; s += buff;}return is; }
小Tips:我们定义一个缓冲区buff,先将字符串输入到缓冲区中,如果字符串很长则分批写入string字符串中,每次写入string后就刷新缓冲区再继续接收,这样就避免了频繁开辟空间。
🎁结语:
本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。
相关文章:

『C++成长记』string模拟实现
🔥博客主页:小王又困了 📚系列专栏:C 🌟人之为学,不日近则日退 ❤️感谢大家点赞👍收藏⭐评论✍️ 目录 一、存储结构 二、默认成员函数 📒2.1构造函数 📒2.…...

【c++】C++ IO流
本专栏内容为:C学习专栏,分为初阶和进阶两部分。 通过本专栏的深入学习,你可以了解并掌握C。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:C 🚚代码仓库:小小unicorn的代码仓库&…...

解密智慧校园基础数据的学年管理功能
在智慧校园平台中,学年管理模块构成了教育活动有序运行的基石,它精心设计来适应多样化的学术日程,确保学校的各项事务都能在清晰规划的学年框架内顺利推进。这一核心功能不仅关乎时间的界定,更深层次地融入了教育管理的每一个细微…...

Python酷库之旅-第三方库Pandas(009)
目录 一、用法精讲 19、pandas.read_xml函数 19-1、语法 19-2、参数 19-3、功能 19-4、返回值 19-5、说明 19-6、用法 19-6-1、数据准备 19-6-2、代码示例 19-6-3、结果输出 20、pandas.DataFrame.to_xml函数 20-1、语法 20-2、参数 20-3、功能 20-4、返回值 …...

VPN 的入门介绍
VPN(虚拟专用网络) 简介 虚拟专用网络,简称虚拟专网(VPN),其主要功能是在公用网络上建立专用网络,进行加密通讯。在企业网络中有广泛应用。VPN网关通过对数据包的加密和数据包目标地址的转换实…...

移动UI: 什么特征会被认为是简洁风格,用案例告诉你
什么是简洁风格,恐怕一百个人有一百个是理解,本文通过理论分析案例的方式进行探讨。 移动 UI 中的简洁风格通常具有以下几个特征: 1. 平面化设计: 简洁风格的移动 UI 善于运用平面化设计,即去除过多的阴影、渐变和立…...

除了伦敦外,英国还有这些热门留学城市
在同学们选择出国留学时,首先要考虑到的便是择校的问题。除了排名、专业、录取要求之外,城市因素也占据了很大比重。 抛开学校自身的优势外,一座城市的氛围、成本、环境都是需要考虑的因素。下面就我们来盘点一下英国热门的留学城市。 爱丁…...

2390. 从字符串中移除星号
2390. 从字符串中移除星号 题目链接:2390. 从字符串中移除星号 代码如下: class Solution { public:string removeStars(string s) {vector<char> sta;for(int i0;i<s.size();i){if(s[i]*) {sta.pop_back();}else {sta.push_back(s[i])…...

UNION、UNION ALL、INTERSECT、MINUS
UNION、UNION ALL、INTERSECT、MINUS? 说明 UNION:对两个结果集进行并集操作,不包括重复行,同时进行默认规则的排序;IUNION ALL:对两个结果集进行并集操作,包括重复行,不进行排序&am…...

Perl 语言开发(九):深入探索Perl语言的文件处理
目录 1. 文件打开与关闭 1.1 打开文件 1.2 关闭文件 2. 读取文件内容 2.1 逐行读取 2.2 一次性读取整个文件 3. 写入文件内容 3.1 覆盖写入 3.2 追加写入 4. 文件测试操作 4.1 文件测试运算符 5. 文件路径操作 5.1 文件路径处理模块 5.2 获取文件路径信息 6. 文…...

稀疏之美:在Mojo模型中实现特征的稀疏表示
稀疏之美:在Mojo模型中实现特征的稀疏表示 在机器学习领域,特征的稀疏表示是一种高效的数据编码方式,尤其适用于具有大量特征和缺失值的数据集。稀疏表示使用特殊的数据结构来存储和处理数据,从而减少内存占用和提高计算效率。Mo…...

如何大幅减少 Vue.js 中的包大小和加载时间,提升用户体验!
大家好,我是CodeQi! 一位热衷于技术分享的码仔。 你知道吗,根据Google 的一项研究,如果网站加载时间超过 3 秒,53% 的移动用户会离开该网站? 性能优化是一个经常讨论的话题,但很多开发人员并不关心提高应用的速度。 在前端开发中,优化包大小和加载时间对于提升用户体…...

性能测试相关理解---性能测试流程(二)
六、性能测试流程(如何做性能测试?) 根据学习全栈测试博主的课程做的笔记 1、前期准备– 项目初期就开始,业务需求评审时尽量参与,对业务更深刻的认识(确定哪些是核心业务、哪些可能存在并发请求、确定什么地方会出现瓶颈,方便后…...

GD32 MCU ADC采样率如何计算?
大家在使用ADC采样的时候是否计算过ADC的采样率,这个问题非常关键! 以下为GD32F303系列MCU中有关ADC的参数,其中ADC时钟最大值为40MHz,12位分辨率下最大采样率为2.86MSPS.如果ADC时钟超频的话,可能会造成ADC采样异常&…...

.mkp勒索病毒:深度解析与防范
引言: 在数字化时代,网络安全问题日益严峻,其中勒索病毒作为一种极具破坏性的恶意软件,严重威胁着个人用户和企业机构的数据安全。在众多勒索病毒家族中,.mkp勒索病毒以其强大的加密能力和广泛的传播方式,成…...

5.opencv深浅拷贝
图像处理的复制操作 深浅拷贝 图像复制分成两种,第一种假复制,从原图片选择一部分图片拿出来观察,此时新生成的图片和原图实际上是同一张图片,即浅拷贝 将图片的一部分复制下来,放到新的内存中,即两张完全…...

C++11中新特性介绍-之(二)
11.自动类型推导 (1) auto类型自动推导 auto自动推导变量的类型 auto并不代表某个实际的类型,只是一个类型声明的占位符 auto并不是万能的在任意场景下都能推导,使用auto声明的变量必须进行初始化,以让编译器推导出它的实际类型,…...

STM32实现看门狗(HAL库)
文章目录 一. 看门狗1. 独立看门狗(IWDG)1.1 原理1.2 相关配置1.3 相关函数 2. 窗口看门狗(WWDG)2.1 原理2.2 相关配置2.3 相关函数 一. 看门狗 单片机在日常工作中常常会因为用户配置代码出现BUG,而导致芯片无法正常工…...

【漏洞复现】网络摄像头——弱口令
声明:本文档或演示材料仅供教育和教学目的使用,任何个人或组织使用本文档中的信息进行非法活动,均与本文档的作者或发布者无关。 文章目录 漏洞描述漏洞复现其他补充 漏洞描述 主流网络摄像头存在弱口令。 漏洞复现 JAWS 1)信息…...

视觉图像面积计算
在图像处理和计算机视觉中,计算对象面积的常见方法有两种:使用四邻域标记算法和使用轮廓计算。每种方法在不同情况下有各自的优缺点。 四邻域标记算法: 优点: 简单易实现。能够处理带有孔洞的复杂区域(只要孔洞不影响连…...

Vue笔记10-其它Composition API
shallowReactive与shallowRef shallow:浅的,和deep相反 shallowReactive:只处理对象最外层属性的响应式 shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理 如果有一个对象数据,结构比较深&a…...

AI集成工具平台一站式体验,零门槛使用国内外主流大模型
目录 0 写在前面1 AI艺术大师1.1 绘画制图1.2 智能作曲 2 AI科研助理2.1 学术搜索2.2 自动代码 3 AI智能对话3.1 聊天机器人3.2 模型竞技场 4 特别福利 0 写在前面 人工智能大模型浪潮滚滚,正推动着千行百业的数智化进程。随着技术演进,2024年被视为是大…...

北京交通大学学报
《北京交通大学学报》是经新闻出版广电总局批准,由教育部主管,北京交通大学主办的自然科学理论与技术类学术期刊。学报致力于全面反映交通运输和信息与通信领域相关学科的最新研究进展。主要刊登交通运输工程、系统科学、信息与通信工程、控制科学与工程…...

【LinuxC语言】手撕Http之处理POST请求
文章目录 前言声明POST的组成读取POST信息读取消息体长度读取消息体解析消息体How to use?总结前言 在互联网的世界中,HTTP协议无疑是最重要的协议之一。它是Web的基础,支持着我们日常生活中的大部分在线活动。尽管有许多现成的库可以处理HTTP请求,但了解其底层工作原理是…...

以软件定义推动智算中心建设
2024 年 6 月 27 日,由益企研究院和 CDCC 主办、OCTC 开放计算委员会协办、隆高展览承办的"2024 中国智算中心全栈技术大会、第 5 届中国数据中心绿色能源大会暨第 10 届中国(上海)国际数据中心产业展览会”在上海圆满结束。本次大会以&…...

Apache Seata分布式事务原理解析探秘
本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 本文来自 Apache Seata官方文档,欢迎访问官网,查看更多深度文章。 前言 fescar发布已有时日,分布式事务一直是业界备受关注的领域,fesca…...

MySQL-18-mysql source 执行 sql 文件时中文乱码
拓展阅读 MySQL 00 View MySQL 01 Ruler mysql 日常开发规范 MySQL 02 truncate table 与 delete 清空表的区别和坑 MySQL 03 Expression 1 of ORDER BY clause is not in SELECT list,references column MySQL 04 EMOJI 表情与 UTF8MB4 的故事 MySQL 05 MySQL入门教程&a…...

flutter环境安装(Mac+vscode)
以前据说flutter跨平台开发app很牛逼,最近突然想到这个东西,于是想体验一下flutter的开发流程,看看能否适合做独立开发。 我用的是mac,手机也是ios,就开始着手部署mac下的开发环境了。 开发后台的时候,一…...

【题解】—— LeetCode一周小结27
🌟欢迎来到 我的博客 —— 探索技术的无限可能! 🌟博客的简介(文章目录) 【题解】—— 每日一道题目栏 上接:【题解】—— LeetCode一周小结26 2024.7 1.最大化一张图中的路径价值 题目链接:…...

C++后端开发--网络编程基础
目录 一、网络编程基础概念 1.1 网络协议 1.2 IP地址和端口号 1.3 Socket 1.4 TCP协议的三次握手和四次挥手 TCP的三次握手 TCP的四次挥手 整个流程更通俗易懂 TCP 三次握手流程图 TCP 四次挥手流程图 1.5 详细介绍一下http协议 HTTP协议的主要特点 HTTP请求 HTT…...