string的模拟

  > 作者简介:დ旧言~,目前大二,现在学习Java,c,c++,Python等
> 座右铭:松树千年终是朽,槿花一日自为荣。

> 目标:能手撕模拟string类

> 毒鸡汤:时间并不可真的帮我们去解决哪些问题,它只不过是会把原来怎么也想不通的问题,变得不再重要了。

> 望小伙伴们点赞👍收藏✨加关注哟💕💕 

🌟前言

        前面我们已经了解string类,知道string下面的相关接口,Member functions(成员函数),Iterators(迭代器),Capacity(容量),Element access(元素访问),Modifiers(修改器),String operations (字符串操作)。今天咱就手搓一个string类,也是对string类加深印象,大家准备好了没,拿起小本本记好笔记。🤩🤩🤩

 ⭐主体

咱还像链表一样建三个文件,具体如下:

⭐模拟实现

咱把重要的接口实现,具体参考c++官网:string - C++ Reference

也可以参考博主写的String类:CSDN

🌙string.h头文件

在这个文件里,咱就简单的包含一下头文件....

内容如下:

  • 包含头文件
  • 命名空间:公有的函数   私有的成员变量   友元函数

咱看图解:



代码实现:

#include<assert.h>
#include<iostream>using namespace std;namespace lyk
{class string{public: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;}//c_str返回值const char* c_str() const{return _str;}//返回元素个数size_t size() const{return _size;}//构造函数string(const char* str = "");//构造函数(初始化 == 轮子)string(const string& s);//析构函数,释放空间~string();//赋值运算重载string& operator=(string s);const char& operator[](size_t pos) const;char& operator[](size_t pos);//扩容void reserve(size_t n);//push_back插入操作(在末尾假如字符)void push_back(char ch);//append插入操作void append(const char* str);//赋值运算重载string& operator+=(char ch);//赋值运算重载加入字符string& operator+=(const char* str);//赋值运算重载加入字符串//insert插入操作void insert(size_t pos, char ch); //(插入在pos位置-- > 字符)void insert(size_t pos, const char* str);//(插入在pos位置-->字符串)//erase删除操作void erase(size_t pos, size_t len = npos);//实现拷贝函数void swap(string& s);//find查找size_t find(char ch, size_t pos = 0);//查找一个字符size_t find(const char* str, size_t pos = 0);//查找字符串//返回string字符串的一个任意子串string substr(size_t pos = 0, size_t len = npos);//删除void clear();private://容量size_t _capacity = 0;//元素个数size_t _size = 0;//定义字符指针char* _str = nullptr;//const static size_t npos = -1;};//输出流istream& operator>>(istream& in, string& s);ostream& operator<<(ostream& out, const string& s);
}

这里的函数我不想详细讲解,等到后面会详细介绍,咱不急,摁住大家躁动的心。

🌙string.cpp源文件

        这里会和Test.cpp文件结合起来讲解,不能上来就咔咔咔写代码,写一个函数,测试一个函数,这样调试起来也比较轻松。可别忘记包含一下头文件  #include"string.h"。🫠🫠咱们也是在命名空间里面写哦。

💫构造函数

在构造函数中无非就是初始化列表和析构函数,简单来讲是实现初始化和释放内存。

构造函数:

  • 一个采用strcpy初始化
  • 另一个采用swap初始化(swap后面会实现)

析构函数:

这个就比较简单,字符指针置空,其它变量赋为0

代码实现:

namespace lyk
{//构造函数(初始化 == 轮子)string::string(const char* str){//元素个数_size = strlen(str);//容量_capacity = _size;//new一个容量_str = new char[_capacity + 1];//拷贝strcpy(_str, str);}//构造函数(初始化 == 轮子)string::string(const string& s){//定义一个string类string tmp(s._str);//拷贝swap(tmp);}//析构函数,释放空间string::~string(){//置空delete[] _str;_str = nullptr;_size = 0;_capacity = 0;}
}

💫拷贝构造

这里捏在构造函数有个代码,这里拿出来深度讲解。

这里我们上个代码

int main()
{string s1;string s2("hello world");string s3(s2);return 0;
}

        当我们使用默认拷贝构造函数来实例化对象时,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。这里只有浅拷贝,我们需要一个深度拷贝的拷贝构造。

