C++ 之 string类的模拟实现

这学习我有三不学

昨天不学,因为昨天是个过去

明天不学,因为明天还是个未知数

今天不学,因为我们要活在当下,我就是玩嘿嘿~

–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀-正文开始-❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–❀–

目录

一、string类的模拟实现

1.成员函数(Member functions)

1.1 构造函数(constructor)

1.2 析构函数(destructor)

1.3 赋值拷贝函数(operator=)

2.迭代器(iterators)

3.容量(Capacity)

4.元素访问(Element access)

5.调节器(Modifiers)

6.字符操作(String operation)

7.成员常量(Member constant)npos实现

8.非成员函数重载(Non-member function overload)

二、完结撒❀


前言:

模拟string类的实现对于我们学习认识string类会有更加深刻的理解,还没学过string类的老铁建议可以先看学习一下我的上一篇博客讲解:C++ 之 string类 详细讲解,再来进行模拟实现。

一、string类的模拟实现

在上篇博客中讲解了string类的常用接口,这篇博客带大家模拟实现一下string类的一些常用接口。

string类查阅文档

我们根据上面文档所规划的接口分类为大家进行部分模拟实现,大家可以先简单看一下上面文档。

1.成员函数(Member functions)

1.1 构造函数(constructor)

● 无参构造函数 string() 实现:

string()
	//:_str(nullptr)
	:_str(new char[1])//不能赋空指针,因为直接c_str会出错
	, _size(0)
	, _capacity(0)
{
	_str[0] = '\0';
}

有参(字符串)构造函数 string(const char* str = "") 实现:

//string(const char* str = nullptr)错
//string(const char* str = '\0')错
string(const char* str = "")//常量字符串默认结尾含有\0
//该构造函数可以替代上面无参构造函数
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];
	strcpy(_str, str);
}

● 拷贝构造函数 string (const string& s) 实现:
 

//s2(s1)
string (const string& s)
{
	_str = new char[s._capacity+1];//完成深拷贝,+1 存放\0使用
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

1.2 析构函数(destructor)

● ~string()实现:

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

1.3 赋值拷贝函数(operator=)

● operator=实现:

	//s2 = s1
	string& operator=(const string& s)
	{
		char* tmp = new char[s._capacity + 1];//多开辟的1个空间用来存储\0
		strcpy(tmp, s._str);
		delete[] _str;
		_str = tmp;
		_size = s._size;
		_capacity = s._capacity;

		return *this;//支持连续赋值
	}

2.迭代器(iterators)

● begin实现:

在string类中迭代器中begin表示的就是字符串的首地址的指针,但并不是所有迭代器都是由指针来实现的。

这里使用迭代器的对象有两种,一种是const修饰的,一种是const没有修饰的,所以begin实现应有const修饰,和const不修饰两种:

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
	return _str;
}

const_iterator begin() const
{
	return _str;
}

● end实现:

同理,end实现也一样:

typedef char* iterator;
typedef const char* const_iterator;

iterator end()
{
	return _str + _size;
}

const_iterator end() const
{
	return _str + _size;
}

这里可以再说一下,范围for的实现也是基于迭代器begin和end所实现的,大家可以在汇编代码中就可以看到。

而对于const修饰的对象和const没有修饰的对象分别对应使用的范围for其内部实现也是const修饰的begin,end和const没有修饰的begin,end两种范围for。

3.容量(Capacity)

● size实现:

size_t size() const
{
	return _size;
}

● capacity实现:

size_t capacity() const
{
	return _capacity;
}

● resize实现:
由于resize函数的实现可能需要对数组进行扩容,所以我们先实现一下reserve函数:

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n+1];//多开1个位置给\0
		strcpy(tmp, _str);
		delete[] _str;//!!!
		_str = tmp;

		_capacity = n;
	}
}

注意:reserve只有在n大于当前有效空间是才会进行开辟,当n小于当前有效空间不会进行任何操作。

resize实现:

void resize(size n,const char* c)
{
    if(n>_size)
    {
        reserve(n);
        for(size i=size; i<n;i++)
        {
            _str[i] = c;
        }
        _str[n] = '\0';
        _size = n;
    }
    else
    {
        _str[n] = '\0'; 
        _size = n;
    }
}

● clear实现:

注意:clear只是清楚当前所存数据,而不是销毁空间

void clear()
{
	_size = 0;
	_str[0] = '\0';
}

4.元素访问(Element access)

● operator[]实现:

operator[]的实现功能就是访问string类里面字符串的字符所以实现并不复杂:
 

char& operator[](size_t pos)
{
	assert(pos < _size);

	return _str[pos];
}

const char& operator[](size_t pos) const
{
	assert(pos < _size);

	return _str[pos];
}

5.调节器(Modifiers)

