【C++学习手札】模拟实现string

                                                     🎬慕斯主页修仙—别有洞天

                                                       ♈️今日夜电波:缶ビール—みゆな

                                                                0:41━━━━━━️💟──────── 2:52
                                                                    🔄   ◀️   ⏸   ▶️    ☰ 

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

一、string实际的底层原理

二、string的模拟实现

        基本成员函数 

        构造函数 

        拷贝构造函数

        析构函数

        重载赋值运算符 

        迭代器

        迭代器的概念 

        begin()

        end()

        空间管理

        修改相关

        push_back()

        append ()​编辑

        重载+=运算符

        c_str ()

        clear()

        swap()

         重载[ ](最爱的运算符!!!)

         关系运算符的重载

         基本操作

        查找find() 

        前置知识substr()

        插入insert()

        删除 erase()

        流插入流提取

 总体代码


一、string实际的底层原理

        string类型底层实现通常使用动态数组,因为它允许在运行时动态分配内存空间。这使得字符串的长度可以根据需要进行调整,而不需要提前分配固定大小的空间,并且可以避免缓冲区溢出和内存泄漏等问题。 string类型还提供了各种字符串操作函数,例如拼接、查找、替换、大小写转换等。

    底层实际的储存结构:

class string
{private:char* _str;size_t _capacity;size_t _size;};

二、string的模拟实现

        基本成员函数 

        构造函数 

        通过设置缺省参数以及初始化列表来默认构造string对象

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

        在模拟实现string的构造函数时,很多人包括作者我一开始都会想着将他分为有参以及无参两部分,但是这种做法是会产生一定的问题的,如下为作者写出的错误的代码:

class string{public:string()//无参构造函数//初始化列表:_str(nullptr), _size(0), _capacity(0){}string(char* str)//带参构造函数->char a[] = "abc";string kk(a):_str(str),_size(strlen(str)),_capacity(strlen(str)){}string(const char* str)//带参构造函数->string kk("abc"):_str(str),_size(strlen(str)),_capacity(strlen(str)){}private:char* _str;size_t _capacity;size_t _size;};

        对于以上代码,若调用如上的带参构造函数中string(const char* str)就会报错,因为str传给_str属于权限放大,现在有两种解决方法,一种是将它改为 string(char* str),但是这样就不能直接传递字符串,需要先生成字符串,还有一种方法是将_str改为const char*类型,但是此时就不能修改_str指向的内容,后续的一些操作会出现一些错误

         拷贝构造函数

        由于我们使用了开辟了空间来用于储存数据,因此我们需要进行深拷贝,防止对于另外一个对象的影响。

        传统写法: 

        开辟对应的空间,然后再给相应的值。

        // 传统写法// s2(s1)//string(const string& s)//{//	_str = new char[s._capacity+1];//	strcpy(_str, s._str);//	_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(const string& s):_str(nullptr),_size(0),_capacity(0){string tmp(s._str);swap(tmp);}

        析构函数

        避免内存泄漏需要自己写一个析构函数用以销毁new在堆区所创建的空间 

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

        重载赋值运算符 

         同上面的拷贝构造函数一样,我们对此也是需要深拷贝的

        传统写法: 

        开辟对应的空间,然后再给相应的值。

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

        较为推荐的写法:

