C++:String的模拟实现

      

     模拟实现的节奏比较快,大家可以先去看看博主的关于string的使用,然后再来看这里的模拟实现过程

C++:String类的使用-CSDN博客

      String模拟实现大致框架迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义)  并且为了不和库的string冲突,我们需要自己搞一个命名空间

namespace cyx
{class string{public://迭代器的实现(可读可写)typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//迭代器的实现(可读不可写)typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;//static const size_t npos=-1  vs下int类型支持给static和const同时修饰的变量用缺省值};const size_t string::npos = -1;//静态成员遍历类外初始化
}

      nops是一个静态变量,要类内定义类外初始化,由于nops是size_t类型,赋值-1会被强转成最大的无符号整数

一、构造+析构+赋值重载(Member functions)

1.1 全缺省构造函数

//构造函数
string(const char* str = ""):_size(strlen(str)){_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];//   多开一块\0的空间strcpy(_str, str);}

1、 “ ”空字符串其实里面默认就有\0,所以缺省值直接给空字符串就行

2、_capacity 一定要给初始空间,不然后面如果涉及到2倍扩容,为0的话就扩不了了

3、要多开一块空间,连这个\0 

4、可以复用strcpy函数

1.2 拷贝构造函数的传统写法

     传统的思路就是拷贝,也就是我们先根据被拷贝的对象的_capacity开空间,然后再进行拷贝

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

1.3 拷贝构造函数的现代写法和swap函数

       现代的思路就是,尝试去复用,比如说我们可不可以直接去利用前面的构造函数去构造一个新对象,然后再窃取新对象的成果(利用swap)

//交换字符串
void swap(string& s)
{std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向std::swap(_size, s._size);std::swap(_capacity, s._capacity);
}
	string(const string& s):_str(nullptr){string temp(s._str);swap(temp);}

 传统写法和现代写法参数一样,不能重载,只能保留一个 

1. 4 迭代器区间构造

		//迭代器区间构造template <class InputIterator>string(InputIterator first, InputIterator last):_str(new char[last-first+1]),_size(0),_capacity(last-first){while (first != last){push_back (*first);++first;}}

       这里定义的模版InputIterator的意思其实是这边我们可以传不同类型对象的迭代器,我们并不知道这个迭代器里面有多少元素,所以得用指针-指针,即last-first来确定我们的容量,然后再开空间,一个个进行尾插。

1.5 赋值重载的传统写法

       传统的思路就是,先开一块新空间拷贝旧数据,然后再释放掉原空间,这里尽量是先开空间再释放,避免我们开空间失败导致原始数据的丢失。   

		//赋值重载(传统写法)string& operator=(const string& s){if (this != &s)//避免自赋值{//先开新空间再毁旧空间,避免新空间开失败导致数据丢失char* temp = new char[s._capacity + 1];strcpy(temp, s._str);delete[]_str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;}

注意:要注意自赋值情况!!否则刚拷贝完自己就被释放了

1.6 赋值重载的现代写法

      现代的思路就是,既然被赋值这个空间不想要,那就和形参直接交换吧!!但是要注意的是,这里就不能像传统的一样用const引用了,否则不想要的空间就给到我们的赋值对象了,这边就得用传值传参,这样被交换的就只是一个临时拷贝,不想要的空间随着栈帧的结束被销毁。

//赋值重载(现代写法)
string& operator=(string s)//必须用值传递,否则会导致原数据的丢失
{swap(s);return *this;
}

  传统写法和现代写法参数不一样,一个是const引用,一个传值传参,所以可以同时存在。

1.7 析构函数

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

 二、容量相关的接口(Capacity)

2.1 size、capacity

	//获取当前sizesize_t size() const{return _size;}//获取当前capacitysize_t capacity() const{return _capacity;}

2.2 reserve

如果n比_capacity大,思路就是先开新空间进行拷贝,然后再释放旧空间

	//改变capacityvoid reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}

2.3 resize

如果n比_size大,我们根据n去扩容,然后因为string的底层是字符数组,所以memset就很适合,他就是可以去一个字节一个字节设置成我们想要的。缺省值给‘\0’

//改变size
void resize(size_t n, char ch = '\0')
{if (n > _size){reserve(n);memset(_str + _size, ch, n - _size);}_size = n;_str[_size] = '\0';
}

2.4 clear和empty

//清理字符串
void clear()
{_str[0] = '\0';_size = 0;
}
//判断字符串是否为空
bool empty()
{return _capacity == 0;
}

2.5 shrink_to_fit(用得少)

缩容到size位置,平时用的很少,我们要尽量减少扩容,思路也是一样的,开辟新空间去拷贝,再释放旧空间

