【c++进阶(二)】STL之string类的模拟实现
💓博主CSDN主页:Am心若依旧💓
⏩专栏分类c++从入门到精通⏪
🚚代码仓库:青酒余成🚚🌹关注我🫵带你学习更多c++
🔝🔝
1.前言
本章重点
本章主要介绍一些关键接口的模拟实现,例如:构造函数,拷贝构造函数,析构函数,赋值重载函数,插入删除函数等等。
2.默认成员函数
在实现默认成员函数之前,先确定成员变量
namespace wzz
{class string{private:char* _str;//用一个字符数组来模拟stringsize_t _size;//数组中实际有效的个数size_t _capacity;//数组中容量的大小static const size_t npos;//静态成员变量来表示最大值};const string::size_t npos=-1;//静态成员变量在类内声明,在类外定义
}
1.默认构造函数--当没有传值的时候,就用一个缺省值来代替--这个缺省值用空字符串代替
string (const char* str="")
{_str=new char[_capacity+1];_size=strlen(str);_capacity=_size;//初始时,容量就让其等于有效元素的个数strcpy(_str,str);
}
补充迭代器构造
template <class Inputeiterrator>
string(Inputeiterator begin,Inputeriterator end)
{while(begin!=end){push_back(*begin);begin++; }
}
2.拷贝构造函数--用一个char*来初始化另一个char*
在这里会详细介绍深浅拷贝--后续就不在重点介绍了
浅拷贝:拷贝出来的目标对象的指针和源对象的指针指向的内存空间是同一块空间。其中一个对象的改动会对另一个对象造成影响。
深拷贝:深拷贝是指源对象与拷贝对象互相独立。其中任何一个对象的改动不会对另外一个对象造成影响。
很明显,深拷贝比浅拷贝更加安全,不会有同一块空间析构两次的问题。 这里拷贝构造就介绍两种写法
法一:开辟一块空间,然后把数据拷贝过来--如下图
代码:
string (const string& s):_size(s._size):_capacity(s._capacity):_str(new char[_capacity+1])
{strcpy(_str,s._str);
}
法二:利用swap函数
法二与法一的思想不同:先根据源字符串的C字符串调用构造函数构造一个tmp对象,然后再将tmp对象与拷贝对象的数据交换即可。拷贝对象的_str与源对象的_str指向的也不是同一块空间,是互相独立的。
代码如下:
string(const string& s):_str(nullptr), _size(0), _capacity(0)
{string tmp(s.begin(),s.end()); //调用构造函数(这里用的是迭代器构造),构造出一个C字符串为s._str的对象swap(tmp); //交换这两个对象
}
swap函数的模拟实现
只需要把每个成员变量进行交换即可
//交换两个对象的数据
void swap(string& s)
{//调用库里的swapstd::swap(_str, s._str); //交换两个对象的C字符串std::swap(_size, s._size); //交换两个对象的大小std::swap(_capacity, s._capacity); //交换两个对象的容量
}
这里推荐使用方法二来模拟实现构造函数
3.赋值重载函数
这里也涉及到深浅拷贝的问题--因此提供两种深拷贝的写法
法一:把原来的空间直接释放,然后开辟新空间,然后再赋值
//法一
string& operator=(const string& s)
{if (this != &s) //防止自己给自己赋值{delete[] _str; //将原来_str指向的空间释放_str = new char[s._capacity + 1]; //重新申请一块空间strcpy(_str, s._str); //将s._str拷贝一份到_str_size = s._size; //_size赋值_capacity = s._capacity; //_capacity赋值}return *this; //返回左值(支持连续赋值)
}
法二:用交换函数
//方法二
string& operator=(const string& s)
{if (this != &s) //防止自己给自己赋值{string tmp(s); //用s拷贝构造出对象tmpswap(tmp); //交换这两个对象}return *this; //返回左值(支持连续赋值)
}
注意:
用传值返回还是传引用返回,要看你返回的这个值除了作用域还在不在,如果在的话就可以使用传值返回,如果不在的话就一定要使用传引用返回,否则就会报错。
4.析构函数
string类的析构函数需要我们进行编写,因为每个string对象中的成员_str都指向堆区的一块空间,当对象销毁时堆区对应的空间并不会自动销毁,为了避免内存泄漏,我们需要使用delete手动释放堆区的空间。
//析构函数
~string()
{delete[] _str; //释放_str指向的空间_str = nullptr; //及时置空,防止非法访问_size = 0; //大小置0_capacity = 0; //容量置0
}
3.迭代器相关函数
string类中的迭代器实际上就是字符指针,只是给字符指针起了一个别名叫iterator而已。
typedef char* iterator;
typedef const char* const_iterator;
注意:不是所有的迭代器都是指针,比如在list中迭代器就是节点
begin函数---返回字符的第一个地址
iterator begin()
{return _str; //返回字符串中第一个字符的地址
}
const_iterator begin()const
{return _str; //返回字符串中第一个字符的const地址
}
end函数---返回字符的最后一个地址(即\0的地址)
iterator end()
{return _str + _size; //返回字符串中最后一个字符的后一个字符的地址
}
const_iterator end()const
{return _str + _size; //返回字符串中最后一个字符的后一个字符的const地址
}
4.容量大小相关的函数
size和capacity
因为string类的成员变量是私有的,我们并不能直接对其进行访问,所以string类设置了size和capacity这两个成员函数,用于获取string对象的大小和容量
size函数用于获取字符串当前的有效长度(不包括’\0’)。
//大小
size_t size()const
{return _size; //返回字符串当前的有效长度
}
capacity用于获取当前的容量
//容量
size_t capacity()const
{return _capacity; //返回字符串当前的容量
}
reverse和resize
reverse--只有当大于当前容量时才会进行扩容,其他不关心
//改变容量,大小不变
void reserve(size_t n)
{if (n > _capacity) //当n大于对象当前容量时才需执行操作{char* tmp = new char[n + 1]; //多开一个空间用于存放'\0'strncpy(tmp, _str, _size + 1); //将对象原本的C字符串拷贝过来(包括'\0')delete[] _str; //释放对象原本的空间_str = tmp; //将新开辟的空间交给_str_capacity = n; //容量跟着改变}
}
注意:代码中使用strncpy进行拷贝对象C字符串而不是strcpy,是为了防止对象的C字符串中含有有效字符’\0’而无法拷贝(strcpy拷贝到第一个’\0’就结束拷贝了)。
reseize
1、当n大于当前的size时,将size扩大到n,扩大的字符为ch,若ch未给出,则默认为’\0’。
2、当n小于当前的size时,将size缩小到n。
//改变大小
void resize(size_t n, char ch = '\0')
{if (n <= _size) //n小于当前size{_size = n; //将size调整为n_str[_size] = '\0'; //在size个字符后放上'\0'}else //n大于当前的size{if (n > _capacity) //判断是否需要扩容{reserve(n); //扩容}for (size_t i = _size; i < n; i++) //将size扩大到n,扩大的字符为ch{_str[i] = ch;}_size = n; //size更新_str[_size] = '\0'; //字符串后面放上'\0',这里如果不放\0后续就会产生错误}
}
empty--判断是否还有元素
//判空
bool empty()
{return _size== 0;
}
5.与插入删除有关的函数
insert函数的作用是在字符串的任意位置插入字符或是字符串。
insert函数用于插入字符时,首先需要判断pos的合法性,若不合法则无法进行操作,紧接着还需判断当前对象能否容纳插入字符后的字符串,若不能则还需调用reserve函数进行扩容。插入字符的过程也是比较简单的,先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后将字符插入字符串即可。
//在pos位置插入字符
string& insert(size_t pos, char ch)
{assert(pos <= _size); //检测下标的合法性if (_size == _capacity) //判断是否需要增容{reserve(_capacity == 0 ? 4 : _capacity * 2); //将容量扩大为原来的两倍}char* end = _str + _size;//将pos位置及其之后的字符向后挪动一位while (end >= _str + pos){*(end + 1) = *(end);end--;}_str[pos] = ch; //pos位置放上指定字符_size++; //size更新return *this;
}
如果要用于插入字符串也是和上述插入一个字符同理
//在pos位置插入字符串
string& insert(size_t pos, const char* str)
{assert(pos <= _size); //检测下标的合法性size_t len = strlen(str); //计算需要插入的字符串的长度(不含'\0')if (len + _size > _capacity) //判断是否需要增容{reserve(len + _size); //增容}char* end = _str + _size;//将pos位置及其之后的字符向后挪动len位while (end >= _str + pos){*(end + len) = *(end);end--;}memcpy(_str + pos, str, len); //pos位置开始放上指定字符串_size += len; //size更新return *this;
}
push_back和push_front
void push_back(char ch)
{insert(_size(),ch);
}void push_front(char ch)
{insert(0,ch);
}
erase函数
erase函数的作用是删除字符串任意位置开始的n个字符。删除字符前也需要判断pos的合法性,进行删除操作的时候分两种情况:
1、pos位置及其之后的有效字符都需要被删除。
这时我们只需在pos位置放上’\0’,然后将对象的size更新即可。
2.删除pos位置的一部分len长度的字符
//删除pos位置开始的len个字符
string& erase(size_t pos, size_t len = npos)
{assert(pos < _size); //检测下标的合法性--下标是0-_size-1,所以删除的位置不能超过_sizesize_t n = _size - pos; //pos位置及其后面的有效字符总数if (len >= n) //说明pos位置及其后面的字符都被删除{_size = pos; //size更新_str[_size] = '\0'; //字符串后面放上'\0'}else //说明pos位置及其后方的有效字符需要保留一部分{strcpy(_str + pos, _str + pos + len); //用需要保留的有效字符覆盖需要删除的有效字符_size -= len; //size更新}return *this;
}
pop_back和pop_front函数
void pop_back()
{erase(_size,1);
}void pop_front()
{erase(0,1);
}
append函数
主要含义就是在尾部追加字符串
//尾插字符串
void append(const char* str)
{insert(_size, str); //在字符串末尾插入字符串str
}
6.访问相关函数
operator[]---实际是一个函数重载
//[]运算符重载(可读可写)---由于是char*的结构,支持下标随机访问
char& operator[](size_t i)
{assert(i < _size); //检测下标的合法性return _str[i]; //返回对应字符
}//只能读
const char& operator[](size_t i)
{assert(i < _size); //检测下标的合法性return _str[i]; //返回对应字符
}
find函数
找到了就返回下标,没找到就返回npos,即-1
//正向查找第一个匹配的字符
size_t find(char ch, size_t pos = 0)
{assert(pos < _size); //检测下标的合法性for (size_t i = pos; i < _size; i++) //从pos位置开始向后寻找目标字符{if (_str[i] == ch){return i; //找到目标字符,返回其下标}}return npos; //没有找到目标字符,返回npos
}
7.总结
string类的大部分重要接口都已经完全的实现了,希望这部分内容能够重点掌握--因为在面试的过程中很有可能考官让你手撕一个string类
下期预告:vector
相关文章:

【c++进阶(二)】STL之string类的模拟实现
💓博主CSDN主页:Am心若依旧💓 ⏩专栏分类c从入门到精通⏪ 🚚代码仓库:青酒余成🚚 🌹关注我🫵带你学习更多c 🔝🔝 1.前言 本章重点 本章主要介绍一些关键接口的模拟实现ÿ…...

PHPStudy(xp 小皮)V8.1.1 通过cmd进入MySQL命令行模式
PHPStudy是一个PHP开发环境集成包,可用在本地电脑或者服务器上,该程序包集成最新的PHP/MySql/Apache/Nginx/Redis/FTP/Composer,一次性安装,无须配置即可使用。MySQL MySQL是一个关系型数据库管理系统,由瑞典 MySQL A…...

php反序列化初步了解
一、定义 序列化(串行化):将变量转换为可保存或传输的字符串的过程(通常是字节流、JSON、XML格式) 反序列比(反串行化):把这个字符串再转化成原始数据结构或对象(原来的…...

Windows系统电脑本地部署AI音乐创作工具并实现无公网IP远程使用
文章目录 前言1. 本地部署2. 使用方法介绍3. 内网穿透工具下载安装4. 配置公网地址5. 配置固定公网地址 前言 本文主要介绍如何在Windows系统电脑上快速本地部署一个文字生成音乐的AI创作工具MusicGPT,并结合cpolar内网穿透工具实现随时随地远程访问使用。 MusicG…...

玩转Linux进度条
准备工作: 一.关于缓冲区 首先,咱们先来一段有意思的代码: #include<stdio.h> #include<unistd.h> int main() {printf("you can see me");sleep(5);} 你可以在你的本地运行一下,这里我告诉大家运行结果…...

真国色码上赞,科技流量双剑合璧,商家获客新纪元开启
在数字化浪潮汹涌的今天,真国色研发团队依托红玉房网络科技公司的雄厚实力,凭借科技领先的核心竞争力,推出了创新性的商家曝光引流工具——码上赞。这款工具借助微信支付与视频号已有功能,为实体商家提供了一种全新的引流获客方式,实现了科技与商业的完美融合。 科技领先,流量黑…...

C++:特殊类设计和四种类型转换
一、特殊类设计 1.1 不能被拷贝的类 拷贝只会放生在两个场景中:拷贝构造函数以及赋值运算符重载,因此想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。 C98: 1、将拷贝构造函数与赋值运算符重载只…...

(南京观海微电子)——屏幕材质及优缺点对比
LED/LCD LCD(Liquid Crystal Ddisplay)即“液晶显示器”,由两块偏光镜、两块薄膜晶体管以及彩色滤光片、光源(荧光灯)、显示面板组成的成像元器件。 LED(Light Emitting Diode)即“发光二极管…...
uniapp uni.showModal 出现点击没有反应
uni.showModal 里面有好些参数 点击后不弹出 是因为 出现了 null 或者undifind 字符 特别是content 里面 title: 提示, cancelColor: #000000, editable: true,//是否显示输入框 content: item.text?item.te…...
Vue3-VueRouter
客户端 vs. 服务端路由 服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时,浏览器会从服务端获得全新的 HTML,然后重新加载整个页面。 然而,在单页面应用中&a…...

【图像处理与机器视觉】频率域滤波
知识铺垫 复数 CRjI 可以看作复平面上的点,则该复数的坐标为(R,I) 欧拉公式 e j θ c o s θ j s i n θ e^{j\theta} cos \theta j sin \theta ejθcosθjsinθ 极坐标系中复数可以表示为: C ∣ C ∣ ( c o s…...

python第五次作业
1.请实现一个装饰器,每次调用函数时,将函数名字以及调用此函数的时间点写入文件中 # 导入datetime模块,用于获取当前时间并格式化输出 import datetime# 定义一个装饰器工厂函数log_funcName_time,它接受一个参数time def log_fu…...
JS面向对象编程
目录 实例对象与new命令this关键字对象的原型和继承Object对象的相关方法浅拷贝和深拷贝严格模式实例对象与new命令 构造函数 构造函数的特点有两个: 函数体内部使用了this关键字,代表了所要生成的对象实例。生成对象的时候,必须使用new命令。var Vehicle...

kotlin1.8.10问题导致gson报错TypeToken type argument must not contain a type variable
书接上回,https://blog.csdn.net/jzlhll123/article/details/139302991。 之前我发现gson报错后: gson在2.11.0给我的kotlin项目代码报错了。 IllegalArgumentException: TypeToken type argument must not contain a type variable 上次解释原因是因为&…...
数据库漫谈-国产数据库
国产数据库突然大量出现,下表列出(按首字母排序 ) AISWare AntDB,亚信科技。 AliSQL,阿里云。 Analyticdb,阿里云。 ArkDB,北京极数云舟科技有限公司。 CynosDB,腾讯云 DM&…...

小白跟做江科大32单片机之光敏传感器控制蜂鸣器
代码部分 1.思路 通过光敏电阻,控制蜂鸣器的发声 2.butter.h代码 #ifndef _BUTTER__H #define _BUTTER__H void butter_Init(void); void butter_on(void); void butter_off(void); #endif 3.butter.c代码 #include "stm32f10x.h" void butter…...

使用 Django Channels 构建实时聊天应用(包含用户认证和消息持久化)
文章目录 准备工作创建 Django 项目创建应用程序配置项目编写 Consumer编写路由创建 URL 路由运行应用用户认证消息持久化显示历史消息结论 Django Channels 是 Django 的一个扩展,允许在 Web 应用中添加实时功能,例如 Websockets、HTTP2 和其他协议。本…...

【Elasticsearch】es基础入门-03.RestClient操作文档
RestClient操作文档 示例: 一.初始化JavaRestClient (一)引入es的RestHighLevelClient依赖 <!--elasticsearch--> <dependency><groupId>org.elasticsearch.client</groupId><artifactId>elasticsearch-rest…...

LeetCode - 二分查找(Binary Search)算法集合(Python)[左右边界|旋转数组|双列表]
欢迎关注我的CSDN:https://spike.blog.csdn.net/ 本文地址:https://spike.blog.csdn.net/article/details/139419653 二分查找,也称为折半查找,是一种在有序数组中查找特定元素的高效算法。其基本原理是将待搜索的区间分成两半&am…...

android睡眠分期图
一、效果图 做医疗类项目,经常会遇到做各种图表,本文做的睡眠分期图。 二、代码 引入用到的库 api joda-time:joda-time:2.10.1 调用代码 /*** 睡眠* 分期*/private SleepChartAdapter mAdapter;private SleepChartAttrs mAttrs;private List<SleepI…...

UE5 学习系列(二)用户操作界面及介绍
这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…...

Docker 离线安装指南
参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性,不同版本的Docker对内核版本有不同要求。例如,Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本,Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

8k长序列建模,蛋白质语言模型Prot42仅利用目标蛋白序列即可生成高亲和力结合剂
蛋白质结合剂(如抗体、抑制肽)在疾病诊断、成像分析及靶向药物递送等关键场景中发挥着不可替代的作用。传统上,高特异性蛋白质结合剂的开发高度依赖噬菌体展示、定向进化等实验技术,但这类方法普遍面临资源消耗巨大、研发周期冗长…...

【JVM】- 内存结构
引言 JVM:Java Virtual Machine 定义:Java虚拟机,Java二进制字节码的运行环境好处: 一次编写,到处运行自动内存管理,垃圾回收的功能数组下标越界检查(会抛异常,不会覆盖到其他代码…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...
基于数字孪生的水厂可视化平台建设:架构与实践
分享大纲: 1、数字孪生水厂可视化平台建设背景 2、数字孪生水厂可视化平台建设架构 3、数字孪生水厂可视化平台建设成效 近几年,数字孪生水厂的建设开展的如火如荼。作为提升水厂管理效率、优化资源的调度手段,基于数字孪生的水厂可视化平台的…...

多模态大语言模型arxiv论文略读(108)
CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题:CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者:Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

蓝桥杯3498 01串的熵
问题描述 对于一个长度为 23333333的 01 串, 如果其信息熵为 11625907.5798, 且 0 出现次数比 1 少, 那么这个 01 串中 0 出现了多少次? #include<iostream> #include<cmath> using namespace std;int n 23333333;int main() {//枚举 0 出现的次数//因…...

Docker 本地安装 mysql 数据库
Docker: Accelerated Container Application Development 下载对应操作系统版本的 docker ;并安装。 基础操作不再赘述。 打开 macOS 终端,开始 docker 安装mysql之旅 第一步 docker search mysql 》〉docker search mysql NAME DE…...

Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...