● insert实现:

插入字符或字符串那么肯定会涉及到空间不够是否需要扩容的问题,解决了之后剩下的就是将插入位置之后的字符串向后移动插入字符或字符串大小的位置,为要插入的字符或字符串流出插入空间,之后再将字符或字符串插入即可:

void insert(size_t pos, char ch)
{
	assert(pos <= _size);

	if (_size == _capacity)
	{
		//扩容。。。reserve
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}

	//后移字符串方案1
	//int end = _size;
	//while (end >= (int)pos)//!!!一个运算符两边操作数类型不同的时候发生类型提升(范围小的像范围大的提升 这里有符号向无符号提升)
	//{
	//	_str[end + 1] = _str[end];
	//	--end;
	//}

	//后移字符串方案2
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
}

void insert(size_t pos, const char* str)
{
	assert(pos <= _size);

	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	size_t end = _size + len;
	while (pos < end - len + 1)
	{
		_str[end] = _str[end - len];
		--end;
	}

	strncpy(_str + pos, str, len);
	_size += len;
}

● append实现:
既然已经实现了insert,那么我们直接赋用insert实现append,push_back,operator+=即可:

	void append(const char* str)
	{
		//size_t len = strlen(str);
		//if (len + _size > _capacity)
		//{
		//	//扩容。。。
		//	reserve(_size + len);
		//}

		//strcpy(_str + _size, str);
		//_size += len;

		insert(_size, str);
	}

● push_back实现:

void push_back(const char ch)
{
	//if (_size == _capacity)
	//{
	//	//扩容。。。reserve
	//	reserve(_capacity == 0 ? 4 : _capacity * 2);
	//}

	//_str[_size] = ch;
	//_str[_size + 1] = '\0';
	//++_size;

	insert(_size, ch);
}

● operator+=实现:
 

string& operator+=(const char ch)
{
	push_back(ch);
	return *this;
}

string& operator+=(const char* str)
{
	append(str);
	return *this;
}

● erase实现:

当len要清除的字符长度大于等于总字符长度减去pos起始位置时就要将pos起始位置后面的字符全都清除,即_str[pos] = '\0'即可。

	void erase(size_t pos = 0, size_t len = npos)
	{
		assert(pos < _size);
		
			if (pos == npos || pos >= _size - len)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
	}

6.字符操作(String operation)

● c_str实现:

const char* c_str() const
{
	return _str;
}

● find实现:

size_t find(const char c, size_t pos = 0) const
{
	assert(pos < _size);

	//从pos位置开始遍历字符串
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == c)
		{
			return i;
		}
	}
	return npos;
}

size_t find(const char* sub, size_t pos = 0) const
{
	assert(pos < _size);

	const char* p = strstr(_str+pos, sub);
	if (p)
	{
		return p - _str;//指针-指针为两指针之间的距离
	}
	return npos;
}

● substr实现:


与erase分析相似,当要截取的len长度的字符大于等于总字符长度_size减起始位置pos时,就要将pos后面的字符全部截取进行返回:

	string substr(size_t pos = 0, size_t len = npos) const
	{
		string sub;
		if (len >= _size - pos)
		{
			for (size_t i = pos; i < _size; i++)
			{
				sub += _str[i];
			}
		}
		else
		{
			for (size_t i = pos; i < pos + len; i++)
			{
				sub += _str[i];
			}
		}
		return sub;
	}

7.成员常量(Member constant)npos实现:

在标准库中,npos为静态全局变量,在npos文档中就有说明,其值应为size_t类型的-1。

private:
	char* _str = nullptr;
	size_t _size = 0;
	size_t _capacity = 0;

	public:
		static size_t npos;//类内声明
};

size_t string::npos = -1;//类外定义

8.非成员函数重载(Non-member function overload)

● swap实现:


在C++库中是有swap函数的,我们可以直接调用使用,但是对于自定义类型来说就比如string类,其直接调用库中的swap函数进行交换的话会调用3次拷贝构造+1次析构(库中的swap实现方式是创建一个新的临时变量tmp进行两个值的交换),这样消耗会很大,所以对于自定义类型我们不推荐直接调用库里面的swap函数。

那么我们该如何实现swap函数呢?我们可以直接对自定义类型里面的内置成员变量进行交换即可满足两者自定义类型的交换,将swap函数定义为非成员函数是因为要满足swap(s1,s2);交换的格式。

内部成员函数:

	void swap(string& s)
	{
		std::swap(_str, s._str);
		std::swap(_size, s._size);
		std::swap(_capacity, s._capacity);
	}

非成员函数(全局函数):

void swap(string& s1, string& s2)
{
	s1.swap(s2);//直接调用成员函数即可
}

● operator<<实现:

ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;//实现连续打印
}