        通过让构造函数帮我们创建一个构造相应的字符的对象,然后我们再交换两个对象的数据即可得。

string& operator=(const string& s){if (this != &s){string tmp(s);//this->swap(tmp);swap(tmp);}return *this;}

        再简化一点:通过在传值时进行拷贝构造,然后交换数据。

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

         迭代器

        迭代器的概念 

         C++迭代器是一种用于遍历容器中元素的对象,它提供了一种统一的接口,使得不同类型的容器都可以使用相同的方式进行遍历。迭代器可以指向容器中的某个元素,并用于访问该元素的值或者进行修改。迭代器通常是一个类,它重载了指针操作符(*和->),以及自增(++)和自减(--)运算符,使得迭代器可以像指针一样进行移动。

        string的迭代器实际实现原理:

        string的迭代器实际上是一个指向string底层存储空间中char类型数据的指针,不同的迭代器类型标志着不同的访问权限。例如,一个string::iterator对象可以用来读写字符,而一个const string::iterator对象只能用来读取字符。

        于是我们定义出以下的迭代器:

        typedef char* iterator;typedef const char* const_iterator;

         begin()

        用于返回第一个字符的地址。

        iterator begin(){return _str; }const_iterator begin()const{return _str; }

      end()

        用于返回后一个字符的后一个字符的地址的地址。其实就是‘\0’的地址

        iterator begin(){return _str; }const_iterator begin()const{return _str; }

        空间管理

        size_t size()const//大小{return _size;}size_t capacity()const//空间{return _capacity;}bool empty()const//空?{return _size == 0;}void reserve(size_t n)//扩容{if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[]_str;_str = tmp;_capacity = n;}}void resize(size_t n, char c = '\0')//重设大小{if (n <= _size){_str[n] = '\0';_size = n;}else {reserve(n);while (_size < n){ _str[_size] = c ;++_size;}_str[_size] = '\0';}}

         修改相关

         push_back()

         根据原型可知此函数是在当前字符串尾插一个字符,其中需要额外注意容量。

        void push_back(char c){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = c;++_size;_str[_size] = '\0';}

        append ()

         根据原型可知此函数有很多的函数重载,但是我们最常用的就是尾插一段字符串,对此就不过多进行阐述。也需要额外注意容量。

        void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){reserve(_size + len);}strcpy(_str + _size, str);//拷贝到原字符串后_size += len;}

         重载+=运算符

         此处复用了以上的push_back和append函数,这也是根据C++中函数管用的复用习惯来做出的选择。

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

        c_str ()

        返回指向数组的指针,该数组包含以 null 结尾的字符序列(即 C 字符串),该字符表示字符串对象的当前值。简而言之就是将string对象转换为C语言风格类型的字符串。

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

        clear()

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

        swap()

        void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}

         重载[ ](最爱的运算符!!!)

        这里其实就是按照数组的相关操作即可,但是要注意越界的判断!

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

         关系运算符的重载

        关系运算符的重载的关键就是根据所需进行相应的数据变化以及做出值的返回。 

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

         基本操作

        查找find() 

        由原型知,分为按照字符查找以及字符串查找还有常量字符串查找,对此,此次实现字符以及字符串查找。

        对于字符查找: 

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

        对于字符串查找: 

        前置知识substr()

         他的主要作用就是在str中从pos位置开始,截取n个字符,然后将其返回。其中的npos实际上是一个全局的静态变量,实际的赋值为-1,都是由于类型为size_t,因此实际上为最大值!

        string substr(size_t pos, size_t len = npos){string s;size_t end = pos + len;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;}// 返回子串s在string中第一次出现的位置size_t find(const char* s, size_t pos = 0) const{const char* p = strstr(_str + pos, s);if (p){return p - _str;}else{return npos;}}

         插入insert()

        根据所插入的空间大小挪字符

void insert(size_t pos, char ch){assert(pos <= _size);if (_size == _capacity){reserve(_capacity == 0 ? 4 : _capacity * 2);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;}void 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;}

        删除 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;}}

        流插入流提取

         重载<<和>>是为了能够像内置一样调用,输入打印。

ostream& operator<<(ostream& out, const string& s)
{/*for (size_t i = 0; i < s.size(); i++){out << s[i];}*/for (auto ch : s)out << ch;return out;
}istream& operator>>(istream& in, string& s)
{s.clear();char buff[129];size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}//s += ch;ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;
}

 总体代码

