C++ 模拟实现string

目录

一.类的声明

二.确定成员变量

三.成员函数

1.带参的构造函数,析构函数,拷贝构造

2.size()与capacity()

3.运算符重载

重载数组下标访问[]

重载+=  

重载比较运算符(<, <= ,   > , >= , ==  , !=  , )

重载流插入与流提取

重载=

​编辑

4.实现迭代器

使用迭代器遍历

使用范围for遍历

5.实现reserve(size_t n)

6.实现push_back(char ch)

7.实现append(const char* str)

8.实现insert()

9.实现erase()

10.实现clear()

11.实现resize()

12实现find()

13.实现substr


一.类的声明

为了避免与库函数里的string类重复定义,这里我们用命名空间,将我们自己定义的string类封到命名空间里,代码为:

namespace wzy
{class string{public://成员变量private://成员函数};
}

二.确定成员变量

我们知道库里面的string类底层大致是使用的是一个字符指针,指向一个数组,然后将对数组的各种操作封装到类中,然后是两个size_t类型的变量记录字符的空间,与有效字符的个数。这里我们的成员变量可以参考:

// 成员变量char*  _str      //指向存储字符的数组(数组空间大小包含'\0')
size_t _size     //记录数组中有效字符的个数(不包含'\0')
size_t _capacity //记录数组空间的大小(不包含'\0')

三.成员函数

1.带参的构造函数,析构函数,拷贝构造

		string(const char* str=""):_str(new char[strlen(str)+1]),_size(strlen(str)),_capacity(_size+1){}

注:在使用初始化列表的时候,要注意类中成员变量的顺序!!!

原因:初始化列表是按照变量声明的顺序初始化的,建议初始化列表的顺序与成员变量声明的顺序一致.

C++规定常量字符串结尾自动加上'\0',因此这里缺省值相当于"\0"

析构函数:

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

拷贝构造

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

2.size()与capacity()

		size_t size()   const{return _size;}size_t capacity()  const{return _capacity;}

3.运算符重载

重载数组下标访问[]

可读可写

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

可读不可写(给被const修饰的string对象使用)

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

使用数组下标的方式遍历string对象

void test1()
{string s("Hello World!");for(size_t i=0;i<s.size();i++){cout<<s[i]<<"  ";}
}

重载+=  

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

重载比较运算符(<, <= ,   > , >= , ==  , !=  , )

		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);}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);}

重载流插入与流提取

	ostream& operator<<(ostream& out, const string& s)  {for (auto ch : s)  //要先实现const迭代器{out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear(); //用之前先清空schar ch;ch=in.get();  //为什么不用in>>ch?//流提插入时,不能遇到空格或换行就停止while (ch != ' ' && ch != '\n'){s += ch;ch=in.get();}return in;}

这里我们可以发现,在流提取时,当s满时,再插入,每插入一次就要扩容一次。这样效率不好。可以改为:

 istream& operator>>(istream& in, string& s){s.clear(); 		char ch;char buff[128];ch=in.get();  size_t i=0;while (ch != ' ' && ch != '\n'){buff[i++]=ch;if(i==129){buff[i]='\0';s+=buff;i=0;}ch=in.get();}if(i){s+=buff;}return in;}

这里我们用buff暂存字符,当buff满时,就尾插。结束当buff还有字符时,再尾插。这样可以减少开辟空间的次数。

重载=

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

这里我们可以看到里面的交换数据次过多,那我们能不能将其封装到一个函数里呢?

这里首先要介绍一个库函数std::swap(),功能:交换两个变量的值。(变量只能是内置类型!!!)

		void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string& operator=(string temp){swap(temp);return *this;}

这里参数temp不能用引用,被const修饰。

4.实现迭代器

这里我们一定要知道:迭代器很多行为与指针类似。当很多情况下,我们完全可以把迭代器当创指针使用。。因为迭代器也代表指向某个元素;迭代器指向下一个元素与指针的方式一样;迭代器也可以解引用使用元素。

这里我们实现迭代器的方式:使用typedef 将char* 重命名为iterator。因此在这里我们完全可以将迭代器当作指针使用。

正向迭代器

		typedef char* iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}