就有了下面代码:

	//构造函数(初始化 == 轮子)string::string(const char* str){//元素个数_size = strlen(str);//容量_capacity = _size;//new一个容量_str = new char[_capacity + 1];//拷贝strcpy(_str, str);}

💫operator=

        不知道大家还记得这个函数不,swap(),这个接口可以实现深度拷贝,而这个函数在后面我们会手搓出来😁😁,大家先用着。

代码实现:

	string& string::operator=(string s){swap(s);return *this;}

注意:

返回的是*this(就是自己创建的string类)

💫operator[]

这个接口咱们有两种,分别实现不同调用的情况。

  1. 实现只可以读不可以写的接口,这里就要加const修饰。
  2. 实现一个可读可写的接口,就不需要const修饰咯
	const char& string::operator[](size_t pos) const{assert(pos <= _size);return _str[pos];}char& string::operator[](size_t pos){assert(pos <= _size);return _str[pos];}

这里需要明白,什么是可读,什么是可写🤔🤔

  • 可读:可读写遍历字符串,
  • 可写:可以在控制台上输入字符

💫迭代器

这里就放在string.h中,调用的时候就用起来比较方便。

public: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;}//c_str返回值const char* c_str() const{return _str;}//返回元素个数size_t size() const{return _size;}

具体使用如下:

void func(const string& s)
{//string::const_reverse_iterator it = s.rbegin();auto it = s.rbegin();while (it != s.rend()){// *it = 'x';cout << *it << " ";++it;}cout << endl;//auto it = s.crbegin();//while (it != s.crend())//{//	// *it = 'x';//	cout << *it << " ";//	++it;//}//cout << endl;
}int main()
{string s1("hello world");// 遍历// 1、下标+[]// 2、迭代器// 3、范围forstring::reverse_iterator it1 = s1.rbegin();while (it1 != s1.rend()){//*it1 = 'x';cout << *it1 << " ";++it1;}cout << endl;func(s1);return 0;
}

运行结果:

💫赋值运算重载

这里也实现两节接口

插入字符:

	//赋值运算重载加入字符string& string::operator+=(char ch){push_back(ch);return *this;}

这里使用了push_back函数,这个函数大家不陌生吧,尾插作用。(后面会实现这个函数,咱先用着)

 插入字符串:

	//赋值运算重载加入字符串string& string::operator+=(const char* str){append(str);return *this;}

这里使用了append函数,这个函数大家不陌生吧,它可以插入字符串,可以插入另一个string对象,而且可以指定n个字符插入,非常多样化。(后面会实现这个函数,咱先用着)

💫string类的扩容

既然是扩容的话肯定不能再在原来的字符串上扩容,因此需要开辟一个新的字符串,再把原来的字符串释放内存,在把现在开辟的空间拷贝到原来的字符串中去,再把capacity赋值。

resize其实也是扩容函数,但resize改变的是size的值,当size的值增大时自动触发string的扩容机制从而也增大了capacity的值

resize在增带size值的时候还会对没有字符的位置初始化,如果没有指定初始化内容就默认初始化为’\0’,而reserve不会进行初始化。

所以咱这里就实现一个函数,因为都基本上一样的。

	//扩容void string::reserve(size_t n){if (n > _capacity){//开辟string类char* tmp = new char[n + 1];strcpy(tmp, _str);//释放原来空间delete[] _str;//拷贝_str = tmp;_capacity = n;}}

💫string类的尾插

尾插嘛,push_back函数和append函数都可以尾插,这里两个函数就一起讲解了。

push_back只能插入一个字符:

	//push_back插入操作(在末尾假如字符)void string::push_back(char ch){//如果空间不足if (_size == _capacity){size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;reserve(newCapacity);}//加入字符_str[_size] = ch;_size++;//末尾需要'\0'_str[_size] = '\0';}

append可以插入字符也可以插入字符串:

	//append插入操作void string::append(const char* str){//计算原来的字符串的长度size_t len = strlen(str);//判断是否需要扩容if (_size + len > _capacity){reserve(_size + len);}//拷贝strcpy(_str + _size, str);//加上插入的字符串长度_size += len;}

上栗子:

int main()
{string s1("hello");s1.push_back(' ');s1.append("world");cout << s1 << endl;string s2 = "xxxx";const string& s3 = "xxxx";s2.append(++s1.begin(), --s1.end());cout << s2 << endl;s1 += '!';s1 += "xxxxx";s1 += s2;cout << s1 << endl;return 0;
}