#pragma once
#define _CRT_SECURE_NO_WARNINGS 01
#include<iostream>
#include<assert.h>
using namespace std;namespace lt
{class string{public:typedef char* iterator;typedef const char* const_iterator;friend ostream& operator<<(ostream& out, const string& s);friend istream& operator>>(istream& in, string& s);iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}/*string():_str(new char[1]{'\0'}),_size(0),_capacity(0){}*/string(const char* str = ""):_size(strlen(str)), _capacity(_size){_str = new char[_capacity + 1];strcpy(_str, str);}// 传统写法// s2(s1)//string(const string& s)//{//	_str = new char[s._capacity+1];//	strcpy(_str, s._str);//	_size = s._size;//	_capacity = s._capacity;//}s2 = s3//string& operator=(const string& s)//{//	if (this != &s)//	{//		char* tmp = new char[s._capacity + 1];//		strcpy(tmp, s._str);//		delete[] _str;//		_str = tmp;//		_size = s._size;//		_capacity = s._capacity;//	}//	return *this;//}void swap(string& s){std::swap(_str, s._str);std::swap(_size, s._size);std::swap(_capacity, s._capacity);}// s2(s1)string(const string& s):_str(nullptr), _size(0), _capacity(0){string tmp(s._str);swap(tmp);}// s2 = s3//string& operator=(const string& s)//{//	if (this != &s)//	{//		string tmp(s);//		//this->swap(tmp);//		swap(tmp);//	}//	return *this;//}// s2 = s3string& operator=(string tmp){swap(tmp);return *this;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}size_t capacity() const{return _capacity;}size_t size() const{return _size;}const char* c_str() const{return _str;}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void resize(size_t n, char ch = '\0'){if (n <= _size){_str[n] = '\0';_size = n;}else{reserve(n);while (_size < n){_str[_size] = ch;++_size;}_str[_size] = '\0';}}size_t find(char ch, size_t pos = 0){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 + pos, sub);if (p){return p - _str;}else{return npos;}}string substr(size_t pos, size_t len = npos){string s;size_t end = pos + len;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;}void push_back(char ch){if (_size == _capacity){reserve(_capacity == 0 ? 4 : _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);}strcpy(_str + _size, str);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const 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);}size_t end = _size + 1;while (end > pos){_str[end] = _str[end - 1];--end;}_str[pos] = ch;_size++;}void 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;}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;}}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);}void clear(){_str[0] = '\0';_size = 0;}private:char* _str;size_t _size;size_t _capacity;//const static size_t npos = -1;  // 特例//const static double npos = 1.1;  // 不支持public:const static size_t npos;};const size_t string::npos = -1;ostream& operator<<(ostream& out, const string& s){/*for (size_t i = 0; i < s.size(); i++){out << s[i];}*/for (auto ch : s)out << ch;return out;}istream& operator>>(istream& in, string& s){s.clear();//s.reserve(128);char buff[129];size_t i = 0;char ch;ch = in.get();while (ch != ' ' && ch != '\n'){buff[i++] = ch;if (i == 128){buff[i] = '\0';s += buff;i = 0;}//s += ch;ch = in.get();}if (i != 0){buff[i] = '\0';s += buff;}return in;}}

                      感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                         给个三连再走嘛~  

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

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

相关文章

linux进程间通信之管道通信

Linux 进程间通信机制分三类&#xff1a;数据交互&#xff0c;同步&#xff0c;信号。理解了这些机制才能灵活运用操作系统提供的 IPC 工具。 一.管道 管道是一种文件形式&#xff0c;是内核的一块缓冲区。匿名管道只能用于具有亲缘关系的进程间通信&#xff0c;命名管道可以用…

在Linux上安装Oracle 数据库 11g (含静默方式安装)

好久没碰11g了&#xff0c;今天&#xff08;2023年11月16日&#xff09;因为有个需求又装了一遍。 在OCI上安装了一个Oracle Linux 6实例&#xff1a; $ uname -a Linux instance-20231116-1239-db11g 4.1.12-124.80.1.el6uek.x86_64 #2 SMP Mon Oct 9 02:32:10 PDT 2023 x86…

多维度梳理 MySQL 锁

多维度梳理 MySQL 锁 1、并发问题的解决方案2、MySQL的各类型锁2.1、从数据操作的类型划分 (读锁、写锁)2.2、从数据操作的粒度划分2.2.1、表锁2.2.1.1、表级别的S 锁、X 锁2.2.1.2、意向锁&#xff08;IS、IX&#xff09;2.2.1.3、自增锁2.2.1.4、元数据锁 2.2.2、行锁2.2.2.1…

华为eNSP综合实验考试

VLAN信息表 设备名称 端口 链路类型 VLAN 参数 HZ-HZCampus-Agg01-S5731 GE0/0/1 Trunk PVID:1 Allow-pass&#xff1a;10 20 Eth-trunk1&#xff08;GE0/0/2,0/0/3,0/0/23&#xff09; Trunk PVID:1 Allow-pass&#xff1a;10 20 GE0/0/24 Access PVID&#xf…

AttributeError: module ‘gradio‘ has no attribute ‘ClearButton‘解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

(1)(1.16) Maxbotix I2C声纳

文章目录 前言 1 连接到Pixhawk 2 通过Mission Planner进行设置 3 测试传感器 4 参数说明 前言 Maxbotix I2C EZ4 声纳&#xff08;又称 I2CXL-MaxSonar-EZ4 或 MB1242&#xff09;是一种相对便宜的短距离&#xff08;最多 7m&#xff09;测距仪&#xff0c;主要设计用于…

小迈迈驰组态软件支持国产龙芯2K1000等处理器

自2019年起&#xff0c;南京迈思德电气自动化有限公司组织研发团队&#xff0c;进行跨平台组态软件的研发&#xff0c;适配国产处理器&#xff0c;目前已经完成单机版产品的研发&#xff0c;并在基于龙芯2K1000处理器[Loongnix操作系统]的加固人机界面产品上应用。 Loongnix是龙…

2023年中国机动车拍卖网络化趋势加速,网络拍卖专场数量大幅上升至47489场[图]

2022年&#xff0c;由于机动车拍卖网络化趋势继续加速&#xff0c;网络拍卖专场数量大幅上升&#xff0c;全国机动车专场拍卖会高达59450场&#xff0c;较上年攀升125.31%。在389家拍卖企业中&#xff0c;举办场次超过100场的企业有27家&#xff0c;合计54850场&#xff0c;占比…

【观察】OpenHarmony:技术先进“创新局”,持续创新“谋新篇”

毫无疑问&#xff0c;开源作为今天整个软件产业的创新“原动力”&#xff0c;目前在软件产业发展中的重要性愈加凸显。根据Linux基金会的统计&#xff0c;现在全球软件产业中有70%以上的代码来源于开源软件。 从这个角度来看&#xff0c;开源技术已逐渐成为推动企业数字化转型和…

全球地表水数据集JRC Global Surface Water Mapping Layers, v1.2数据

简介&#xff1a; 全球地表水覆盖&#xff08;Global Surface Water&#xff09;是利用1984至2019年获取的landsat5、landsat7和landsat8的卫星影像&#xff0c;生成分辨率为30米的一套全球地表水覆盖的地图集。用户可以在全球尺度上按地区回溯某个时间上地表水分的变化情况。…

数据防泄密系统,企业数据防泄密软件

数据防泄密系统&#xff0c;企业数据防泄密软件 数据防泄密系统是一种企业数据防泄密软件&#xff0c;旨在保护企业核心信息资产&#xff0c;防止数据泄露。对电脑中的电子文档进行实时动态保护&#xff0c;全程跟踪操作&#xff0c;并对各种可能造成泄密的途径进行控制&#…

java“贪吃蛇”小游戏

基于java实现贪吃蛇小游戏&#xff0c;主要通过绘制不同的图片并以一定速度一帧一帧地在窗体上进行展示。 我是在javaSwing项目下创建了一个包 名字叫做&#xff1a;Snakes包 包下有一个启动类和一个设置代码的主界面两个类 代码主界面&#xff1a; 代码主界面主要讲解的是 …