【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 

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

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

相关文章

0603《哎选》已经稳定运行2年

0603《哎选》已经稳定运行2年 0603《哎选》已经稳定运行2年 介绍 2022年6月3日经过一年的努力&#xff0c;优雅草蜻蜓G系统原生版诞生&#xff0c;本产品应用于《哎选》&#xff0c;经过2年的运营不断的更新迭代&#xff0c;目前产品已经有了一定的用户量&#xff0c;本产品…

Spark 3.5.1 升级 Java 17 异常 cannot access class sun.nio.ch.DirectBuffer

异常说明 使用Spark 3.5.1 升级到Java17的时候会有一个异常&#xff0c;异常如下 SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder". SLF4J: Defaulting to no-operation (NOP) logger implementation SLF4J: See http://www.slf4j.org/codes.htm…

离轴磁编案例分享 - 机器人关机模组

客户产品 六轴协作机器人产品 关机模组 关机模组内部结构 项目介绍 客户需求: 需要离轴&#xff0c;优点&#xff1a;可以中空走线&#xff0c;方便线缆从机器人中间穿过去&#xff0c;可以更好得保护好线缆&#xff0c;不需要把线漏在外面&#xff0c;影响使用和产品寿命。目…

最适合上班族和宝妈的兼职副业,一天500多,小众副业项目

近年来&#xff0c;地方特色小吃逐渐受到人们的热烈追捧&#xff0c;尤其是在直播的助力下&#xff0c;许多地方的特色小吃得以走进大众视野&#xff0c;吸引了大量流量和人气。因此&#xff0c;有很大一部分商家和创业者看准了这一商机&#xff0c;纷纷投身于地方特色小吃的制…

怎么把多种内容做成二维码?扫码展现多种内容的制作方法

现在很多的场景下都有不同类型的二维码&#xff0c;用来承载内容为用户提供内容展示&#xff0c;比如图片、视频、文字、文件、地图等等内容&#xff0c;都可以组合起来通过扫码的方式在手机上展示。那么如何制作组合内容的二维码相信有很多的小伙伴都非常的感兴趣。 其实二维…

众汇:外汇狙击指标如何使用?

对于投资者来说&#xff0c;我们各位交易的目的是什么?WeTrade众汇认为那就是盈利。所以来说有一个指标对各位投资者来说那是相当有帮助的。这是因为对于交易者而言&#xff0c;利用这些指标可以快速识别盈利的买卖时机。当我们选择一个指标之后&#xff0c;深入了解其适用范围…

「布道师系列文章」众安保险王凯解析 Kafka 网络通信

作者&#xff5c;众安保险基础平台 Java 开发专家王凯 引言 今天给大家带来的是 Kafka 网路通信主要流程的解析&#xff08;基于 Apache Kafka 3.7[2]&#xff09;。同时引申分析了业界当前较火的AutoMQ基于Kafka在网络通信层面的优化和提升。 01 如何构建一个基本的请求…

学习笔记(一)——Langchain基本操作与函数

学习笔记(一)——Langchain基本操作与函数 目录 学习笔记(一)——Langchain基本操作与函数基本初始化配置LangsmithLanguage Models 基础指令传递信息OutputParsers 输出解析器chain 链Prompt Templates 提示模板Message History 消息历史记录Managing Conversation History 管…

XL7005A SOP-8 0.4A1.25-20V 150KHz降压直流转换器芯片

XL7005A作为一款高性能的降压型电源管理芯片&#xff0c;在智能家居中有着广泛的应用。以下是一些具体的案例&#xff1a; 1. 智能灯具&#xff1a;XL7005A可用于控制LED灯的电源&#xff0c;提供稳定高效的电源支持&#xff0c;确保灯具亮度稳定且无频闪&#xff0c;提高用户体…

springboot从2.7.2 升级到 3.3.0

文章目录 概要准备报错调整小结后记 概要 时代在进步&#xff0c;springboot已经来到了3.3.0 , 于是我们也打算升级下sbvadmin到3.3&#xff0c; jdk使用21的版本&#xff0c;下面是升级过程中碰到的一些问题&#xff0c;问题不大。 2.7.2 -> 3.3.0 准备 下载jdk21&#…