运行结果:

💫string类的insert

这里实现两个接口😏😏😏

一个插入字符:

	//insert插入操作(插入在pos位置-->字符)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++;}

一个插入字符串:

	//insert插入操作(插入在pos位置-->字符串)void string::insert(size_t pos, const char* str){//断言assert(pos <= _size);//计算插入的字符串的大小size_t len = strlen(str);//判断是否需要扩容if (_size + len > _capacity){reserve(_size + len);}//向后覆盖int end = _size;while (end >= (int)pos){_str[end + len] = _str[end];--end;}//拷贝strncpy(_str + pos, str, len);_size += len;}

💫string类的erase

删除操作

	//erase删除操作void string::erase(size_t pos, size_t len){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{strcpy(_str + pos, _str + pos + len);_size -= len;}}

insert和erase的栗子:

int main()
{//string s1("hello world");//s1.insert(5, "xxxx");//cout << s1 << endl;s1.insert(0, 1, 'x');//s1.insert(s1.begin(), 'y');//cout << s1 << endl;string s1("hello world");s1.insert(5, "xxxx");cout << s1 << endl;s1.insert(0, 1, 'x');s1.insert(s1.begin(), 'y');cout << s1 << endl;s1.erase(5, 4);cout << s1 << endl;s1.erase(5);cout << s1 << endl;return 0;
}

运行结果:

💫string类的swap

交换函数:

	//实现拷贝函数void string::swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

💫string类的find

查找函数,这里有两个接口

一个查找字符:

	//find查找(查找一个字符)size_t string::find(char ch, size_t pos){//遍历一遍字符串就行for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}//当找不到就返回-1return npos;}

一个查找字符串:

	//find查找(查找字符串)size_t string::find(const char* str, size_t pos){// 返回一个指向str1中第一个出现的str2的指针,// 如果str2不是str1的一部分,则返回一个空指针。const char* ptr = strstr(_str + pos, str);if (ptr == nullptr){return npos;}else{return ptr - _str;}}

咱们举个栗子了解swap和find:

int main()
{string s1("hello  world hello");cout << s1 << endl;// 所有的空格替换为20%size_t pos = s1.find(' ', 0);while (pos != string::npos){s1.replace(pos, 1, "20%");// 效率很低,能不用就不要用了pos = s1.find(' ', pos + 3);}cout << s1 << endl;string s2("hello  world hello");cout << s2 << endl;string s3;for (auto ch : s2){if (ch == ' '){s3 += "20%";}else{s3 += ch;}}cout << s3 << endl;s2.swap(s3);cout << s2 << endl;return 0;
}

运行结果:

 💫string类的substr

返回string字符串的一个任意子串

	//返回string字符串的一个任意子串string string::substr(size_t pos, size_t len){//断言assert(pos < _size);//size_t end = pos + len;if (len == npos || pos + len >= _size){end = _size;}string str;str.reserve(end - pos);for (size_t i = pos; i < end; i++){str += _str[i];}return str;}

咱举个栗子叭:

