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:代码小豪的代码仓库

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

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

相关文章

【已解决】 ‘Conv2d’ object has no attribute ‘register_full_backward_hook’

&#x1f60e; 作者介绍&#xff1a;我是程序员行者孙&#xff0c;一个热爱分享技术的制能工人。计算机本硕&#xff0c;人工制能研究生。公众号&#xff1a;AI Sun&#xff0c;视频号&#xff1a;AI-行者Sun &#x1f388; 本文专栏&#xff1a;本文收录于《AI实战中的各种bug…

(四十二)第 6 章 树和二叉树(树的二叉链表(孩子-兄弟)存储)

1. 背景说明 2. 示例代码 1) errorRecord.h // 记录错误宏定义头文件#ifndef ERROR_RECORD_H #define ERROR_RECORD_H#include <stdio.h> #include <string.h> #include <stdint.h>// 从文件路径中提取文件名 #define FILE_NAME(X) strrchr(X, \\) ? strrch…

centos7中如何优雅的动态切换jdk版本?

在 CentOS 7 中动态切换 JDK 版本可以通过多种方法实现&#xff0c;其中最常见的方法是使用 alternatives 命令&#xff0c;这是 CentOS 和其他基于 Red Hat 的系统中用于管理多个软件版本的标准工具。下面我会详细介绍如何使用 alternatives 命令来切换 JDK 版本。 步骤 1: 安…

【JS面试题】this

this取什么值&#xff0c;是在函数执行的时候确定的&#xff0c;不是在函数定义的时候确定的&#xff01; this的6种使用场景&#xff1a; ① 在普通函数中使用&#xff1a;返回window对象 ② 使用call apply bind 调用&#xff1a;绑定的是哪个对象就返回哪个对象 ③ 在对象…

怎样计算Excel一列数值中十位数为5的个数?

有一列数字&#xff0c;可能正数也可能是负数&#xff0c;有可能有小数&#xff0c;要怎么计算这列数字中十位数为5的数量有多少个&#xff1f; 一、按示例情况&#xff0c;数字均为整数 公式如下&#xff1a; SUM(--(MID(A1:A6,LEN(A1:A6)-1,1)"5")) 数组公式&a…

一台linux通过另一台linux访问互联网-TinyProxy

参考&#xff1a; https://blog.csdn.net/weixin_41831919/article/details/113061317https://www.yuncongz.com/archives/1.htmlhttps://blog.csdn.net/aoc68397/article/details/101893369 环境&#xff1a;ubuntu 18.04 机器1: IP 219.216.65.252 (可以访问外网) 机器2: IP…

【C++语言】动态内存管理

文章目录 前言内存管理数据存储位置C语言动态内存管理方式C动态内存管理方式&#xff1a;new/deleteoperator new与operator delete函数new和delete的实现原理定位new表达式&#xff08;了解&#xff09;常见面试题 总结C语言系列学习目录 前言 本章要介绍的是动态内存管理&am…

学习神经网络基础架构

今日学习了解了常见的几种神经网络基础架构。 1.卷积神经网络 卷积神经网络CNN是一种人工神经网络&#xff0c;旨在处理和分析具有网格状拓扑结构的数据&#xff0c;如图像和视频。将 CNN 想象成一个多层过滤器&#xff0c;可处理图像以提取有意义的特征并进行推理预测。 想…

PG数据文件和块管理与Oracle比较

之前有说过PG数据库中的对象oid与数据文件一一对应&#xff0c;创建的数据库如果没有指定表空间&#xff0c;则会默认放在默认表空间中&#xff0c;例如&#xff1a; 1.对象OID与数据文件对应关系 Oracle的逻辑与物理对应关系如下&#xff1a; 两种结果相比较而言&#xff1a; …

赋能业务全球化,明道云HAP通过亚马逊云科技 FTR认证

近日&#xff0c;明道云作为融合多元能力的超级应用平台&#xff0c;成功通过了AWS&#xff08;Amazon Web Service&#xff09;的FTR&#xff08;Foundational Technical Review&#xff09;认证。FTR是亚马逊云科技为合作伙伴解决方案提供的一项全面技术审核机制&#xff0c;…

