【C++】string类模拟实现上篇(附完整源码)

目录

      • 前言
      • 1. string的基本结构
      • 2. 构造函数、析构函数
        • 2.1 构造函数的实现
          • 2.1.1带参构造函数
        • 2.2析构函数
        • 2.3无参构造函数
        • 2.4无参和带参构造函数合并
      • 3. string的遍历
        • 3.1 operator[ ]
        • 3.2迭代器模拟实现 (简单实现)
        • 3.3 const迭代器模拟实现
      • 4. 数据的增删查改
        • 4.1 reserve
        • 4.2 push_back和append
        • 4.3 +=
        • 4.4 insert
        • 4.5 erase
      • 4.6 find
        • 4.7 substr
      • 5. 拷贝构造
        • 5.1 浅拷贝默认拷贝构造
        • 5.2 深拷贝拷贝构造函数
      • 6. 源码(上部分)
        • 6.1 string.h
        • 6.2 test.cpp
      • 7. 总结

前言

在上一篇文章中,我们详细介绍了string类一些常用接口的使用,那这篇文章,我们将对string进行一个模拟实现,帮助大家理解的更加深刻。

1. string的基本结构

在上篇文章中我们了解:

string的底层其实就是一个支持动态增长的字符数组。那确定它的结构,接下来我们就开始模拟实现它。

首先新建一个头文件string.h,定义一个string类:

class string
{public ://成员函数private :char*  _str;size_t _size;size_t _capacity;
};

这里string类的三个成员变量,一个字符指针_str指向开辟的动态数组,_size标识有效数据个数,_capacity记录容量的大小(不包含’\0’)。

但是因为标准库里已经有string类,为了避免冲突,我们需要定义一个命名空间,把我们自己实现的string类放到自己的命名空间里面

namespace w
{class string
{public ://成员函数private :char*  _str;size_t _size;size_t _capacity;};} 

2. 构造函数、析构函数

2.1 构造函数的实现

2.1.1带参构造函数

首先我们来模拟实现一个带参构造函数:

我们知道标准库里string类的构造函数有很多,这里我们只模拟实现最常用的:
在这里插入图片描述

在之前的文章中我们提到尽量使用初始化列表进行初始化,我们可以这样写:
在这里插入图片描述
但是这里你会发现程序报错了,因为如果像上图一样初始化,首先涉及到权限放大的问题(之前文章有讲过)char* strconst修饰,不能被修改,但是赋给_str_strchar* 类型的,可以修改。其次用常量字符串去初始化也不能被修改。

那怎么办呢? 我们这里不直接传参而是开空间,用strcpy去进行拷贝:

      string(const char* str):_str(new char[strlen(str)+1]),_size(strlen(str)),_capacity(strlen(str)){strcpy(_str, str);}

顺便这里我们提供一个接口用来返回字符串:

 const char* c_str(){return _str;}

我们在创建一个test.cpp文件用来测试我们写的接口:
在这里插入图片描述

2.2析构函数

这里我们直接顺便给出析构函数:

~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}

2.3无参构造函数

我们有的时候还会遇到这样的场景:
在这里插入图片描述
所以这里需要我们去实现一个无参的构造函数。

假设这里的无参构造函数我们这样实现:
在这里插入图片描述

那这样真的可行吗?
如果这里_str传空指针那么在刚刚实现的c_str函数就会返回空程序,程序会崩溃。并且在标准库里的c_str接口即使传空也是会有返回值的。

那这里应该怎么办呢? 我们可以这样写:

