c++编程(11)——string类的模拟实现
欢迎来到博主的专栏——c++编程
博主ID:代码小豪
文章目录
- 前言
- string类的模拟实现
- string的成员对象
- 构造、赋值、析构
- 访问成员对象的接口
- 访问字符串中的元素
- 迭代器
- 对字符序列的插入、删除元素操作
- mystring类的相关操作
- mystring类的所有模拟实现以及测试案例
前言
本片的string类的模拟实现不涉及模板,泛型编程并不是本专栏的重点内容,本专栏的主要目的是了解c++面向对象编程的特性,以及STL的部分使用方法。因此本博客模拟的string类是为了让读者了解类的封装方法、接口设计。
string类的模拟实现
string的成员对象
一个类需要设计该类的属性和行为。属性是成员对象,而行为则是成员函数,string类是字符序列的类。
c++是基于C语言扩展而成,因此c++当中的字符序列和c语言的如出一撤,即字符序列的字符在内存中连续存储,再用一个char*的指针指向字符序列。c++称这种字符序列为c-string。
如果要将字符串封装起来,那么我们还需要提供其他的属性来显示这个字符序列的状态,比如当前字符串的长度。
我们还想要这个字符串可以自动的扩大存储。那么最好的方法就是使用动态内存来管理这个字符序列。因此还需要为其设计容量这一属性。方便用户(即类的使用者)查看当前字符序列的可存储内存。
那么根据上述理论,我们可以确定一个基本的string类应该封装这么三个对象:c-string,大小,可存储容量。
class mystring
{
private:char* _str;//c-stringsize_t _size;//当前的字符长度size_t _capacity;//容量
};
c++的string类还存在一个特殊的static const成员常量npos,我们也将其设计在类中,但是要注意static成员变量要声明在类中,定义在类外。
```cpp
class mystring
{
private:char* _str;//c-stringsize_t _size;//当前的字符长度size_t _capacity;//容量static const size_t npos;
};
const size_t mystring::npos = -1;
构造、赋值、析构
一个类想要正常的使用,那么为其设计合理的构造、析构、赋值成员函数时必不可少的,即使你粗心的遗忘了某个部分,编译器都会为你生成这些函数,因此,想要设计好一个类,这个模块是必不可少的。
我们希望string类的构造能支持下面三种初始化形式:默认初始化成空string,也可以用c-string初始化这个string,还可以用string对象初始化string。因此我们需要设计这么三个函数。默认构造、拷贝构造、和c-string作为参数的构造。
class mystring
{
public:mystring();//默认构造mystring(const mystring& str);//拷贝构造mystring(const char* str);//c-string构造
private:char* _str;//c-stringsize_t _size;//当前的字符长度size_t _capacity;//容量
};
我们最好将声明和定义分离在两个编程单元当中,这是为了减少链接问题。由于篇幅问题博主就不详细声明了,博主将会在c++杂谈中提到这个。
string的默认构造,默认构造是构造一个空字符串。空字符串长度为0,但是内存并不为0,因为空字符串并不是没有字符,而是开辟了一个只有‘\0’的字符串。
mystring::mystring()
{_size = 0;_capacity = 0;_str = new char[_capacity + 1] {0};
}
为什么申请的空间要比_capacity多一个呢?这是因为容量不需要注意\0的管理,我们设计的容量是为了记录可存储有效字符的容量,但是在申请空间的时候需要比容量多申请一个,这个位置是留给\0的。
拷贝构造还是为了拷贝c-string的。因此不仅仅string对象中的_str指向的字符序列与c-string一致,而且_size和_capacity都要与c-string一致。
mystring::mystring(const char* str):_size(strlen(str))
{_capacity = _size;_str = new char[_capacity + 1];strcpy(_str, str);
}
博主这里使用了C语言的库函数strlen和strcpy,C语言的<string.h>在string类的模拟实现中大量使用,主要原因还是博主懒,如果大家想了解strlen和strcpy的使用逻辑,那么可以去看博主在C语言进阶指南(14),里面有相对应的模拟实现。(模拟实现是为了让自己能够对最近的知识进行一次实践、而不是造轮子,博主当前的阶段还能写string类能比设计标准库大佬好?这当然不可能)。
拷贝构造函数是,将待拷贝的mystring对象的所有属性都拷贝过来。
mystring::mystring(const mystring& str)
{_size = str._size;_capacity = str._capacity;//这里是深拷贝_str = new char[_capacity + 1];strcpy(_str, str._str);
}
析构函数则是将申请的空间进行释放,由于字符序列不再存在有效字符,因此_size和_capacity置为0。
mystring::~mystring()
{delete[] _str;_size = 0;_capacity = 0;
}
赋值重载函数的本质也是将对象参数进行拷贝,与拷贝构造的原理相同。但是要将申请的空间进行销毁。
mystring& mystring::operator=(const mystring& str)
{_size = str._size;_capacity = str._capacity;delete[] _str;_str = new char[_capacity + 1];strcpy(_str, str._str);return *this;
}
访问成员对象的接口
mystring中的成员对象都被隐藏了起来,如果我们想要让用户知道这些数据,可以为其设计访问的接口,方便用户进行操作,而不破坏类的封装性。
size_t size() { return _size; }size_t capacity() { return _capacity; }void reserve(size_t n);//扩容函数void clear();//清空clear
reserve的目的是让用户可以手动的为mystring对象进行扩容。当然,我们也可以在成员函数当中调用这个函数完成扩容,可谓是一举两得。
由于c++并没有给出像realloc这种可以原地扩容的关键字,因此博主在reserve当中使用的是异地扩容。
void mystring::reserve(size_t n)
{if (n > _capacity){size_t newcapacity = _capacity == 0 ? 4 : 1.5 * _capacity;//异地扩容char* tmp = new char[newcapacity + 1] {0};strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = newcapacity;}
}
clear是将字符串里的有效字符清空,我们不需要修改容量,只需要修改大小,并且将字符序列的起始字符换成‘\0’就行了。(因为_str的本质是c-string,而c-string的定义就是从起始字符开始到第一个遇到的’\0’为c-string)。
void mystring::clear()
{_str[0] = '\0';_size = 0;
}
访问字符串中的元素
mystring对象时一个字符序列,因此我们需要考虑用户该如何访问字符序列的元素。由于_str是一个c-string,那么我们可以考虑C语言的做法,用下标访问符[]访问元素。这就需要我们为mystring重载一个下标访问符的函数了。
char& operator[](size_t pos) { return _str[pos]; }const char& operator[](size_t pos)const { return _str[pos]; }
由于我们需要考虑到const对象和non-const对象调用此函数的不同效果,因此需要将其重载一个const对象的调用版本,和一个非const对象的调用版本。
迭代器
迭代器是一个用来在容器、对象当中遍历或者访问元素的接口。由于c-string可以用char的指针来遍历或访问元素。因此我们不妨将char的指针作为mystring的迭代器。
typedef char* iterator;typedef const char* const_iterator;iterator begin() { return _str; }//返回对象的起始迭代器iterator end() { return _str + _size; }//返回对象的末尾迭代器const_iterator end() const{ return _str + _size; }//返回const对象的末尾迭代器const_iterator begin() const{ return _str + _size; }//返回const对象的起始迭代器
对字符序列的插入、删除元素操作
我们设计为字符序列的相关修改函数,字符序列本身是一个顺序表的数据结构,因此我们设计插入、删除元素的函数时可以参考顺序表的插入、删除元素的算法。
void push_back(char c);//追加字符void append(const mystring& str);//追加字符串mystring& operator+=(const mystring& str);//追加字符串mystring& operator+=(char c);//追加字符void insert(size_t pos, char ch);//在pos的位置,插入字符void insert(size_t pos, const char*str);//在pos的位置,插入字符void erase(size_t pos = 0, size_t len = npos);//删除pos位置后len个长度的字符void swap(mystring& s);//交换字符串
追加字符或字符串的操作可以参考顺序表的尾插法。尾插是在字符串的末尾加入元素。
追加的过程中要注意mystring对象的容量可能满了,注意为该对象进行扩容。
void mystring::push_back(char c){if (_size == _capacity)//扩容{size_t newcapacity = _capacity == 0 ? 4 : 1.5 * _capacity;reserve(newcapacity);_capacity = newcapacity;}_str[_size++] = c;_str[_size] = '\0';}void mystring::append(const mystring& str){size_t len = strlen(str._str);while (_size + len >= _capacity)//扩容{size_t newcapacity = _capacity == 0 ? 4 : 1.5 * _capacity;reserve(newcapacity);_capacity = newcapacity;}size_t end = _size + len;size_t i = 0;strcpy(_str,str._str);}mystring& mystring::operator+=(const mystring& str){append(str);//这里我们直接复用追加函数return *this;}mystring& mystring::operator+=(char c){push_back(c);//复用追加函数return *this;
插入操作也是和顺序表的插入算法一致,因为字符序列的本质就是一个顺序表。
void mystring::insert(size_t pos, char ch){assert(_size >= pos);//检测合法性if (_size == _capacity)//扩容{size_t newcapacity = _capacity == 0 ? 4 : 1.5 * _capacity;reserve(newcapacity);_capacity = newcapacity;}size_t end = _size;while (end > pos)//挪动数据{_str[end] = _str[end - 1];end--;}_str[pos] = ch;//插入数据_size++;}void mystring::insert(size_t pos, const char* str){assert(_size >=pos);//判断合法性size_t len = strlen(str);//判断插入字符的有效字符个数size_t newsize = _size + len;while (newsize >= _capacity)//扩容{size_t newcapacity = _capacity == 0 ? 4 : 1.5 * _capacity;reserve(newcapacity);_capacity = newcapacity;}size_t end = newsize;while (end >= pos+len)//挪动数据{_str[end] = _str[end - len];end--;}while (len--)//插入数据,不会插入‘\0’{_str[pos + len] = str[len];}_size = newsize;}
删除数据则是将该范围的数据被后面的数据覆盖就行
void mystring::erase(size_t pos, size_t len ){assert(pos <= _size);if(_size -pos <= len){_str[pos] = '\0';_size = pos;}else{size_t end = _size;size_t begin = pos + len;while (begin <= end){_str[begin - len] = _str[begin];begin++;}_size -= len;}}
mystring类的相关操作
我们希望mystring类可以用于C语言的函数,换句话说就是让mystring中的_str拿出来是用于C语言设计的函数。
const char* c_str() const{ return _str; }
我们还可以设计一个查找函数,方便我们查找字符或字符串在mystring对象当中的位置。
size_t mystring::find(char ch, size_t pos )const{assert(pos <= _size);while (_str[pos] != '\0'){if (_str[pos] == ch)//查找字符{return pos;}pos++;}return npos;//返回字符}size_t mystring::find(const char* str, size_t pos )const{assert(pos <= _size);const char* substr;substr = strstr(_str+pos, str);//查找字符串if (substr == nullptr){return npos;}return substr-_str;//返回字符串}
查找字符串会用到复杂的算法,比如KMR查找算法,这里博主不多讲述,所以用strstr这个C语言函数,并且利用指针相减的特性取巧的解决了这个问题。
我们还可以重载io流,使得cout和cin可以对mystring类的对象进行操作,注意这两函数是定义成非成员函数的。
istream& operator>> (istream& is, mystring& str);ostream& operator<< (ostream& os, const mystring& str);
istream& operator>> (istream& is, mystring& str){str.clear();char ch=0;ch=is.get();while (ch != '\n' && ch != ' '){str += ch;ch = is.get();}return is;}ostream& operator<< (ostream& os, const mystring& str){os << str.c_str();return os;}
mystring类的所有模拟实现以及测试案例
博主将mystring类的模拟实现的所有代码以及测试案例都放在博主本人的代码仓库当中。欢迎查阅。
博主的gitee:代码小豪的代码仓库
相关文章:

c++编程(11)——string类的模拟实现
欢迎来到博主的专栏——c编程 博主ID:代码小豪 文章目录 前言string类的模拟实现string的成员对象构造、赋值、析构访问成员对象的接口访问字符串中的元素迭代器对字符序列的插入、删除元素操作mystring类的相关操作 mystring类的所有模拟实现以及测试案例 前言 本…...

Python从0到POC编写--函数
数学函数: 1. len len() 函数返回对象(字符、列表、元组等)长度或项目个数, 例如: str "python" len(str)2. range range() 函数返回的是一个可迭代对象(类型是对象),…...

【教程】Linux/Jetson 安装X11VNC同步屏幕内容
转载请注明出处:小锋学长生活大爆炸[xfxuezhagn.cn] 如果本文帮助到了你,请不吝给个[点赞、收藏、关注]哦~ 目录 背景说明 实际效果 安装步骤 安装 x11vnc 配置 x11vnc 配置 x11vnc 作为系统服务 使用 VNC 客户端连接 背景说明 通常vnc-server是单…...

【LLM第五篇】名词解释:prompt
1.是什么 提示工程(Prompt Engineering)是一门较新的学科,关注提示词开发和优化,帮助用户将大语言模型(Large Language Model, LLM)用于各场景和研究领域。 掌握了提示工程相关技能将有助于用户更好地了解…...

k8s v1.20二进制部署 部署 CNI 网络组件 部署 Calico
一、部署 flannel 1.1.K8S 中 Pod 网络通信 ●Pod 内容器与容器之间的通信 在同一个 Pod 内的容器(Pod 内的容器是不会跨宿主机的)共享同一个网络命名空间,相当于它们在同一台机器上一样,可以用 localhost 地址访问彼此的端口。…...

在React中利用Postman测试代码获取数据
文章目录 概要名词解释1、Postman2、axios 使用Postman测试API在React中获取并展示数据小结 概要 在Web开发中,通过API获取数据是一项常见任务。Postman是一个功能强大的工具,可以帮助开发者测试API,并查看API的响应数据。在本篇博客中&…...

嵌入式学习-通用定时器
简介 框图介绍 时钟选择 计数器部分 输入捕获和输出比较框图 嵌入式学习全文参考(小向是个der)做笔记:https://blog.csdn.net/qq_41954556/article/details/129735708...

培训行业有哪些ai工具?
培训行业利用人工智能(AI)工具的方式多种多样,其中一些常见的工具包括: 1. **经AI深度学习的OCR软件**:OCR能给培训行业带来很大的便利,能大大提高工作效率和降低文字录入的成本,但一般的OCR工具…...

7.STL中string的一些超常用函数 (附习题)
目录 1.find 2.atoi 3.to_string 4.getline 【leetcode 习题】 387.字符串中的第一个唯一字符 125. 验证回文串 1.find 1.查找第一次出现的目标字符串:说明:如果查找成功则输出查找到的第一个位置,否则返回-1; s1.find(s2…...

GPT搜索鸽了!改升级GPT-4
最近OpenAI太反常,消息一会一变,直让人摸不着头脑。 奥特曼最新宣布:5月13日开发布会,不是GPT-5,也不是盛传的GPT搜索引擎,改成对ChatGP和GPT-4的升级~ 消息一出,大伙儿都蒙了。 之…...

数字绘画教学实训解决方案
一、建设背景 1.1政策背景 教育信息化政策推动:近年来,随着教育信息化政策的不断推动,各级教育部门纷纷出台相关政策,鼓励和支持教育信息化的发展。数字绘画作为现代艺术教育的重要组成部分,其教学实训解决方案的建设…...

C#之如何判断数据类型
一、GetType方法 a.GetType():获取当前变量的类型对象 string str "Hello World";Console.WriteLine(str.GetType()); 结果: 二、typeof方法 typeof(Int):获取的是Int类型的类型对象 int num 10;Console.WriteLine(num.GetType() typeof(i…...
算法学习笔记(Tarjan)
本文介绍 T a r j a n Tarjan Tarjan求强联通分量、找割点和割边、找环。 Tarjan求强联通分量 例题:【模板】有向图缩点 题目描述 给定一个 n n n点 m m m边的有向图(保证不存在重边与自环,但不保证连通),请你求出…...

一台linux通过另一台linux访问互联网-TinyProxy
参考: https://blog.csdn.net/weixin_41831919/article/details/113061317https://www.yuncongz.com/archives/1.htmlhttps://blog.csdn.net/aoc68397/article/details/101893369 环境:ubuntu 18.04 机器1: IP 219.216.65.252 (可以访问外网) 机器2: IP…...

探索数据结构:堆的具体实现与应用
✨✨ 欢迎大家来到贝蒂大讲堂✨✨ 🎈🎈养成好习惯,先赞后看哦~🎈🎈 所属专栏:数据结构与算法 贝蒂的主页:Betty’s blog 1. 堆的概念 堆(Heap)是计算机科学中一类特殊的数据结构。堆通常是一个…...

网络2--MAC地址,IP地址的理解
引入: 每一张主机都会有一张网卡,每一张网卡都有一个48bit位的序列号 当我们的热点被连上,你查看时,就会出现MAC地址,IP地址 那么他们两个是什么呢??? MAC地址 在同一个局域网中…...
类型的转换
首先我们要了解java中的数据类型转换是指将一种数据类型转换成另一种数据类型的过程。 什么时候会用到?我觉得两种情况会用到 等号左右两边类型不一致(一般发生在赋值时)不同类型的数据参与运算(一般发生在计算时) 转…...

memset函数
让我们先看两个代码 memset(dp, 0x3f, sizeof(dp)); for (int i 0; i < 5; i)cout << dp[i] << " "; memset(dp, 127, sizeof(dp)); for (int i 0; i < 5; i)cout << dp[i] << " "; 代码结果如下: 现在我们来分…...
Java面向对象——多态
即同一个方法可以根据发送对象的不同而采用多种不同的行为方式。 一个对象的实际类型是确定的,但可以指向对象的引用的类型有很多(父类,有关系的类)。 多态存在的条件: 1. 有继承关系; 2. 子类重写父类…...

python 对矩阵与矩阵之间对应位置的元素,做softmax操作,代码实战
1.对矩阵中对应位置的元素,做softmax 对于一个向量,softmax函数会对其中每一个元素进行指数运算,然后除以所有元素指数和的结果。当将其应用到多个矩阵的相应位置上时,我们实际上是在对每个位置的一组数(从各个矩阵的同…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
解锁数据库简洁之道:FastAPI与SQLModel实战指南
在构建现代Web应用程序时,与数据库的交互无疑是核心环节。虽然传统的数据库操作方式(如直接编写SQL语句与psycopg2交互)赋予了我们精细的控制权,但在面对日益复杂的业务逻辑和快速迭代的需求时,这种方式的开发效率和可…...
【C语言练习】080. 使用C语言实现简单的数据库操作
080. 使用C语言实现简单的数据库操作 080. 使用C语言实现简单的数据库操作使用原生APIODBC接口第三方库ORM框架文件模拟1. 安装SQLite2. 示例代码:使用SQLite创建数据库、表和插入数据3. 编译和运行4. 示例运行输出:5. 注意事项6. 总结080. 使用C语言实现简单的数据库操作 在…...

涂鸦T5AI手搓语音、emoji、otto机器人从入门到实战
“🤖手搓TuyaAI语音指令 😍秒变表情包大师,让萌系Otto机器人🔥玩出智能新花样!开整!” 🤖 Otto机器人 → 直接点明主体 手搓TuyaAI语音 → 强调 自主编程/自定义 语音控制(TuyaAI…...

【OSG学习笔记】Day 16: 骨骼动画与蒙皮(osgAnimation)
骨骼动画基础 骨骼动画是 3D 计算机图形中常用的技术,它通过以下两个主要组件实现角色动画。 骨骼系统 (Skeleton):由层级结构的骨头组成,类似于人体骨骼蒙皮 (Mesh Skinning):将模型网格顶点绑定到骨骼上,使骨骼移动…...
【JavaSE】绘图与事件入门学习笔记
-Java绘图坐标体系 坐标体系-介绍 坐标原点位于左上角,以像素为单位。 在Java坐标系中,第一个是x坐标,表示当前位置为水平方向,距离坐标原点x个像素;第二个是y坐标,表示当前位置为垂直方向,距离坐标原点y个像素。 坐标体系-像素 …...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
Redis:现代应用开发的高效内存数据存储利器
一、Redis的起源与发展 Redis最初由意大利程序员Salvatore Sanfilippo在2009年开发,其初衷是为了满足他自己的一个项目需求,即需要一个高性能的键值存储系统来解决传统数据库在高并发场景下的性能瓶颈。随着项目的开源,Redis凭借其简单易用、…...

三分算法与DeepSeek辅助证明是单峰函数
前置 单峰函数有唯一的最大值,最大值左侧的数值严格单调递增,最大值右侧的数值严格单调递减。 单谷函数有唯一的最小值,最小值左侧的数值严格单调递减,最小值右侧的数值严格单调递增。 三分的本质 三分和二分一样都是通过不断缩…...

C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...