『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.rhkb.cn/news/371614.html

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

相关文章

Linux内核链表使用方法

简介&#xff1a; 链表是linux内核中最简单&#xff0c;同时也是应用最广泛的数据结构。内核中定义的是双向链表。 linux的链表不是将用户数据保存在链表节点中&#xff0c;而是将链表节点保存在用户数据中。linux的链表节点只有2个指针(pre和next)&#xff0c;这样的话&#x…

【Linux】记录一起网站劫持事件

故事很短&#xff0c;处理也简单。权当记录一下&#xff0c;各位安全大大们手下留情。 最近一位客户遇到官网被劫持的情况&#xff0c;想我们帮忙解决一下&#xff08;本来不关我们的事&#xff0c;毕竟情面在这…还是无偿地协助一下&#xff09;&#xff0c;经过三四轮“谦让…

多线程(进阶)

前言&#x1f440;~ 上一章我们介绍了线程池的一些基本概念&#xff0c;今天接着分享多线程的相关知识&#xff0c;这些属于是面试比较常见的&#xff0c;大部分都是文本内容 常见的锁策略 乐观锁 悲观锁 轻量锁 重量级锁 自旋锁 挂起等待锁 可重入锁和不可重入锁 互斥…

Python结合MobileNetV2:图像识别分类系统实战

一、目录 算法模型介绍模型使用训练模型评估项目扩展 二、算法模型介绍 图像识别是计算机视觉领域的重要研究方向&#xff0c;它在人脸识别、物体检测、图像分类等领域有着广泛的应用。随着移动设备的普及和计算资源的限制&#xff0c;设计高效的图像识别算法变得尤为重要。…

MPI hello world SSH 免密互联

目标&#xff1a; 我们想实现2台主机免密互联&#xff0c;将MPI Hello World跑起来 假设hostname是node01,node02,&#xff08;Linux shell窗口一般是UserNameHostName&#xff0c;node1和node2一定要和HostName一样&#xff09; hostname是/etc/hosts中的配置&#xff0c;如下…

并发编程-05AQS原理

并发编程-深入理解AQS之ReentrantLock 一 认识AQS 在讲解AQS原理以及相关同步器之前&#xff0c;我们需要对AQS有一些基本的认识&#xff0c;了解下它有什么样的机制&#xff0c;这样追踪源码的时候就不会太过于迷茫&#xff01; 1.1 什么是AQS java.util.concurrent包中的大…

JavaWeb—js(2)

函数 /* 创建一个方法形参不需要写参数类型&#xff0c;多个参数直接用逗号隔开就可以 */function show(a,b,c){console.log(我是show方法,a,b,c)return ab;}// 调用方法//调用方法时 形参个数如果和实参个数对不上不会有异常show();show(你好)show(1,2);let add show(1,2,3…

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

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

JCR一区 | Matlab实现GAF-PCNN-MATT、GASF-CNN、GADF-CNN的多特征输入数据分类预测/故障诊断

JJCR一区 | Matlab实现GAF-PCNN-MATT、GASF-CNN、GADF-CNN的多特征输入数据分类预测/故障诊断 目录 JJCR一区 | Matlab实现GAF-PCNN-MATT、GASF-CNN、GADF-CNN的多特征输入数据分类预测/故障诊断分类效果格拉姆矩阵图GAF-PCNN-MATTGASF-CNNGADF-CNN 基本介绍程序设计参考资料 分…

npm 淘宝镜像证书过期,错误信息 Could not retrieve https://npm.taobao.org/mirrors/node/latest

更换 npm 证书 问题描述报错原因更换步骤1 找到 nvm 安装目录2 发现证书过期3 更换新地址4 保存后&#xff0c;重新安装成功 问题描述 在使用 nvm 安装新版本时&#xff0c;未成功&#xff0c;出现报错&#xff1a; Could not retrieve https://npm.taobao.org/mirrors/node/l…

Python酷库之旅-第三方库Pandas(009)

