『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任意位置插入字符

        我们要考虑头插的位置,endpos的类型都是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_backappend函数来实现。

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

六、其他操作

📒6.1删除操作

        erasepos下标开始删除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中的对象,所以不需要定义在类中作为成员函数,也不需要声明为友元,因为使用流体去和流插入需要ostreamistream对象作为左操作参数。

🌟流插入

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后就刷新缓冲区再继续接收,这样就避免了频繁开辟空间。


🎁结语: 

     本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位读者三连支持。文章有问题可以在评论区留言,博主一定认真认真修改,以后写出更好的文章。你们的支持就是博主最大的动力。

 

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

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

相关文章

尚品汇-(十二)

&#xff08;1&#xff09;数据库表结构 根据以上的需求&#xff0c;以此将SKU关联的数据库表结构设计为如下&#xff1a; base_attr_value&#xff1a;前面学的平台属性值表 我们进行关联&#xff0c;可以从分类导向平台&#xff0c;通过平台过滤商品 &#xff08;2&#xf…

MySQL—统计函数和数学函数以及GROUP BY配合HAVING

合计/统计函数 count -- 演示 mysql 的统计函数的使用 -- 统计一个班级共有多少学生&#xff1f; SELECT COUNT(*) FROM student -- 统计数学成绩大于 90 的学生有多少个&#xff1f; SELECT COUNT(*) FROM student WHERE math > 90 -- 统计总分大于 250 的人数有多少&…

【SpringCloud应用框架】Nacos命名空间、分组和DataID三者关系

第五章 Spring Cloud Alibaba Nacos之命名空间、分组和DataID三者关系 文章目录 一、名词解释三者关系 二、切换不同环境DataID方案Group方案Namespace空间方案 一、名词解释 命名空间&#xff08;Namespace&#xff09; ​用于进行租户粒度的配置隔离。不同的命名空间下&…

GraphRAG

什么是RAG&#xff1f; RAG 是一种自然语言查询方法&#xff0c;用于通过外部知识增强现有的LLM&#xff0c;因此如果问题需要特定知识&#xff0c;问题的答案会更相关。它包括一个检索信息组件&#xff0c;用于从外部源获取附加信息&#xff0c;也称为“基础上下文”&#xf…

pnpm介绍

PNPM 是一个 JavaScript 包管理器&#xff0c;类似于 npm 和 Yarn。它的全称是 "Performant npm"&#xff0c;主要设计目标是优化包的安装和管理过程&#xff0c;以提升速度和效率。PNPM 的主要特点包括&#xff1a; 符号链接&#xff08;Symlink&#xff09;&#x…

AI产品经理发展与规划

今天引用高飞老师的讲课内容&#xff0c;分享一下&#xff0c;何为AI产品经理&#xff1f;这个话题不仅仅希望介绍AI产品经理的工作方式等方面的内容&#xff0c;更多的在于讨论未来产品经理这个行业应该如何发展&#xff1f;行业壁垒在何处&#xff1f;如何应对中年危机&#…

名企面试必问30题(二十六)——毕业这么久了,为什么还没有找到工作?

回答一&#xff1a; “毕业后的这段时间&#xff0c;我一直在努力寻找最适合自己发展的岗位。我没有急于随便接受一份工作&#xff0c;而是希望能够进入一个与我的专业技能和职业规划高度匹配的公司。在这个过程中&#xff0c;我不断提升自己的技术能力&#xff0c;学习新的测…

PyQT: 开发一款ROI绘制小程序

在一些基于图像或者视频流的应用中&#xff0c;比如电子围栏/客流统计等&#xff0c;我们需要手动绘制一些感兴趣&#xff08;Region of Interest&#xff0c;简称ROI&#xff09;区域。 在这里&#xff0c;我们基于Python和PyQt5框架开发了一款桌面应用程序&#xff0c;允许用…

c#类型转换和常见集合类型

目录 1. 整数转换&#xff0c;整数和字符串&#xff0c;字符串和整数之间的转换怎么实现&#xff1f; 2. 日期转换&#xff0c;获取当前日期&#xff0c;字符串转日期&#xff0c;日期转字符串怎么实现&#xff1f; 3. 举例一维、二维、三维数组 4. 需求&#xff1a;有个88…