● operator>>实现:

istream& operator>>(istream& in, string& s)
{
	s.clear();//输入变量值之前需要将变量原先所存的值给清除
	char ch;
	ch = in.get();//可以获取空格' ',cin不支持获取空格

	while (ch != ' ' && ch != '\n')
	{
		s += ch;
		ch = in.get();
	}

	return in;//实现连续赋值
}

● getline实现:

istream& getline(istream& in, string& s)
{
	s.clear();

	char c;
	c = in.get();
	//栈区开辟空间,栈区开辟空间比堆区开辟空间高
	char ch[128];//提高效率
	size_t i = 0;

	while (c != '\n')
	{
		ch[i++] = c;

		if (i == 127)
		{
			ch[127] = '\0';
			s += ch;
			i = 0;
		}
        c = in.get();
	}

	if (i > 0)
	{
		ch[i] = '\0';
		s += ch;
	}
	return in;
}

上面所实现的都是string类的常用的一些接口,其他一些没有实现的大家感兴趣的话可以查阅其他资料自行模拟实现一下

二、完结撒❀

如果以上内容对你有帮助不妨点赞支持一下,以后还会分享更多编程知识,我们一起进步。
最后我想讲的是,据说点赞的都能找到漂亮女朋友❤

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

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

相关文章

Docker基础学习(3.Docker架构)

⭐ 作者简介&#xff1a;码上言 ⭐ 代表教程&#xff1a;Spring Boot vue-element 开发个人博客项目实战教程 ⭐专栏内容&#xff1a;个人博客系统 ⭐我的文档网站&#xff1a;http://xyhwh-nav.cn/ ⭐微信公众号&#xff1a;码上言 文章目录 Docker基本概念1、镜像&…

JavaWeb-自学JSP组件报告

自学JSP组件报告 一、组件资源及作用 1. commons-fileupload-1.2.2.jar 组件作用&#xff1a;用于处理HTTP文件上传请求&#xff0c;提供了文件上传的解析和存储功能。它允许开发者在Web应用中轻松实现文件上传功能。 2. commons-io-2.4.jar 组件作用&#xff1a;提供了一…

springboot+vue新疆肉牛智慧牧场养殖系统

系统涉及的对象是奶牛。 系统使用员工有管理员和普通员工。 管理员有修改的权限&#xff0c;普通员工没有。 系统需要包含奶牛的编号&#xff0c;种类&#xff0c;体重&#xff0c;健康情况、生长情况、牛奶产量&#xff0c;以及上次更新数据时间等信息&#xff0c;管理员可以对…

Perfect Clear WorkBench 智能修图黑科技,你尽管拍剩下的交给我(v4.6.0.2653)

01 Perfect Clear Perfect Clear WorkBench是EyeQlmaging推出的先进图片处理工具&#xff0c;旨在自动优化和简化图像校正。它通过智能技术提高图片的清晰度、颜色保真度&#xff0c;并增强视觉效果&#xff0c;确保高品质输出。 它的核心优势是利用高级算法和AI技术&#xff…

第59篇:创建Nios II工程之控制LED<一>

Q&#xff1a;还记得第1篇吗&#xff1f;设计简单的逻辑电路&#xff0c;控制DE2-115开发板上LED的亮与熄灭&#xff0c;一行Verilog HDL的assign赋值语句即可实现。本期开始创建Nios II工程&#xff0c;用C语言代码控制DE2-115开发板上的LED实现流水灯效果。 A&#xff1a;在…

win下安装desktop及使用desktop安装k8s

1、下载desktop安装包 Docker Desktop: The #1 Containerization Tool for Developers | Docker 2、点击exe文件进行安装 3、安装完需要在启用或关闭windows功能中勾选如下三个选项 4、在desktop中配置Docker Engine { "registry-mirrors": [ "https:/…

Linux创建YUM仓库

在rhel-8.5中的/mnt/目录下是有AppStream和BaseOS这两个软件包的&#xff0c;里面有可安装的一些软件。 /mnt/BaseOS/Packages/ 普通安装 1.使用rpm命令安装&#xff08;rpm -i 程序名称&#xff09; 查看&#xff0c;已经有了这个程序&#xff08;rpm -qa | grep 程序名&…

Footprint Analytics 与 GalaChain 达成战略合作

​ Footprint Analytics 宣布与 GalaChain 达成战略合作。GalaChain 是 Gala 旗下的 Layer 1 区块链。此次合作标志着双方在游戏&#xff08;包括 Gala Games) 、娱乐和金融等多个行业的区块链生态系统革新方面迈出了重要的一步。 GalaChain 致力于满足企业级项目的广泛需求&…

【电路笔记】-Colpitts振荡器