目录 一、用法精讲 19、pandas.read_xml函数 19-1、语法 19-2、参数 19-3、功能 19-4、返回值 19-5、说明 19-6、用法 19-6-1、数据准备 19-6-2、代码示例 19-6-3、结果输出 20、pandas.DataFrame.to_xml函数 20-1、语法 20-2、参数 20-3、功能 20-4、返回值 …

AI Earth ——开发者模式案例10:基于 CNN 的 AI 分类模型开发

基于 CNN 的 AI 分类模型开发 本案例主要介绍如何快速利用 AIE Python SDK 创建机器学习建模流程。我们主要使用到 Python SDK的Machine Learning Proxy 模块(下文简称 AieMlProxy )。该模块涵盖了一系列用户与训练集群之间的交互接口,包括:鉴权、数据加载、训练任务提交、…

Shell Expect自动化交互(示例)

Shell Expect自动化交互 日常linux运维时&#xff0c;经常需要远程登录到服务器&#xff0c;登录过程中需要交互的过程&#xff0c;可能需要输入yes/no等信息&#xff0c;所以就用到expect来实现交互。 关键语法 ❶&#xff3b;#!/usr/bin/expect&#xff3d; 这一行告诉操…

MySQL之备份与恢复和MySQL用户工具(一)

备份与恢复 备份脚本化 为备份写一些脚本是标准做法。展示一个示例程序&#xff0c;其中必定有很多辅助内容&#xff0c;这只会增加篇幅&#xff0c;在这里我们更愿意列举一些典型的备份脚本功能&#xff0c;展示一些Perl脚本的代码片段。你可以把这些当作可重用的代码块&…

io流 多线程

目录 一、io流 1.什么是io流 2.流的方向 i.输入流 ii.输出流 3.操作文件的类型 i.字节流 1.拷贝 ii.字符流 ​3.字符流输出流出数据 4.字节流和字符流的使用场景 5.练习 6.缓冲流 1.字节缓冲流拷贝文件 2.字符缓冲流特有的方法 1.方法 2.总结 7.转换流基本用法…

数字信号处理及MATLAB仿真(3)——量化的其他概念

上回书说到AD转换的两个步骤——量化与采样两个步骤。现在更加深入的去了解以下对应的概念。学无止境&#xff0c;要不断地努力才有好的收获。万丈高楼平地起&#xff0c;唯有打好基础&#xff0c;才能踏实前行。 不说了&#xff0c;今天咱们继续说说这两个步骤&#xff0c;首先…

【国产开源可视化引擎Meta2d.js】网格

画布背景网格 在线体验&#xff1a; 乐吾乐2D可视化 示例&#xff1a; // 设置默认缺省网格属性 meta2d.store.options.grid true; // 开启 meta2d.store.options.gridColor eeeeee; // 网格线条颜色 meta2d.store.options.gridSize 10; // 格子大小// 设置单个图纸的网格…

pnpm的坑

请问pnpm的两个坑怎么解决&#xff1a; 第一个坑&#xff1a;没有节省磁盘空间 我已经配置了依赖的存储位置&#xff0c; 但我在项目里pnpm install以后&#xff0c;发现依赖包还是很大&#xff0c; 然后发现里面的链接并不是指向先前配置的依赖存储位置&#xff0c;而是指…

java核心-泛型

目录 概述什么是泛型分类泛型类泛型接口泛型方法 泛型通配符分类 泛型类型擦除分类无限制类型擦除有限制类型擦除 问题需求第一种第二种 概述 了解泛型有利于学习 jdk 、中间件的源码&#xff0c;提升代码抽象能力&#xff0c;封装通用性更强的组件。 什么是泛型 在定义类、接…

VSCode设置好看清晰的字体!中文用鸿蒙,英文用Jetbrains Mono

一、中文字体——HarmonyOS Sans SC 1、下载字体 官网地址&#xff1a;https://developer.huawei.com/consumer/cn/design/resource/ 直接下载&#xff1a;https://communityfile-drcn.op.dbankcloud.cn/FileServer/getFile/cmtyPub/011/111/111/0000000000011111111.20230517…