	void shrink_to_fit(){char* temp = new char[_size + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = _size;}

三、[ ]和比较运算符重载

//[]重载(可读可写)
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 || *this == s;
}
//<
bool operator<(const string& s) const
{return !(*this >= s);
}
//<=
bool operator<=(const string& s) const
{return !(*this > s);
}
//!=
bool operator!=(const string& s) const
{return !(*this == s);
}

有了[ ]、迭代器,我们可以展示3种遍历方法:下标访问、迭代器区间访问、范围for访问

	void Print(const string& s){//下标遍历for (size_t i = 0; i < s.size(); ++i)cout << s[i] << " ";cout << endl;//迭代器遍历访问string::const_iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;//范围forfor (auto &e : s)cout << e << " ";cout << endl;}

四、增删接口(Modifiers)

       字符串的增删接口一般要设置两个版本,一个是操作字符,一个是操作字符串,我们先把最难的insert和erase搞了,其他的就可以复用了

4.1 insert

	//指定位置插入一个字符string& insert(size_t pos, char ch){assert(pos <= _size);//判断是否需要扩容if (_size + 1 > _capacity)reserve(2 * _capacity);//pos后的数据要往后挪,所以要从后往前移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);//判断是否需要扩容size_t 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;}

4.2 erase

//删除指定位置之后的字符
string& erase(const size_t pos, size_t len = npos)
{assert(pos <= _size);//有两种情况,一种是全删完,一种是删中间的一部分//全删完if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出{_str[pos] = '\0';_size = pos;}//删一部分else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;
}

 len == npos这个判断条件必须写,因为nops已经是无符号最大值了,再+会溢出

4.3 push_back

	//尾插一个字符void push_back(char ch){insert(_size, ch);}

4.4 append

//尾插一个字符串
string& append(const char* str)
{return insert(_size, str);
}

4.5 +=重载(用的多)

	//+=重载 字符string& operator+=(char ch){push_back(ch);return *this;}//+=重载 字符串string& operator+=(const char* str){append(str);return *this;}

五、字符串操作(String operations

5.1 获取c类型字符串

//获取c类型字符串
const char* c_str() const
{return _str;
}

5.2 寻找指定字符串并返回下标

	//寻找指定字符串并返回下标size_t find(const char* str, size_t pos = 0)const{assert(pos < _size);char* p = strstr(_str + pos, str);if (p == nullptr)return npos;return p - _str;}

六、重载流插入和流提取 

我们不能写在类内,否则会*this会占用第一个操作数,不符合我们的使用习惯。

6.1 流提取

//重载<<
std::ostream& operator<< (std::ostream& out, const string& s)
{//可以用范围for,也可以用迭代器   范围for是用宏写的,本质上也是迭代器!for (auto ch : s)out << ch;return out;
}

6.2 流插入

首先我们要知道两点,1.>>只会读取到空格或者换行结束 2.读取前会清理掉原空间的数据

	//重载>>std::istream& operator>> (std::istream& in, string& s){//读取前要先清理掉原来存在的字符s.clear();//用get获取字符char ch = in.get();//先用一个数组存起来,再一起加char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){//原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加//s += ch;buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;//重置i}ch = in.get();}//循环结束后可能还要一些字母没有存进去if (i != 0){buff[i] = '\0';s += buff;}return in;}

我们用一个buff数组来暂时存储需要插入的字符,等存完了再+=,这样可以提高效率,以空间换时间 

七、string模拟实现全部代码

namespace cyx
{using std::endl;using std::cout;class string{public://迭代器的实现(可读可写)typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}//迭代器的实现(可读不可写)typedef const char* const_iterator;const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}//交换字符串void swap(string& s){std::swap(_str, s._str);//浅拷贝,没有开空间,只是改变指针指向std::swap(_size, s._size);std::swap(_capacity, s._capacity);}//构造函数string(const char* str = ""):_size(strlen(str)){_capacity = _size == 0 ? 3 : _size;_str = new char[_capacity + 1];//   多开一块\0的空间strcpy(_str, str);}//拷贝构造函数(传统写法)/*string(const string& s):_size(s._size), _capacity(s._capacity){_str = new char[_capacity + 1];strcpy(_str, s._str);}*///拷贝函数的现代写法string(const string& s):_str(nullptr){string temp(s._str);swap(temp);}//迭代器区间构造template <class InputIterator>string(InputIterator first, InputIterator last):_str(new char[last-first+1]),_size(0),_capacity(last-first){while (first != last){push_back (*first);++first;}}//赋值重载(传统写法)string& operator=(const string& s){if (this != &s)//避免自赋值{//先开新空间再毁旧空间,避免新空间开失败导致数据丢失char* temp = new char[s._capacity + 1];strcpy(temp, s._str);delete[]_str;_str = temp;_size = s._size;_capacity = s._capacity;}return *this;}//赋值重载(现代写法)string& operator=(string s)//必须用值传递,否则会导致原数据的丢失{swap(s);return *this;}//析构函数~string(){delete[]_str;_str = nullptr;_size = _capacity = 0;}//获取c类型字符串const char* c_str() const{return _str;}//获取当前sizesize_t size() const{return _size;}//获取当前capacitysize_t capacity() const{return _capacity;}//[]重载(可读可写)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 || *this == s;}//<bool operator<(const string& s) const{return !(*this >= s);}//<=bool operator<=(const string& s) const{return !(*this > s);}//!=bool operator!=(const string& s) const{return !(*this == s);}//改变sizevoid resize(size_t n, char ch = '\0'){if (n > _size){reserve(n);memset(_str + _size, ch, n - _size);}_size = n;_str[_size] = '\0';}//改变capacityvoid reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}//尾插一个字符void push_back(char ch){insert(_size, ch);}//尾插一个字符串string& append(const char* str){return insert(_size, str);}//+=重载 字符string& operator+=(char ch){push_back(ch);return *this;}//+=重载 字符串string& operator+=(const char* str){append(str);return *this;}//指定位置插入一个字符string& insert(size_t pos, char ch){assert(pos <= _size);//判断是否需要扩容if (_size + 1 > _capacity)reserve(2 * _capacity);//pos后的数据要往后挪,所以要从后往前移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);//判断是否需要扩容size_t 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(const size_t pos, size_t len = npos){assert(pos <= _size);//有两种情况,一种是全删完,一种是删中间的一部分//全删完if (len == npos || pos + len > _size)//len == npos必须写,因为nops是无符号最大值,+的话会溢出{_str[pos] = '\0';_size = pos;}//删一部分else{strcpy(_str + pos, _str + pos + len);_size -= len;}return *this;}//寻找指定字符并返回下标size_t find(char ch, size_t pos = 0)const{assert(pos < _size);for (size_t i = pos; i < _size; ++i)if (_str[i] == ch)return i;return npos;}//寻找指定字符串并返回下标size_t find(const char* str, size_t pos = 0)const{assert(pos < _size);char* p = strstr(_str + pos, str);if (p == nullptr)return npos;return p - _str;}//清理字符串void clear(){_str[0] = '\0';_size = 0;}//缩容到他的sizevoid shrink_to_fit(){char* temp = new char[_size + 1];strcpy(temp, _str);delete[] _str;_str = temp;_capacity = _size;}//判断字符串是否为空bool empty(){return _capacity == 0;}private:char* _str;size_t _size;size_t _capacity;static const size_t npos;//static const size_t npos=-1       vs下int类型支持给static和const同时修饰的变量用缺省值};const size_t string::npos = -1;//静态成员遍历类外初始化//重载<<std::ostream& operator<< (std::ostream& out, const string& s){//可以用范围for,也可以用迭代器   范围for是用宏写的,本质上也是迭代器!for (auto ch : s)out << ch;return out;}//重载>>std::istream& operator>> (std::istream& in, string& s){//读取前要先清理掉原来存在的字符s.clear();//用get获取字符char ch = in.get();//先用一个数组存起来,再一起加char buff[128];size_t i = 0;while (ch != ' ' && ch != '\n'){//原始方法,一个字符一个字符加太麻烦,先用一个数组存起来,再一起加//s += ch;buff[i++] = ch;if (i == 127){buff[127] = '\0';s += buff;i = 0;//重置i}ch = in.get();}//循环结束后可能还要一些字母没有存进去if (i != 0){buff[i] = '\0';s += buff;}return in;}//遍历方法的展示void Print(const string& s){//下标遍历for (size_t i = 0; i < s.size(); ++i)cout << s[i] << " ";cout << endl;//迭代器遍历访问string::const_iterator it = s.begin();while (it != s.end()){cout << *it << " ";++it;}cout << endl;//范围forfor (auto &e : s)cout << e << " ";cout << endl;}

有新的后面再补充哦! 

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

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

相关文章

Day16:信息打点-语言框架开发组件FastJsonShiroLog4jSpringBoot等

目录 前置知识 指纹识别-本地工具-GotoScan&#xff08;CMSEEK&#xff09; Python-开发框架-Django&Flask PHP-开发框架-ThinkPHP&Laravel&Yii Java-框架组件-Fastjson&Shiro&Solr&Spring 思维导图 章节知识点 Web&#xff1a;语言/CMS/中间件/…

基于springboot+vue的医疗挂号管理系统

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

002-CSS-三角形

三角形 普通三角形带阴影的三角形&#xff1a;box-shadow带阴影的三角形&#xff1a;filter 普通三角形 &#x1f4a1; Tips&#xff1a;设置 div 盒子宽高为零&#xff0c;使用单边框实现 .triangle {width: 0;height: 0;border: 20px solid transparent;border-top-color: …

GitLab EE 企业版破解

在当今数字化时代&#xff0c;软件开发与团队协作已经成为现代企业不可或缺的一部分。而在这个过程中&#xff0c;版本控制、协作和持续集成等工具的运用变得至关重要。GitLab作为一个领先的、完整的DevOps平台&#xff0c;为团队提供了一个集成的解决方案&#xff0c;使得软件…

Thingsboard本地源码部署教程

本章将介绍ThingsBoard的本地环境搭建&#xff0c;以及源码的编译安装。本机环境&#xff1a;jdk11、maven 3.6.2、node v12.18.2、idea 2023.1、redis 6.2 环境安装 开发环境要求&#xff1a; Jdk 11 版本 &#xff1b;Postgresql 9 以上&#xff1b;Maven 3.6 以上&#xf…

前端+php:实现提示框(自动消失)

效果 php部分&#xff1a;只展示插入过程 <?php//插入注册表中$sql_insert "INSERT INTO regist_user(userid,password,phone,email)VALUES (" . $_POST[UserID] . "," . CryptPass($_POST[Password]) . "," . $_POST[Phone] . ",&qu…

论文阅读:2022Decoupled Knowledge Distillation解耦知识蒸馏

SOTA的蒸馏方法往往是基于feature蒸馏的&#xff0c;而基于logit蒸馏的研究被忽视了。为了找到一个新的切入点去分析并提高logit蒸馏&#xff0c;我们将传统的KD分成了两个部分&#xff1a;TCKD和NCKD。实验表明&#xff1a;TCKD在传递和样本难度有关的知识&#xff0c;同时NCK…

javascript作用域编译浅析

作用域思维导图 1&#xff1a;编译原理 分词/词法分析 如果词法单元生成器在判断a是一个独立的词法单元还是其他词法单元的一部分时&#xff0c;调用的是有状态的解析规则&#xff0c;那么这个过程就被称为词法分析。 解析/语法分析 由词法单元流转换成一个由元素逐级嵌套所组…

CVE-2024-23334 AIOHTTP 目录遍历漏洞复现

aiohttp简介 aiohttp 是一个基于 asyncio 实现的 Python HTTP 客户端和服务器框架。它提供了异步的 HTTP 客户端和服务器功能&#xff0c;能够处理高并发的网络请求。以下是关于 aiohttp 的一些重要特点和用途&#xff1a; 基于 asyncio&#xff1a;aiohttp 是基于 Python 的 a…

no declaration can be found for element ‘rabbit:connection-factory‘

spring-mvc 配置 rabbitmq 出现问题。 我的解决方案如下&#xff1a; 1 找到配置文件 spring-rabbitmq.xml 我的配置文件叫&#xff1a;spring-rabbitmq.xml&#xff0c;你们按照自己的查找。 2 定位如下URI 接着 Ctrl鼠标左键 3 确定spring-rabbit-x.x.xsd 按照步骤2 &…

Leetcode刷题笔记题解(C++):232. 用栈实现队列

思路&#xff1a;双栈实现入队列和出队列的操作 //AB栈来实现队列 //A栈用来push 可以利用A栈获取到队列的back即A.top //B栈用来pop 如果要获取队列的top&#xff0c;可以先把A栈元素依次弹出依次压入B栈中&#xff0c;然后B.top就是队列的top&#xff0c;pop也类似 cla…

记录些大语言模型(LLM)相关的知识点

槽位对齐&#xff08;slot alignment&#xff09; 在text2sql任务中&#xff0c;槽位对齐&#xff08;slot alignment&#xff09;通常指的是将自然语言问题中的关键信息&#xff08;槽位&#xff09;与数据库中的列名或API调用中的参数进行匹配的过程。这个过程中&#xff0c…