Colpitts振荡器 文章目录 Colpitts振荡器1、概述2、基本Colpitts 振荡器电路3、示例14、使用运算放大器的Colpitts振荡器5、总结Colpitts 振荡器设计使用两个中心抽头电容器与并联电感器串联,形成产生正弦振荡的谐振储能电路。 1、概述 在许多方面,Colpitts 振荡器与我们在上…

GO语言写Prometheus自定义node-exporter的Docker容器测试

1. 安装docker-compose 执行以下命令&#xff0c;安装docker-compose到CentOS7.9环境中&#xff1a; # 下载二进制文件 sudo curl -L "https://github.com/docker/compose/releases/download/v2.24.7/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/d…

不懂就问!现货黄金和实物黄金如何选择?

近期金价大涨&#xff0c;很多投资者就将资金从股票等其他投资品种抽调出来&#xff0c;而投入到黄金市场中。然而&#xff0c;整个黄金投资市场中拥有这么多不同的黄金投资品种&#xff0c;像现货黄金和实物黄金&#xff0c;投资者根本不知道该选哪种&#xff0c;下面我们就来…

[数据结构]——排序——插入排序

目录 ​编辑 1 .插入排序 1.基本思想&#xff1a; 2.直接插入排序&#xff1a; ​编辑 1.代码实现 2.直接插入排序的特性总结&#xff1a; 3.希尔排序( 缩小增量排序 ) 1.预排序 2.预排序代码 3.希尔排序代码 4.希尔排序的特性总结&#xff1a; 1 .插入排序 1.基本思…

2023年全国消费金融财务数据挖掘-投资回报率最高的竟是!

作者Toby&#xff0c;来源公众号Python风控模型&#xff0c;2023年全国消费金融财务统计 大家好&#xff0c;Toby老师汇总了2023年全国消费金融财务数据。这份数据可以用来分析各个消费金融公司在2023年的财务表现&#xff0c;包括资产状况、营业收入、净利润以及投资回报率等…

鸿蒙APP开发页面组件之间的属性关系

我们将对于多页面以及更多有趣的功能展开叙述&#xff0c;这次我们对于 HarmonyOS 的很多有趣常用组件并引出一些其他概念以及解决方案、页面跳转传值、生命周期、启动模式&#xff08;UiAbility&#xff09;&#xff0c;样式的书写、状态管理以及动画等方面进行探讨 页面之间…

【自动化测试】使用MeterSphere进行接口测试

一、接口介绍二、接口测试的过程三、接口自动化测试执行自动化流程 四、接口之间的协议HTTP协议 五、 接口测试用例设计接口文档 六、使用MeterSphere创建接口测试创建接口定义设计接口测试用例 一、接口介绍 自动化测试按对象分为&#xff1a;单元测试、接口测试、UI测试等。…

一次违法网站的渗透经历

0x01 前言 在一次攻防演练中&#xff0c;我发现了一个有趣的渗透路径。在信息收集阶段&#xff0c;我注意到目标网站和用户资产网站共享相同的IP网段。这意味着它们可能在同一台服务器上托管&#xff0c;或者至少由同一家互联网服务提供商管理。这种情况为我们的渗透测试提供了…

路由重分布的概念与配置

路由重分布的概念 l 路由重分布是指连接不同路由域&#xff08;自治系统&#xff09;的边界路由器&#xff0c;它在路由协议之间交换和通告路由信息 从一种协议&#xff08;含静态/直连路由&#xff09;到另一种协议 同一种协议的多个实例 路由重分布的背景 网络出口位置…

几个局域网文件互传工具

推荐几个 局域网文件互传工具 一、 snapdrop https://snapdrop.net/ 两个设备都打开网页 网页会刷新出传送设备&#xff0c;点传送设备&#xff0c;选择文件&#xff0c;确定&#xff0c;另一个点下载 优点无需安装 二、 localsend https://github.com/localsend/locals…

C语言如何使⽤指针操作多维数组?

一、问题 如何使⽤指针操作多维数组呢&#xff1f; 二、解答 从⼆维数组的⻆度来看&#xff0c;a 是⼆维数组名&#xff0c;a 代表整个⼆维数组的⾸地址&#xff0c;也是⼆维数组 0 ⾏的⾸地址&#xff0c;等于1000。a1 代表第⼀⾏的⾸地址&#xff0c;等于1008。 如下图所示。…

【工具使用】神经网络训练高效可视乎库visdom | 使用方式 概念全梳理

我们知道深度学习训练过程中&#xff0c;非常重要的一部分是深度学习的可视乎 一般主流的是tensorboard 还有我在一个代码中看到了visdom&#xff0c;感觉非常Nice 想系统学习并了解一下相关内容 Visdom 是一个由 Facebook Research 开发的开源可视化工具&#xff0c;主要用…