  string():_str(new char[1]),_size(0),_capacity(0){_str[0] = '\0';}

这里我们给_str开辟一个空间,然后给这块空间给上'\0'。这样就不会出现上面的问题了

2.4无参和带参构造函数合并

我们之前讲过无参和带参的可以用全缺省

我们来看几种写法:
在这里插入图片描述
能这样写吗?答案是肯定不能这样写类型就不匹配,一个是字符一个是字符串
在这里插入图片描述
能这样写吗? 答案是肯定不能。这样写strlen里的str就是空串了。

其实应该这样写:
在这里插入图片描述
这里我们直接给一个空串,常量字符串末尾是默认有"\0"

3. string的遍历

3.1 operator[ ]

我们知道在标准库中可以通过下标去访问字符串中的某一个字符,下面我们来实现对[]的重载。

首先我们需要实现size()接口:
在这里插入图片描述

接下来我们来实现一下[]的重载:
在这里插入图片描述
在这里插入图片描述
这里我们实现了两个版本普通版本对应普通对象,const版本对应const对象,且这两个函数构成函数重载

下面我们来验证一下:
在这里插入图片描述

3.2迭代器模拟实现 (简单实现)

除了[]可以遍历访问string对象,我们还可以用迭代器进行访问。

那迭代器我们说了大家可以理解成一个像指针一样的东西,但是不一定是指针。
我们最开始介绍了STL有好几个版本,不同的版本实现可能是不一样的。
那其实vs下string的迭代器呢就不是使用指针实现的,而G++下使用的SGI版本是指针实现的。
那这里我们模拟实现就使用指针来实现:
在这里插入图片描述

下面我们来验证一下:
在这里插入图片描述
在这里插入图片描述

同样的我们还可以使用范围for进行遍历:
在这里插入图片描述
范围for的底层就是用的迭代器。
大家可以理解成范围for的语法其实就跟我们之前学过的宏有点类似,它会被替换成迭代器,相当于把*it赋值给ch。范围for的底层就是无脑替换。

3.3 const迭代器模拟实现

这里我们再实现const版本给const对象使用:
在这里插入图片描述

4. 数据的增删查改

首先我们来实现一下push_back()append().这两个都是插入数据,既然插入数据那我们就必须考虑扩容的问题。
在这里插入图片描述
那这里如果扩容的话,我们一次扩多少呢?
对于push_back来说一次扩二倍没问题,但是append一次扩二倍有可能是不行的。
为什么?
如果当前的容量是10,现在追加一个长度为25的字符串,扩容到原来的两倍才
20,也是不够用的。

那这里我们通过string的另一个接口reserve,它可以改变容量为我们指定的大小,帮助我们扩容。
下面我们就先来实现一下reserve。

4.1 reserve

我们先来看一下reserve怎么实现:
在这里插入图片描述
这里当参数n的值小于_capacity,如果不加这个if判断这里就会缩容。但是我们知道,库里的接口是不会缩容的。所以需要加上这个条件判断。

4.2 push_back和append

那接下来有了reserve我们继续来实现push_backappend

在这里插入图片描述
push_back这里我们直接选择两倍扩。

在这里插入图片描述
这里append最少扩容到_size + len.

下面我们来实现一下:
在这里插入图片描述

4.3 +=

我们虽然有push_back和append但是我们更喜欢用重载的+=。当然+=的底层也是可以用push_back和append实现的。
在这里插入图片描述

下面我们来实现一下:
在这里插入图片描述

4.4 insert

对于insert我们主要实现库里的这两个版本:
在这里插入图片描述
在这里插入图片描述

首先我们来实现一下在pos位置插入n个字符:
逻辑其实是比较简单的。首先判断一下,是否需要扩容,然后就插入数据,如果往中间插就需要挪动数据。
在这里插入图片描述

这样写有没有问题呢? 我们来测试一下:
在这里插入图片描述
好像没什么问题啊。真的没问题吗?

我们来看一种特殊情况:当pos = 0 时插入数据:
在这里插入图片描述

程序这里挂了。那为什么呢?
这里当pos = 0时,end等于0时还会进入循环,end再- -会变成多少? 是-1吗?
在这里插入图片描述
这里end的类型是szie_t,无符号整型,所以end为0后再- -并不是-1,而是整型最大值,发生越界,循环也没正常结束,所以程序崩了。

那怎么解决呢?把end改成int可行吗?

这里也是不可行的。end和pos比较,end变成int,但是pos是size_t类型,这里是会发生整型提升(C语言知识)那我们应该如何解决呢?

这里解决方法有很多,我们采用其中一种利用我们之前文章中提到的npos解决:
在这里插入图片描述

在这里插入图片描述

我们再来测试一下:
在这里插入图片描述

刚才是插入一个字符,现在我们再来实现插入字符串的。那么逻辑和上面其实是一样的。只不过上面我们只需要挪出n个空间就可以了,那这里我们需要挪动数据腾出strlen(str)个空间。
在这里插入图片描述

下面我们来测试一下:
在这里插入图片描述

4.5 erase

那么接下来我们来实现一下erase,从pos位置删除len个字符:
在这里插入图片描述

对于erase首先第一种情况就是pos+len小于字符串的长度,那我们需要把pos位置开始的后len个字符删掉,但是仍然保留后续字符。那这里就是挪动后面的数据,把需要删除的覆盖掉就行
那其它情况就是len比较大,pos+len直接大于等于字符串的长度,那就把pos后面的全部删掉。或者没有传pos这个参数,缺省值npos,那也要把后面的全删,所以这两种情况可以统一处理。这里只需要把pos位置给成“\0”就行了。
在这里插入图片描述

我们来测试一下:
在这里插入图片描述

当然为了和标准库里的一致我们这里也使用引用返回:
在这里插入图片描述

4.6 find

下面我们来实现一下find。find的实现其实很简单,遍历去找,找到了就返回下标,找不到就返回npos
在这里插入图片描述

当然find还支持从pos位置开始查找一个字符串:在这里我们复用C语言中的strstr去查找。在这里插入图片描述

下面我们来测试一下:
在这里插入图片描述

4.7 substr

下面我们再来实现一下substr。它的逻辑也是很简单的。

这里稍微需要注意的是我们需要条件判断当截取的字串足够长,我们截取的长度就是pos位置一直到字符串的末尾。
在这里插入图片描述

5. 拷贝构造

我们现在先来写一段这样的代码:
在这里插入图片描述
这里有一个拷贝构造,s2是s1拷贝构造而来的。

5.1 浅拷贝默认拷贝构造

在之前类和对象的文章中,我们知道,拷贝构造函数我们自己不写编译器是会默认生成的,这里我们直接运行上面的代码:
在这里插入图片描述

这里程序出错发生了一个经典的浅拷贝的问题。在之前的文章中我们也有讲过若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数拷贝对象 按内存存储字节序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,就会出现问题。

5.2 深拷贝拷贝构造函数

这里就需要我们自己去实现拷贝构造函数,完成深拷贝:
在这里插入图片描述
在这里插入图片描述
下面我们来测试一下:
在这里插入图片描述

6. 源码(上部分)

6.1 string.h

#include <iostream>
using namespace std;
namespace w
{class string
{public :typedef char* iterator;typedef const char* const_iterator;iterator begin(){return _str;}iterator end(){return _str + _size;}const_iterator begin() const{return _str;}const_iterator end() const{return _str + _size;}string(const char* str = ""):_str(new char[strlen(str)+1]),_size(strlen(str)),_capacity(strlen(str)){strcpy(_str, str);}string(const string& s){_str = new char[s._capacity + 1];strcpy(_str, s._str);_size = s._size;_capacity = s._capacity;}~string(){delete[] _str;_str = nullptr;_size = _capacity = 0;}const char* c_str() const{return _str;}size_t size() const{return _size;}char& operator[](size_t pos){assert(pos < _size);return _str[pos];}const char& operator[](size_t pos) const{assert(pos < _size);return _str[pos];}void reserve(size_t n){if (n > _capacity){char* tmp = new char[n + 1];strcpy(tmp, _str);delete[] _str;_str = tmp;_capacity = n;}}void push_back(char ch){if (_size == _capacity){// 2倍扩容reserve(_capacity == 0 ? 4 : _capacity * 2);}_str[_size] = ch;++_size;_str[_size] = '\0';}void append(const char* str){size_t len = strlen(str);if (_size + len > _capacity){// 至少扩容到_size + lenreserve(_size+len);}strcpy(_str + _size, str);_size += len;}string& operator+=(char ch){push_back(ch);return *this;}string& operator+=(const char* str){append(str);return *this;}void insert(size_t pos, size_t n, char ch){assert(pos <= _size);if (_size +n > _capacity){// 至少扩容到_size + lenreserve(_size + n);}// 添加注释最好size_t end = _size;while (end >= pos && end != npos){_str[end + n] = _str[end];--end;}for (size_t i = 0; i < n; i++){_str[pos + i] = ch;}_size += n;}void insert(size_t pos, const char* str){assert(pos <= _size);size_t len = strlen(str);if (_size + len > _capacity){// 至少扩容到_size + lenreserve(_size + len);}// 添加注释最好size_t end = _size;while (end >= pos && end != npos){_str[end + len] = _str[end];--end;}for (size_t i = 0; i < len; i++){_str[pos + i] = str[i];}_size += len;}string& erase(size_t pos, size_t len = npos){assert(pos <= _size);if (len == npos || pos + len >= _size){_str[pos] = '\0';_size = pos;_str[_size] = '\0';}else{size_t end = pos + len;while (end <= _size){_str[pos++] = _str[end++];}_size -= len;}return *this;}size_t find(char ch, size_t pos = 0){assert(pos < _size);for (size_t i = pos; i < _size; i++){if (_str[i] == ch){return i;}}return npos;}size_t find(const char* str , size_t pos = 0){assert(pos < _size);const char* ptr = strstr(_str + pos, str);if (ptr){return ptr - _str;}else{return npos;}}string substr(size_t pos = 0, size_t len = npos){assert(pos < _size);size_t n = len;if (len == npos || pos + len > _size){n = _size - pos;}string tmp;tmp.reserve(n);for (size_t i = pos; i < pos + n; i++){tmp += _str[i];}return tmp;}private :char*  _str;size_t _size;size_t _capacity;public:const static size_t npos;};const size_t string::npos = -1;
} 

6.2 test.cpp

#include "Mystring.h"void test_string1()
{w ::string s1("hello world");cout << s1.c_str() << endl;for (size_t i = 0; i < s1.size(); i++){cout << s1[i] << " ";}cout << endl;w ::string::iterator it = s1.begin();while (it != s1.end()){cout << *it << " ";++it;}cout <<endl;for (auto ch : s1){cout << ch <<" ";}cout <<endl;
}void test_string2()
{w::string s1("hello world");cout << s1.c_str() << endl;s1.push_back(' ');s1.push_back('#');s1.append("hello");cout << s1.c_str() << endl;w::string s2("hello world");cout << s2.c_str() << endl;s2 += ' ';s2 += '#';s2 += "hello code";cout << s2.c_str() << endl;}void test_string3()
{w::string s1("helloworld");cout << s1.c_str() << endl;s1.insert(5, 3, '#');cout << s1.c_str() << endl;s1.insert(0, 3, '#');cout << s1.c_str() << endl;w::string s2("helloworld");s2.insert(5, "%%%%%");cout << s2.c_str() << endl;}void test_string4()
{w::string s1("helloworld");cout << s1.c_str() << endl;s1.erase(5, 3);cout << s1.c_str() << endl;s1.erase(5, 30);cout << s1.c_str() << endl;s1.erase(2);cout << s1.c_str() << endl;
}void test_string5()
{w::string s1("helloworld");cout << s1.find('w',2) << endl;}void test_string6()
{w::string s1("hello world");w::string s2(s1);cout << s1.c_str() << endl;cout << s2.c_str() << endl;}int main()
{test_string6();return 0;
}

7. 总结

文章篇幅有限,剩余内容将在下篇进行讲解。

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

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

相关文章

基于TensorFlow 2.3.0 的手势识别系统设计

一、开发环境 Windows 10PyCharm 2021.3.2Python 3.7TensorFlow 2.3.0 二、制作数据集&#xff0c;作者使用了10个类别的手势数集据 三、开始训练模型&#xff0c;作者使用自己开发的软件进行训练模型&#xff0c;方便快捷。软件介绍及下载地址&#xff1a; 手把手教你使用T…

原生js之dom表单改变和鼠标常用事件

那么好,本次我们聊聊表单改变时如何利用onchange方法来触发input改变事件以及鼠标常用的滑入滑出,点击down和点击up事件. 关于onchange方法 onchange方法在鼠标输入完后点击任何非输入框位置时触发.触发时即可改变原有输入框的值. out 、leave、over、down、up鼠标方法 当用…

KMP超高效匹配算法

简介&#xff1a; KMP算法是一种改进的字符串匹配算法&#xff0c;其中&#xff0c;KMP算法的运用核心是利用匹配失败后的信息&#xff0c;最大进度的减少模式串与目标串的匹配次数以达到快速匹配的效果。算法与暴力求解的改进在于每当一趟匹配过程中出现的字符比较不相等时&am…

Django创建应用、ORM的进阶使用及模型类数据库迁移

1 Django项目创建第一个应用 Django 项目就是基于 Django 框架开发的 Web 应用&#xff0c;它包含了一组配置和多个应用&#xff0c;我们把应用称之为 App&#xff0c;在前文中对它也做了相应的介绍&#xff0c;比如 auth、admin&#xff0c;它们都属于 APP。 一个 App 就是一…

postman token 请求头添加

思路&#xff1a; 1、登录成功后将 得到的token设置为集合变量 2、在需要携带Authorization的请求头上使用该集合变量 关键代码 const responseData pm.response.json(); if(responseData.code 1) {// 获取tokenconst {data:{token}} responseData// 设置为集合变量pm.colle…

C# 实现电子签名

本项目基于Emgu.CV&#xff08;C#下OpenCv的封装&#xff09;开发的&#xff0c;编译器最新版Vs2022&#xff0c;编译环境x86 直接看效果图 1.主页面 2.我们先看手写的方式&#xff1a; 点击确认就到主界面&#xff0c;如下 &#xff1a; 点击自动适配-&#xff0c;再点击生成…

《C++设计模式》——创建型

前言 创建型为了创建东西才是有用的&#xff0c;创建型设计模式使用的场景&#xff1a; 1、创建一个东西&#xff1b; 2、可重复利用&#xff1b; 3、灵活性高&#xff0c;代码可因地制宜。 Factory Method(工厂模式) 工厂模式将目的将创建对象的具体过程屏蔽隔离起来&#…

go channel实践与源码探索(初始化、发送消息、接收消息、关闭)

文章目录 概要一、并发编程1.1、Actor模型1.2、CSP模型 二、Go Channel实践三、源码分析3.1、初始化3.2、发送消息3.3、接收消息3.4、关闭通道 总结 概要 通道&#xff08;Channel&#xff09;是Go语言提供的协程之间通信与同步的方式。我们知道在并发编程&#xff08;多进程、…

时序分解 | MATLAB实现北方苍鹰优化算法NGO优化VMD信号分量可视化

时序分解 | MATLAB实现北方苍鹰优化算法NGO优化VMD信号分量可视化 目录 时序分解 | MATLAB实现北方苍鹰优化算法NGO优化VMD信号分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 北方苍鹰优化算法NGO优化VMD&#xff0c;对其分解层数&#xff0c;惩罚因子数做优化…

基于Python开发的玛丽大冒险小游戏(源码+可执行程序exe文件+程序配置说明书+程序使用说明书)

一、项目简介 本项目是一套基于Python开发的玛丽冒险小游戏程序&#xff0c;主要针对计算机相关专业的正在做毕设的学生与需要项目实战练习的Python学习者。 包含&#xff1a;项目源码、项目文档等&#xff0c;该项目附带全部源码可作为毕设使用。 项目都经过严格调试&#xf…

Alibaba(商品详情)API接口

为了进行电商平台 的API开发&#xff0c;首先我们需要做下面几件事情。 1&#xff09;开发者注册一个账号 2&#xff09;然后为每个alibaba应用注册一个应用程序键&#xff08;App Key) 。 3&#xff09;下载alibaba API的SDK并掌握基本的API基础知识和调用 4&#xff09;利…

管理类联考——数学——汇总篇——知识点突破——应用题——交叉比例法/杠杆原理

读书笔记 甲有&#xff1a;x个a&#xff0c;乙有&#xff1a;y个b&#xff0c;甲乙的平均值为c&#xff0c;根据总数相等&#xff0c;得&#xff1a;axbyc(xy)&#xff0c;即ax-cxcy-by&#xff0c;则 x y c − b a − c \frac{x}{y}\frac{c-b}{a-c} yx​a−cc−b​ &#…

OpenCV(二十五):边缘检测(一)

目录 1.边缘检测原理 2.Sobel算子边缘检测 3.Scharr算子边缘检测 4.两种算子的生成getDerivKernels() 1.边缘检测原理 其原理是基于图像中灰度值的变化来捕捉图像中的边界和轮廓。梯度则表示了图像中像素强度变化的强弱和方向。 所以沿梯度方向找到有最大梯度值的像素&…

17-数据结构-查找-(顺序、折半、分块)

简介&#xff1a;查找&#xff0c;顾名思义&#xff0c;是我们处理数据时常用的操作之一。大概就是我们从表格中去搜索我们想要的东西&#xff0c;这个表格&#xff0c;就是所谓的查找表&#xff08;存储数据的表&#xff09;。而我们怎么设计查找&#xff0c;才可以让计算机更…

sqli-labs关卡之一(两种做法)

目录 一、布尔盲注(bool注入) 二、时间盲注(sleep注入) 一、布尔盲注(bool注入) 页面没有报错和回显信息&#xff0c;只会返回正常或者不正常的信息&#xff0c;这时候就可以用布尔盲注 布尔盲注原理是先将你查询结果的第一个字符转换为ascii码&#xff0c;再与后面的数字比较…

Redis带你深入学习数据类型set

目录 1、set 2、set相关命令 2.1、添加元素 sadd 2.2、获取元素 smembers 2.3、判断元素是否存在 sismember 2.4、获取set中元素数量 scard 2.5、删除元素spop、srem 2.6、移动元素smove 2.7、集合中相关命令&#xff1a;sinter、sinterstore、sunion、sunionstore、s…

mac电脑安装paste教程以及重新安装软件后不能使用解决方法

问题背景 mac电脑安装paste教程以及重新安装软件后不能使用解决方法。 mac电脑安装paste失败&#xff0c;安装好后还是无法使用&#xff0c;paste显示还是历史粘贴信息&#xff0c;导致无法使用。新 copy的内容也无法进入历史粘贴版里面。 笔者电脑配置信息&#xff1a;MacB…

【算法与数据结构】501、LeetCode二叉搜索树中的众数

文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引&#xff0c;可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析&#xff1a;根据前面几篇文章98、LeetCode验证二叉搜索树、530、LeetCode二叉搜索树的最小绝对差。我们知道二叉搜…

Boost搜索引擎

项目背景 先说一下什么是搜索引擎,很简单,就是我们平常使用的百度,我们把自己想要所有的内容输入进去,百度给我们返回相关的内容.百度一般给我们返回哪些内容呢?这里很简单,我们先来看一下. 搜索引擎基本原理 这里我们简单的说一下我们的搜索引擎的基本原理. 我们给服务器发…

Selenium - Tracy 小笔记2

selenium本身是一个自动化测试工具。 它可以让python代码调用浏览器。并获取到浏览器中加们可以利用selenium提供的各项功能。帮助我们完成数据的抓取。它容易被网站识别到&#xff0c;所以有些网站爬不到。 它没有逻辑&#xff0c;只有相应的函数&#xff0c;直接搜索即可 …