int main()
{string s1("Test.cpp");string s2("Test.tar.zip");size_t pos1 = s1.rfind('.');if (pos1 != string::npos){//string suff = s1.substr(pos1, s1.size() - pos1);string suff = s1.substr(pos1);cout << suff << endl;}size_t pos2 = s2.rfind('.');if (pos2 != string::npos){string suff = s2.substr(pos2);cout << suff << endl;}string str("https://legacy.cplusplus.com/reference/string/string/substr/");string sub1, sub2, sub3;pos1 = str.find(':');sub1 = str.substr(0, pos1 - 0);cout << sub1 << endl;pos2 = str.find('/', pos1+3);sub2 = str.substr(pos1 + 3, pos2 - (pos1 + 3));cout << sub2 << endl;sub3 = str.substr(pos2 + 1);cout << sub3 << endl;return 0;
}

运行结果:

💫string类的clear

	//删除void string::clear(){_size = 0;_str[0] = '\0';}

💫string类的流

这里需要讲解两种流

输入流:

	//输入流istream& operator>>(istream& in, string& s){//删除s.clear();char buff[128];char ch = in.get();int i = 0;while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 127){buff[i] = '\0';s += buff;i = 0;}ch = in.get();}if (i > 0){buff[i] = '\0';s += buff;}return in;}

输出流:

	//输出流ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}

 🌟结束语

       今天内容就到这里啦,时间过得很快,大家沉下心来好好学习,会有一定的收获的,大家多多坚持,嘻嘻,成功路上注定孤独,因为坚持的人不多。那请大家举起自己的小说手给博主一键三连,有你们的支持是我最大的动力💞💞💞,回见。

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

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

相关文章

强化学习(一)——基本概念及DQN

1 基本概念 智能体 agent &#xff0c;做动作的主体&#xff0c;&#xff08;大模型中的AI agent&#xff09; 环境 environment&#xff1a;与智能体交互的对象 状态 state &#xff1b;当前所处状态&#xff0c;如围棋棋局 动作 action&#xff1a;执行的动作&#xff0c;…

无脑018——win11部署whisper,语音转文字

1.conda创建环境 conda create -n whisper python3.9 conda activate whisper安装pytorch pip install torch1.8.1cu101 torchvision0.9.1cu101 torchaudio0.8.1 -f https://download.pytorch.org/whl/torch_stable.html安装whisper pip install -U openai-whisper2.准备模型…

代码随想录算法训练营第三十四天|62.不同路径,63. 不同路径 II

62. 不同路径 - 力扣&#xff08;LeetCode&#xff09; 一个机器人位于一个 m x n 网格的左上角 &#xff08;起始点在下图中标记为 “Start” &#xff09;。 机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角&#xff08;在下图中标记为 “Finish” &#…

分享几个可以免费使用GPT工具

1. 国产可以使用GPT3.5和4.0的网站&#xff0c;每日有免费的使用额度&#xff0c;响应速度&#xff0c;注册时不用使用手机号&#xff0c;等个人信息&#xff0c;注重用户隐私&#xff0c;好评&#xff01; 一个好用的ChatGPT系统 &#xff0c;可以免费使用3.5 和 4.0https://…

如何用Java实现扑克牌(附源码)

目录 一.扑克牌的数据结构 二.买牌(扑克牌的初始化) 三.洗牌 四.发牌 五.完整代码 Card.java CardList.java 六.测试 输出结果 一.扑克牌的数据结构 首先&#xff0c;扑克牌是一幅一幅的&#xff0c;除去大小王以外一共有52张&#xff0c;我们可以考虑用数组来存储…

Java高级技术-反射

认识反射、获取类 获取类的方法 获取类的构造器 获取类的构造器、并对其进行操作 获取构造器的作用&#xff1a;依然是初始化对象返回 获取成员变量 获取成员变量的方法 获取成员变量的作用&#xff1a;赋值、取值 获取类的成员方法 方法 作用&#xff1a;依然是执行 作用、…

Docker容器间网络共享

Docker容器间网络共享 1、新建网络2、容器绑定网卡3、验证 Docker环境中为了一套应用部署多个环境、并且不修改配置文件的情况下&#xff0c;做到一键部署。要求不同容器直接的网络交互&#xff0c;使用容器名称。 网络相关常用命令 #查看网络内部信息docker network inspect b…

Vim多行编辑

Vim多行编辑 Ctrlq进入多行编辑模式&#xff0c;然后上下选择要编辑的行 按下I或者Shifti&#xff0c;进入编辑模式 编辑的时候多行不会同时变化&#xff0c;不要担心&#xff0c;确实是多行编辑 编辑完成&#xff0c;想要结束多行编辑&#xff0c;按下Esc&#xff0c;此时…

前端小记--2.element-ui中级联选择器cascader如何默认展开下拉框

最近做项目时&#xff0c;遇到一个需求&#xff1a;在一个排班表中&#xff0c;展示人员的值班情况&#xff0c;点击单元格&#xff0c;弹出下拉框&#xff0c;修改人员排班信息。 由于下拉框选择内容是树状结构&#xff0c;这里使用了element-ui中级联组件cascader&#xff0c…

C-语言每日刷题

目录 [蓝桥杯 2015 省 A] 饮料换购 题目描述 输入格式 输出格式 输入输出样例 # [蓝桥杯 2023 省 A] 平方差 题目描述 输入格式 输出格式 输入输出样例 说明/提示 【样例说明】 [NOIP2001 普及组] 数的计算 题目描述 输入格式 输出格式 输入输出样例 说明/提示 样例 1 解释 数据…

TCP 重传、滑动窗口、流量控制、拥塞控制

1&#xff1a;重传机制 超时重传 快速重传 SACK 方法 Duplicate SACK 1&#xff1a;重传机制 超时重传&#xff1a;重传机制的其中一个方式&#xff0c;就是在发送数据时&#xff0c;设定一个定时器&#xff0c;当超过指定的时间后&#xff0c;没有收到对方的ACK确认应答报文…

matlab三维地形图

matlab三维地形图 %%%%—————Code to draw 3D bathymetry—————————— %-------Created by bobo,10/10/2021-------------------- clear;clc;close all; ncdisp E:\data\etopo\scs_etopo.nc filenmE:\data\etopo\scs_etopo.nc; londouble(ncread(filenm,lon)); lat…

分析实现HarmonyOS中的Linux内核架构模式

在当今的科技领域&#xff0c;操作系统是各种智能设备运行的关键所在。而在这方面&#xff0c;华为的鸿蒙系统备受瞩目。那么&#xff0c;鸿蒙系统技术架构是怎样的呢&#xff1f;本文将为您揭开这一神秘面纱。 首先&#xff0c;我们需要了解鸿蒙系统的基本架构。鸿蒙系统采用…

聊聊测试for Jeffky

什么是测试 测试是一个系统性的过程&#xff0c;它涉及到在已开发的软件中执行程序、应用工具和技术来评估其质量、功能和性能。这个过程的目的是发现并纠正程序中的错误&#xff0c;提高软件的可靠性和稳定性&#xff0c;以满足用户的需求。 测试的分类 什么是自动化测试 自动…

国产AI边缘计算盒子,双核心A55丨2.5Tops算力

边缘计算盒子 双核心A55丨2.5Tops算力 ● 2.5TopsINT8算力&#xff0c;支持INT8/INT4/FP16多精度混合量化。 ● 4路以上1080p30fps视频编解码&#xff0c;IVE模块独立提供图像基础算子加速。 ● 支持Caffe、ONNX/PyTorch深度学习框架&#xff0c;提供resnet50、yolov5等AI算…

Raft 算法

Raft 算法 1 背景 当今的数据中心和应用程序在高度动态的环境中运行&#xff0c;为了应对高度动态的环境&#xff0c;它们通过额外的服务器进行横向扩展&#xff0c;并且根据需求进行扩展和收缩。同时&#xff0c;服务器和网络故障也很常见。 因此&#xff0c;系统必须在正常…

C++的类和对象(一)

目录 1、面向过程和面向对象初认识 2、为什么要有类 3、类的定义 类的两种定义方式 4、类的访问限定符 5、类的作用域 5.1 为什么要有作用域&#xff1f; 5.2类作用域 6、类的实例化 6.1类的实例化的定义 6.2类的实例化的实现 6.3经典面试题 7、类对象 7.1类对…

【论文解读】NuScenes-QA:自动驾驶场景的多模态视觉问答基准

来源&#xff1a;投稿 作者&#xff1a;橡皮 编辑&#xff1a;学姐 论文链接&#xff1a;https://arxiv.org/pdf/2305.14836.pdf 开源代码&#xff1a;https://github.com/qiantianwen/NuScenes-QA 摘要&#xff1a; 我们在自动驾驶背景下引入了一种新颖的视觉问答&#xf…

Course2-Week1-神经网络

Course2-Week1-神经网络 文章目录 Course2-Week1-神经网络1. 神经网络概述1.1 欢迎来到Course21.2 神经元和大脑1.3 引入神经网络-需求预测1.4 神经网络的其他示例-图像感知 2. 神经网络的数学表达式2.1 单层的神经网络-需求预测2.3 前向传播的神经网络-手写数字识别 3. Tensor…

揭秘原型链:探索 JavaScript 面向对象编程的核心(上)

&#x1f90d; 前端开发工程师&#xff08;主业&#xff09;、技术博主&#xff08;副业&#xff09;、已过CET6 &#x1f368; 阿珊和她的猫_CSDN个人主页 &#x1f560; 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 &#x1f35a; 蓝桥云课签约作者、已在蓝桥云…