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-string
	size_t _size;//当前的字符长度
	size_t _capacity;//容量
};

c++的string类还存在一个特殊的static const成员常量npos,我们也将其设计在类中,但是要注意static成员变量要声明在类中,定义在类外。

```cpp
class mystring
{
private:
	char* _str;//c-string
	size_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-string
	size_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:代码小豪的代码仓库

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

云服务器和主机的区别

在今天的数字化时代&#xff0c;对于个人和企业来说&#xff0c;选择适当的服务器托管解决方案至关重要。然而&#xff0c;很多人对于云服务器和传统主机之间的区别不太清楚。本文将为您提供一个详细的指南&#xff0c;帮助您理解云服务器与主机之间的区别&#xff0c;以便您能…

光耦 IS314W中文资料 IS314W引脚图及功能说明

IS314W是一款IGBT/MOSFET输出型光耦&#xff0c;由Isocom公司制造。它主要用于驱动用于电机控制和电源系统变频器的功率IGBT和MOSFET。以下是该产品的部分功能和参数&#xff1a; - 两个独立的光耦输出通道 - 轨对轨输出电压 - 最大峰值输出电流&#xff1a;1.0A - 最小峰值输…

一文扫盲(13):电商管理系统的功能模块和设计要点

电商管理系统是一种用于管理和运营电子商务平台的软件系统。它提供了一系列功能模块&#xff0c;帮助企业进行商品管理、订单管理、会员管理、营销推广、数据分析等工作。本文将从以下四个方面介绍电商管理系统。 一、什么是电商管理系统 电商管理系统是一种集成了各种功能模块…

Moveit Noetic的运动规划组python接口

Moveit Noetic的运动规划组python接口 记录学习如何使用moveit的python API进行机械臂的控制 &#x1f680; 环境配置 &#x1f680; ubuntu 20.04ros noeticmoveit noetic法奥机械臂 frcobot_fr5 文章目录 Moveit Noetic的运动规划组python接口1. 设置moveit功能包2. 编写M…

springboot月度员工绩效考核管理系统

摘要 本月度员工绩效考核管理系统采用java语言做为代码编写工具&#xff0c;采用mysql数据库进行系统中信息的存储与处理。框架采用springboot。 本系统的功能分为管理员和员工两个角色&#xff0c;管理员的功能有&#xff1a; &#xff08;1&#xff09;个人中心管理功能&am…

解决OpenHarmony设备开发Device Tools工具的QUICK ACCESS一直为空

今天重新安装了OpenHarmony设备开发的环境&#xff0c;在安装过程中&#xff0c;到了工程之后&#xff0c;QUICK ACCESS一直为空。如下图红色大方框的内容一开始没有。 解决方案&#xff1a; 在此记录我的原因&#xff0c;我的原因主要是deveco device tools的远程连接的是z…

Spring Bean的生命周期 五步 七步 十步 循序渐进

&#x1f468;‍&#x1f3eb; 参考视频地址 &#x1f496; 五步版 实例化 bean&#xff08;构造方法&#xff09;属性注入&#xff08;set() 方法&#xff09;初始化方法&#xff08;自定义&#xff09;使用bean销毁方法&#xff08;自定义&#xff09; &#x1f496; 七步版…

janus源码分析(1)--代码结构整理

基础说明 janus官网 https://janus.conf.meetecho.com/index.html janus源码地址 https://github.com/meetecho/janus-gateway 编译及部署参考 https://pro-hnb.blog.csdn.net/article/details/137730389?spm1001.2014.3001.5502 https://pro-hnb.blog.csdn.net/article/deta…

Online RL + IL : TGRL: An Algorithm for Teacher Guided Reinforcement Learning

ICML 2023 Poster paper Intro 文章设定一个专家策略&#xff0c;给出两种优化目标。一个是基于专家策略正则的累计回报&#xff0c;一个是原始累计回报。通过比较二者动态的衡量专家策略对智能体在线学习的影响程度&#xff0c;进而实现在线引导过程。 Method 原始的RL目标…

MyBatis常见报错:org.apache.ibatis.binding.BindingException

哈喽&#xff0c;大家好&#xff0c;我是木头左&#xff01; 异常现象描述 当开发者在使用MyBatis进行数据库操作时&#xff0c;可能会遇到org.apache.ibatis.binding.BindingException: Parameter appId not found这样的错误提示。这个错误通常会让程序无法正常运行&#xff…

DeepSort / Sort 区别

推荐两篇博文,详细介绍了deepsort的流程及代码大致讲解: https://blog.csdn.net/qq_48764574/article/details/138816891 https://zhuanlan.zhihu.com/p/196622890 DeepSort与Sort区别: 1、Sort 算法利用卡尔曼滤波算法预测检测框在下一帧的状态,将该状态与下一帧的检测结…

TongWeb8 脚本录制功能

应用场景 在TongWeb8的命令行使用过程中&#xff0c;为简化从手册查找命令行参数的过程&#xff0c;增加了脚本录功能。录制您在控制台上所进行的操作过程&#xff0c;并可在一个新的环境回放这些操作&#xff0c;以提高业务系统的部署效率。录制的脚本文件类型包括 commandsto…

webapi路由寻址机制

路由匹配的原则 1、启动 Application_Start 文件夹中有个WebApiConfig 会把路由规则写入一个容器 2、客户端请求时&#xff1a; 请求会去容器匹配&#xff0c;先找到控制器&#xff08;找到满足的&#xff0c;就转下一步了&#xff09;&#xff0c;然后找Action&#xff0c;we…

高级DBA手把手教你达梦8国产数据库级联更新语句用MergeInto合并代替方法(达梦官方手册无此内容)

高级DBA手把手教你达梦8国产数据库级联更新语句用MergeInto合并代替方法&#xff08;达梦官方手册无此内容&#xff09; 一、传统级联更新语句例子 举例&#xff1a; 表 1&#xff1a;T1 字段名类型A时间类型B字符类型C字符类型D字符类型E字符类型 表 2&#xff1a;T2 字…

IDEA找不到database图标的解决方法

首先右边侧边栏和左边的侧边栏都看一下&#xff0c;确认没有数据库图标以后再参考下面方法。 第一步&#xff0c;打开设置&#xff0c;在插件里搜索database 第二步 安装好&#xff0c;点击确定 返回主页面&#xff0c;左边的侧边栏会出现database图标&#xff0c;点击号就可以…

C++自定义日期类的精彩之旅(详解)

在学习了C的6个默认成员函数后&#xff0c;我们现在动手实现一个完整的日期类&#xff0c;来加强对这6个默认成员函数的认识。 这是日期类中所包含的成员函数和成员变量&#xff1a; 构造函数 // 函数&#xff1a;获取某年某月的天数 inline int GetMonthDay(int yea…

2024精美UI小程序打印系统源码 PHP后端 附搭建教程+功能脑图

内容目录 一、详细介绍二、效果展示1.部分代码2.效果图展示 三、学习资料下载 一、详细介绍 后端安装说明&#xff1a; 测试环境&#xff1a;NginxPHP7.4MySQL5.6 PHP安装扩展&#xff1a;sg11 网站运行目录设置为&#xff1a;/public 网站伪静态规则设置为&#xff1a;think…

C++基础语法之数组

一、一维数组 在C中&#xff0c;一维数组是一系列具有相同数据类型的元素的集合。它们在内存中是连续存储的&#xff0c;可以通过索引访问每个元素。 一维数组的声明形式如下&#xff1a; 数据类型 数组名[常量表达式] 例如&#xff1a; // 声明一个能存储10个整数的数组 in…

33三个启动菜单的区别辨析与本质探索

三个启动菜单的区别辨析与本质探索 你是否傻傻分不清以下三种启动菜单的本质到底是什么&#xff1f; 有一个看起来非常古老生硬&#xff0c;蓝色大背景&#xff0c;字母丑陋&#xff1b; 还有一个看起来老气横秋&#xff0c;黑底白字&#xff0c;像极了远古时期的电脑报废的样…

CSS2(一):CSS选择器

文章目录 1、CSS基础1.1 CSS简介1.2 CSS编写位置1.2.1 行内样式1.2.2 内部样式1.2.3 外部样式1.2.4 样式优先级 1.2.5 CSS代码风格 2、CSS选择器2.1、基本选择器2.1.1 通配选择器2.1.2 元素选择器2.1.3 类选择器2.1.4 ID选择器2.1.5 总结 2.2、CSS复合选择器2.2.1 交集选择器2.…