Python 操作数据库

十、Python3 操作数据库 1、Python3 操作 MySQL 1、基本介绍 Python3 操作 MySQL 数据库 可以使用的模块是 pymysql 和 MySQLdb。 这个两个模块都是通过自己的 API 执行原生的 SQL 语句实现的。 MySQLdb 是最早出现的一个操作 MySQL 数据库的模块&#xff0c;核心由C语言编…

【NodeMCU实时天气时钟温湿度项目 7】和风天气API返回JSON数据信息的解压缩实现——ArduinoUZlib功能库

今天是第七专题&#xff0c;主要内容是&#xff1a;导入ArduinoUZlib功能库&#xff0c;借助该库把从【和风天气】官网返回的经过Gzip压缩的JSON数据&#xff0c;进行解压缩和t解析&#xff0c;在串口监视器上输出解析后的JSON信息。 如您需要了解其它专题的内容&#xff0c;请…

C++:编程世界的永恒之石

在编程的广袤领域中&#xff0c;C犹如一块永恒的基石&#xff0c;历经岁月的洗礼&#xff0c;依旧坚固而璀璨。它的深厚底蕴、强大功能和广泛的应用领域&#xff0c;使其成为无数程序员心中的信仰与追求。 一、C&#xff1a;历史与传承的交汇点 C的历史可追溯到上世纪80年代&…

由于安全设置错误,远程桌面连接失败怎么办?

问题&#xff1a;远程桌面安全设置错误&#xff1f; “我是一名IT经理&#xff0c;需要经常使用远程桌面连接到办公室的电脑。近期&#xff0c;我在使用远程桌面时&#xff0c;远程桌面提示‘由于安全设置错误&#xff0c;客户端无法连接到远程计算机。’我不清楚是什么原因所…

哪些软件格式在win跟linux上都能运行?

在开始前我有一些资料&#xff0c;是我根据网友给的问题精心整理了一份「linux的资料从专业入门到高级教程」&#xff0c; 点个关注在评论区回复“888”之后私信回复“888”&#xff0c;全部无偿共享给大家&#xff01;&#xff01;&#xff01; 有一些软件格式在Windows和Li…

Windows snmp++获取本地主机信息

编译snmp的包 调用snmp.lib实现信息获取_哔哩哔哩_bilibili 代码&#xff1a; #include <iostream> #include <libsnmp.h> #include <vector> #include <fstream> #include <string> #include "snmp_pp/snmp_pp.h" //#define _NO_L…

基于django医用耗材网上申领系统的实现

基于django医用耗材网上申领系统的实现 开发语言:Python 数据库&#xff1a;MySQL所用到的知识&#xff1a;Django框架工具&#xff1a;pycharm、Navicat、Maven 系统功能实现 管理员登录 系统在安全性的验证方面究竟做了什么功能呢&#xff1f;在做之前我们也进行了思量&…

开源go实现的iot物联网新基建平台

软件介绍 Magistrala IoT平台是由Abstract Machines公司开发的创新基础设施解决方案&#xff0c;旨在帮助组织和开发者构建安全、可扩展和创新的物联网应用程序。曾经被称为Mainflux的平台&#xff0c;现在已经开源&#xff0c;并在国际物联网领域受到广泛关注。 功能描述 多协…

re--SMC

参考&#xff1a;http://t.csdnimg.cn/g7fUY 参考&#xff1a;http://t.csdnimg.cn/qi3q5 简介 SMC&#xff0c;即Self Modifying Code&#xff0c;动态代码加密技术&#xff0c;指通过修改代码或数据&#xff0c;阻止别人直接静态分析&#xff0c;然后在动态运行程序时对代…

[动画详解]LeetCode151.翻转字符串里的单词

&#x1f496;&#x1f496;&#x1f496;欢迎来到我的博客&#xff0c;我是anmory&#x1f496;&#x1f496;&#x1f496; 又和大家见面了 欢迎来到动画详解LeetCode算法系列 用通俗易懂的动画让算法题不再神秘 先来自我推荐一波 个人网站欢迎访问以及捐款 推荐阅读 如何低成…