const迭代器

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

使用迭代器遍历

void test2()
{string s("Hello World!");string::iterator it = s.begin();while (it != s.end()){cout << *it << "  ";}cout << endl;
}

使用范围for遍历

我们知道范围for的底层就是使用迭代器实现的,因此当我们实现迭代器之后就可以使用范围for了。

void test3()
{string s("Hello World!");for(auto ch:s){cout<<ch<<" ";}cout << endl;
}

5.实现reserve(size_t n)

按照库的方式分为两种情况:

1.  n>_capacity 。 扩容(申请新空间,拷贝旧空间,释放旧空间,_str指向新空间,更新_size与_capacity的值)

2. n<=_capacity  。不做处理

注:这里_str指向新空间我们可以使用:std::swap 作用:交换两个内置类型的值.

只改变 _capacity,不改变 _size

	void reserve(size_t n){if (n > _capacity){char* temp = new char[n+1]; //需要为'\0'开辟空间strcpy(temp, _str);std::swap(temp, _str);delete[]temp;_capacity = n;}}

6.实现push_back(char ch)

这里我们实现push_back按照库里面的方式。

这里我们可以分两种情况:

1._size<_capacity。扩容(使用reserve),然后尾插

2._size==_capacity。直接尾插

void push_back(char ch){if (_size == _capacity){reserve(_capacity==0?4:_capacity*2);//注意当_capacity==0时}_str[_size] = ch;_size++;_str[_size] = '\0';}

7.实现append(const char* str)

分两种情况:

1. _szie+strlen(str)>_capacity 。扩容(使用reserve),然后再将str的内容拷贝到_str里

2._szie+sizeof(str)<=_capacity。 直接拷贝

		void append(const char* str){size_t len = strlen(str);if (_size + len >= _capacity)    //第一种情况{reserve(_size + len);//这里的扩容具体还得看要求}strncpy(_str+_size,str,len);//strncpy可以把'\0'也拷贝过来_size += len;}

8.实现insert()

string底层用的是数组,这里在将一个字符或字符串插入某个位置,可以看作像数组里插入。

		string& insert(size_t pos, char ch){assert(pos <= _size);  //检查插入位置是否正确if (_size == _capacity)//检测是否扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}//这里要注意size_t是无符号的整形,要注意头插时,i的初始化值//这里即使i为整形,i也会被整形提升为size_t类型for (size_t i = _size+1; i > pos; i--) {_str[i] = _str[i - 1];}_str[pos] = ch;_size++;}string& insert(size_t pos, const char* str){assert(pos <= _capacity);int len = strlen(str);if (pos + len > _capacity)   //检查是否扩容{reserve(pos + len);}for (int i = _size; i >=(int)pos; i--){_str[i+len] = _str[i];}strncpy(_str + pos, str, len);_size += len;return *this;}

9.实现erase()

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

这里npos时一个无符号的整形,是size_t类型最大的整数,比特为全为1,所以直接赋值为1.

因为npos属于string类的成员变量,它的值不变,因此我们可以用const与static修饰它。我们知道被static修饰的变量要在类外面定义,但是C++11中,const修饰的static的成员变量(只能是整形),可以直接在类里面给缺省值

10.实现clear()

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

11.实现resize()

以库里面的resize为例,分三种情况:
1.  n>_capacity  ,扩容,初始化多出来的空间。

2.  _size<n<_capcity  ,将大于_size,小于n的空间初始化。

3. n<_size , 缩容

		void resize(size_t n, char ch = '\0'){if (n <= _capacity){_str[n] = '\0';_size = n;}else{reserve(n);int i;for (i = _size; i < n; i++){_str[i] = ch;}_str[i] = '\0';}}

12实现find()

在字符串中查找子串在理论上效率很高,但是在实际上效率并不好,所以我们一般用可以直接暴力查找。这里我们直接用了一个strstr()函数查找。

找到就返回子串的首地址,没找到就返回空指针。

        size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* sub, size_t pos = 0){const char* p = strstr(_str, sub);if (p){return p-_str;}return npos;}

13.实现substr

分两种情况:

1. pos+len>=_size,

2.pos+len <_size

		string substr(size_t pos, size_t len = npos){assert(pos < _size);string s;size_t end = len + pos;if (len==npos||pos + len >= _size ){len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < end; i++){s += _str[i];}return s;}

注:使用这个函数时,要先实现拷贝构造,否则在传返回值的时候是浅拷贝,会报错。

完整代码:

因为string类的方法太多,这里我只选了几个我们经常使用的实现,大家可以参考我这个头文件,继续实现下去:

#pragma once
#include<iostream>#include<assert.h>
using namespace std;namespace wzy
{class string{public:string(const char* str = ""):_str(new char[strlen(str) + 1]), _size(strlen(str)), _capacity(_size + 1){}string(const string& s){_str = new char[s._size + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}size_t size(){return _size;}size_t capacity(){return _capacity;}char& operator[](int i){assert(i < _size);return _str[i];}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;}void reserve(size_t n){if (n > _capacity){char* temp = new char[n + 1];strcpy(temp, _str);std::swap(temp, _str);delete[]temp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){reserve(_capacity * 2);}_str[_size] = ch;_size++;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len >= _capacity){reserve(_size + len);//这里的扩容具体还得看要求}strncpy(_str + _size, str, len);//strncpy可以把'\0'也拷贝过来_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(char* str){append(str);return *this;}void insert(size_t pos, char ch){assert(pos <= _size);  //检查插入位置是否正确if (_size == _capacity)//检测是否扩容{reserve(_capacity == 0 ? 4 : _capacity * 2);}for (size_t i = _size + 1; i > pos; i--){_str[i] = _str[i - 1];}_str[pos] = ch;_size++;}string& insert(size_t pos, const char* str){assert(pos <= _capacity);int len = strlen(str);if (pos + len > _capacity){reserve(pos + len);}for (int i = _size; i >= (int)pos; i--){_str[i + len] = _str[i];}strncpy(_str + pos, str, len);_size += len;return *this;}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);}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);}string& operator=(const string& s){if (this != &s){char* temp = new char[s._capacity + 1];strcmp(temp, s._str);delete[]_str;_str = temp;_size = s._size;_capacity = s._capacity;}}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}string& operator=(string s){if (this != &s){swap(s);return *this;}}void clear(){_str[0] = '\0';_size = 0;}void erase(size_t pos, size_t len = npos){assert(pos < _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;}else{size_t begin = pos + len;while (begin <= _size){_str[begin - len] = _str[begin];begin++;}_size -= len;}}void resize(size_t n, char ch = '\0'){if (n <= _capacity){_str[n] = '\0';_size = n;}else{reserve(n);int i;for (i = _size; i < n; i++){_str[i] = ch;}_str[i] = '\0';}}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* sub, size_t pos = 0){const char* p = strstr(_str, sub);if (p){return p-_str;}return npos;}string substr(size_t pos, size_t len = npos){assert(pos < _size);string s;size_t end = len + pos;if (len==npos||pos + len >= _size ){len = _size - pos;end = _size;}s.reserve(len);for (size_t i = pos; i < end; i++){s += _str[i];}return s;}private:char* _str;size_t _size;size_t _capacity;public:const static size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){for (auto ch : s){out << ch;}return out;}istream& operator>>(istream& in, string& s){s.clear();char ch;ch=in.get() ;while (ch != ' ' && ch != '\n'){s += ch;ch=in.get();}return in;}
}

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

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

相关文章

改变传媒格局的新趋势

在如今信息高速发展的时代&#xff0c;人们早已进入了一个以手机为中心的智能化时代。随着科技的迅猛发展&#xff0c;手机无人直播成为了一种新兴的传媒形态&#xff0c;正逐渐改变着传媒格局。本文将从手机无人直播的定义、发展背景和影响等方面进行探讨。 首先&#xff0c;…

数字孪生开发技术分析

数字孪生的开发涉及多个技术领域&#xff0c;包括计算机科学、数据科学、人工智能和工程等。以下是数字孪生开发中常用的一些关键技术&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢迎交流合作。 1.建模和仿真&am…

《论文阅读28》Unsupervised 3D Shape Completion through GAN Inversion

GAN&#xff0c;全称GenerativeAdversarialNetworks&#xff0c;中文叫生成式对抗网络。顾名思义GAN分为两个模块&#xff0c;生成网络以及判别网络&#xff0c;其中 生成网络负责根据随机向量产生图片、语音等内容&#xff0c;产生的内容是数据集中没有见过的&#xff0c;也可…

hive 用户自定义函数udf,udaf,udtf

udf&#xff1a;一对一的关系 udtf&#xff1a;一对多的关系 udaf&#xff1a;多对一的关系 使用Java实现步骤 自定义编写UDF函数注意&#xff1a; 1.需要继承org.apache.hadoop.hive.ql.exec.UDF 2.需要实现evaluete函数 编写UDTF函数注意&#xff1a; 1.需要继承org.apache…

Vue前端设计模式

文章目录 一、什么是设计模式&#xff1f;二、设计几个原则三、常见的设计模式及实际案例3.1、单例模式3.1.1、Element UI3.1.2、Vuex 3.2、工厂模式3.2.1、VNode3.2.2、vue-route 3.3、策略模式3.3.1、表格 formatter3.3.2、表单验证 3.4、代理模式3.4.1、拦截器3.4.2、前端框…

【分享】如何给Excel加密?码住这三种方法!

想要给Excel文件进行加密&#xff0c;方法有很多&#xff0c;今天分享三种Excel加密方法给大家。 打开密码 设置了打开密码的excel文件&#xff0c;打开文件就会提示输入密码才能打开excel文件&#xff0c;只有输入了正确的密码才能打开并且编辑文件&#xff0c;如果密码错误…

Elasticsearch常见面试题

文章目录 1.简单介绍下ES&#xff1f;2.简单介绍当前可以下载的ES稳定版本&#xff1f;3.安装ES前需要安装哪种软件&#xff1f;4.请介绍启动ES服务的步骤&#xff1f;5.ES中的倒排索引是什么&#xff1f;6. ES是如何实现master选举的&#xff1f;7. 如何解决ES集群的脑裂问题8…

高速视频采集卡设计方案:620-基于PCIe的高速视频采集卡

一、产品概述 基于PCIe的高速视频采集卡&#xff0c;通过PCIe3.0X8传输到存储计算服务器&#xff0c;实现信号的分析、存储。 北京太速科技 产品固化FPGA逻辑&#xff0c;适配视频连续采集&#xff0c;缓存容量2GB&#xff0c;开源的PCIe QT客户端软件&#xff0c…

(七)STM32 NVIC 中断、优先级管理及 AFIO 时钟的开启

目录 1. 中断相关知识简介 1.1 什么是中断 1.2 什么是内中断、外中断 1.3 什么是可屏蔽中断、不可屏蔽中断 2. CM3 内核中断介绍 2.1 F103系统异常清单 2.2 F103 外部中断清单 3. NVIC 简介 3.1 NVIC 寄存器简介 3.2 NVIC 相关寄存器的介绍 4. 中断优先级 4.1 优先…

2017年第六届数学建模国际赛小美赛B题电子邮件中的笔迹分析解题全过程文档及程序

2017年第六届数学建模国际赛小美赛 B题 电子邮件中的笔迹分析 原题再现&#xff1a; 笔迹分析是一种非常特殊的调查形式&#xff0c;用于将人们与书面证据联系起来。在法庭或刑事调查中&#xff0c;通常要求笔迹鉴定人确认笔迹样本是否来自特定的人。由于许多语言证据出现在电…

PyTorch深度学习实战(26)——卷积自编码器(Convolutional Autoencoder)

PyTorch深度学习实战&#xff08;26&#xff09;——卷积自编码器 0. 前言1. 卷积自编码器2. 使用 t-SNE 对相似图像进行分组小结系列链接 0. 前言 我们已经学习了自编码器 (AutoEncoder) 的原理&#xff0c;并使用 PyTorch 搭建了全连接自编码器&#xff0c;但我们使用的数据…

【PHP入门】2.2 流程控制

-流程控制- 流程控制&#xff1a;代码执行的方向 2.2.1控制分类 顺序结构&#xff1a;代码从上往下&#xff0c;顺序执行。&#xff08;代码执行的最基本结构&#xff09; 分支结构&#xff1a;给定一个条件&#xff0c;同时有多种可执行代码&#xff08;块&#xff09;&am…

阿里推荐 LongAdder ,不推荐 AtomicLong !

其他系列文章导航 Java基础合集数据结构与算法合集 设计模式合集 多线程合集 分布式合集 ES合集 文章目录 其他系列文章导航 文章目录 前言 一、CAS 1.1 CAS 全称 1.2 通俗理解CAS 1.3 CAS的问题 1.4 解决 ABA 问题 二、LongAdder 2.1 什么是 LongAdder 2.2 为什么推…

用JVS低代码实现业务流程的撤回和重新开始

在当今的数字化时代&#xff0c;业务流程的效率和准确性对于企业的运营至关重要。在实际业务场景中&#xff0c;我们可能需要处理一些复杂的流程&#xff0c;例如申请审批流程、合同签订流程等。这些流程在执行过程中可能会遇到各种情况&#xff0c;例如某个审批步骤需要重新审…

❀My虚拟机上的ftp服务器搭建(centos)❀

❀My虚拟机上的ftp服务器搭建(centos)❀ 在CentOS上搭建FTP服务器可以使用vsftpd软件&#xff0c;下面是详细的搭建教程&#xff1a; ①安装vsftpd软件 在终端中输入以下命令进行安装&#xff1a; sudo yum install vsftpd ②配置vsftpd 打开vsftpd的配置文件&#xff0c;…

【深度学习】序列生成模型(五):评价方法计算实例:计算BLEU-N得分【理论到程序】

文章目录 一、BLEU-N得分&#xff08;Bilingual Evaluation Understudy&#xff09;1. 定义2. 计算N1N2BLEU-N 得分 3. 程序 给定一个生成序列“The cat sat on the mat”和两个参考序列“The cat is on the mat”“The bird sat on the bush”分别计算BLEU-N和ROUGE-N得分(N1或…

WEB渗透—PHP反序列化(六)

Web渗透—PHP反序列化 课程学习分享&#xff08;课程非本人制作&#xff0c;仅提供学习分享&#xff09; 靶场下载地址&#xff1a;GitHub - mcc0624/php_ser_Class: php反序列化靶场课程&#xff0c;基于课程制作的靶场 课程地址&#xff1a;PHP反序列化漏洞学习_哔哩…

Ubuntu 22.04 禁用(彻底移除)Snap

什么是Snaps Snaps 是 Ubuntu 的母公司 Canonical 于 2016 年 4 月发布 Ubuntu 16.04 LTS&#xff08;Long Term Support&#xff0c;长期支持版&#xff09;时引入的一种容器化的软件包格式。自 Ubuntu 16.04 LTS 起&#xff0c;Ubuntu 操作系统可以同时支持 Snap 及 Debian …

3dsmax渲染太慢,用云渲染农场多少钱?

对于许多从事计算机图形设计的创作者来说&#xff0c;渲染速度慢是一个常见问题&#xff0c;尤其是对于那些追求极致出图效果的室内设计师和建筑可视化师&#xff0c;他们通常使用3ds Max这样的工具&#xff0c;而高质量的渲染经常意味着长时间的等待。场景复杂、细节丰富&…

APView500PV电能质量在线监测装置——安科瑞 顾烊宇

概述 APView500PV电能质量在线监测装置采用了高性能多核平台和嵌入式操作系统&#xff0c;遵照IEC61000-4-30《测试和测量技术-电能质量测量方法》中规定的各电能质量指标的测量方法进行测量&#xff0c;集谐波分析、波形采样、电压暂降/暂升/中断、闪变监测、电压不平衡度监测…