事务(数据库)

是一组操作的集合&#xff0c;是一个不可分割的工作单位&#xff0c;事物会把所有的操作作为一个整体一起向系统提交或撤销操作请求&#xff0c;这些操作要么同时成功&#xff0c;要么同时失败 create table account(id int auto_increment primary key comment 主键ID,name va…

VPN 的入门介绍

VPN&#xff08;虚拟专用网络&#xff09; 简介 虚拟专用网络&#xff0c;简称虚拟专网&#xff08;VPN&#xff09;&#xff0c;其主要功能是在公用网络上建立专用网络&#xff0c;进行加密通讯。在企业网络中有广泛应用。VPN网关通过对数据包的加密和数据包目标地址的转换实…

14-48 剑和诗人22 - RAG 的主要痛点和解决方案

​​​​​ 检索增强生成 (RAG) 模型已成为一种有前途的方法&#xff0c;它利用存储在文档中的外部知识来提高生成文本的准确性和相关性。通过检索和调节相关的上下文文档&#xff0c;与传统语言模型相比&#xff0c;RAG 模型可以产生更真实、更深入和更具体的响应。 然而&…

诸葛亮的空城计 - 代理模式

定场诗 “无形之中蕴含至理&#xff0c;虚实相生方见大道。” 在三国演义中&#xff0c;诸葛亮的空城计可谓神来之笔。这看似冒险的策略&#xff0c;实则蕴含深意。今天&#xff0c;我们将透过空城计&#xff0c;一窥软件设计中代理模式的奥秘。 西城无人旦夕危&#xff0c;…

君方智能设计平台-事务管理技术方案

1.背景介绍 事务处理是指对数据进行一组操作&#xff0c;这些操作要么全部成功&#xff0c;要么全部失败&#xff0c;以确保数据的一致性和完整性。软件的事务管理主要实现方案主要涉及以下几个方面&#xff1a; &#xff08;1&#xff09;数据一致性&#xff1a;在CAD软件中…

STM32实现看门狗(HAL库)

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

Flask项目搭建及部署(完整版!全网最全)

flask搭建及部署 pip 19.2.3 python 3.7.5 Flask 1.1.1 Flask-SQLAlchemy 2.4.1 Pika 1.1.0 Redis 3.3.11 flask-wtf 0.14.2 1、创建flask项目&#xff1a; 创建完成后整个项目结构树&#xff1a; app.py: 项⽬管理⽂件&#xff0c;通过它管理项⽬。 static: 存放静态文…

map和set的原理、优劣势、应用场景和示例代码,统统告诉你。

map和set的原理都是基于哈希表实现的&#xff0c;通过哈希值来快速查找和插入元素&#xff0c;从而实现高效的数据存储和管理&#xff0c;那么他们之间有什么不同呢&#xff0c;该如何选择&#xff0c;本文带你了解。 一、map和set的原理 map和set都是数据结构&#xff0c;用…

【分布式系统三】监控平台Zabbix对接grafana(截图详细版)

目录 一.安装grafana并启动 二.浏览器访问 三.导入zabbix数据&#xff0c;对接grafana 四.如何导入模版 以前两篇博客为基础 【分布式系统】监控平台Zabbix介绍与部署&#xff08;命令截图版&#xff09;-CSDN博客 【分布式系统】监控平台Zabbix自定义模版配置-CSDN博客 …

java ReadWriteLock接口

在 Java 中&#xff0c;ReadWriteLock 接口的实现类ReentrantReadWriteLock 类提供了一种允许多个线程同时读取某一资源但只允许一个线程写的锁定机制。这种机制可以提高并发性能&#xff0c;特别是在读操作远多于写操作的场景下。 特性&#xff1a; 可重入&#xff1b;不存…

前端使用Threejs加载机械臂并控制机械臂跳舞

1. 前言 在我的第一篇博客中,大致讲解了如何使用threejs导入机械臂模型,以及如何让机械臂模型动起来的案例,可以看一下之前的博客前端使用Threejs控制机械臂模型运动 本篇博客主要讲解的是在原来的基础上添加GSAP动画库的应用,可以通过动画,来让机械臂进行指定轨迹位姿的运动…