windows的软件修改图标

要修改一个可执行文件&#xff08;.exe&#xff09;的图标&#xff0c;你可以使用 Resource Hacker 这样的工具。Resource Hacker 是一个免费的资源编辑器&#xff0c;可以用于修改和编辑 Windows 可执行文件中的资源。 以下是一个简单的步骤来修改一个 .exe 文件的图标&#x…

shell脚本 字符串拼接变量赋值失效

问题现象&#xff1a; 代码如下&#xff1a; 执行结果&#xff1a; 可以看到data_dir属性是有值的&#xff0c;但是做字符串拼接变量赋值失效了很奇怪 怀疑赋值哪里写错了 问题分析&#xff1a; 1. 还是觉得赋值没有问题&#xff0c;手动显式赋值再执行下 执行结果&#…

职场如同“染缸”,老板只是给你个平台,染的好坏,全凭运气!

无论哪个单位&#xff0c;在职场大染缸里总有那么一拨同事是你喜欢的&#xff0c;也有那么一拨同事是不痛不痒的&#xff0c;还有那么一拨同事却是你怎么看都觉得不顺眼的。“不顺眼”的定义很宽泛&#xff0c;可能是他曾经的一些言论触及了你的道德底线&#xff0c;可能是他的…

C++数据结构之:堆Heap

摘要&#xff1a; it人员无论是使用哪种高级语言开发东东&#xff0c;想要更高效有层次的开发程序的话都躲不开三件套&#xff1a;数据结构&#xff0c;算法和设计模式。数据结构是相互之间存在一种或多种特定关系的数据元素的集合&#xff0c;即带“结构”的数据元素的集合&am…

【Qt】win10,QTableWidget表头下无分隔线的问题

1. 现象 2. 原因 win10系统的UI样式默认是这样的。 3. 解决 - 方法1 //横向表头ui->table->horizontalHeader()->setStyleSheet("QHeaderView::section{""border-top:0px solid #E5E5E5;""border-left:0px solid #E5E5E5;""bord…

Matlab|【重磅】配电网故障重构/孤岛划分

目录 1 主要内容 1.1 背景 1.2 流程图 2 部分代码 3 程序结果 4 下载链接 1 主要内容 程序主要复现《基于GA_BFGS算法的配电网故障恢复性重构研究_郑海广》&#xff0c;采用matlab编程软件实现&#xff0c;依据网络结构和DG供电方式对配电网进行孤岛划分&#xff0c;将含…

【算法训练记录——Day24】

Day24——回溯算法Ⅰ 77.组合 今日内容&#xff1a; ● 理论基础 ● 77. 组合 理论&#xff1a;代码随想录 77.组合 思路&#xff1a;k层for循环&#xff0c;不会 回溯&#xff0c;将组合问题抽象成n叉树&#xff0c;for循环控制宽度&#xff0c;递归的深度控制二叉树的深度 …

CSS学习笔记之高级教程(五)

23、CSS 媒体查询 - 实例 /* 如果屏幕尺寸超过 600 像素&#xff0c;把 <div> 的字体大小设置为 80 像素 */ media screen and (min-width: 600px) {div.example {font-size: 80px;} }/* 如果屏幕大小为 600px 或更小&#xff0c;把 <div> 的字体大小设置为 30px …

程序员的五大职业素养,你知道吗?

程序员职业生涯的挑战与机遇 在当今这个科技日新月异的时代&#xff0c;程序员作为技术行业的中坚力量&#xff0c;其职业生涯无疑充满了无数挑战与机遇。技术的快速迭代要求他们必须不断学习新知识、掌握新技能&#xff0c;以跟上时代的步伐。同时&#xff0c;云计算、人工智…

学习经验分享篇(1)——怎样将示波器数据(.CSV数据)导入Matlab/Simulink中并进行FFT分析(电机控制/电力电子方向必备技能)

最近比较忙&#xff0c;没怎么更新&#xff0c;后续打算不断出一些学习贴。 1.为什么要出这篇文章&#xff1f; &#xff08;1&#xff09;我当时第一次导示波器数据进入Matlab里面的时候&#xff0c;一直疯狂报错&#xff0c;搞了好久。 &#xff08;2&#xff09;好多同学现…