C++(14)——string的模拟实现

news2025/1/13 14:08:59

       前几篇文章中介绍了关于string以及其相关函数的使用,为了更清楚的了解这些函数的作用,本篇文章通过模拟实现的方式来加深对于函数作用原理的理解。

目录

1. String的整体框架:

1.1 成员变量:

1.2 构造函数:

1.3 析构函数:

1.4 外部获取:

测试: 

2. 功能函数:

2.1 获取_size:

2.2 迭代器:

2.3 []访问及修改: 

测试:

2.4 打印函数:

3 对于对象的修改函数:

3.1 扩容函数:

3.2 插入函数:单个字符:

3.3 插入函数:字符串:

3.4 运算符重载+=:

测试:

3.5 在指定位置插入单个字符:

3.6 在指定位置插入字符串:

测试:

3.7  从指定位置开始向后删除任意长度字符串:

测试:

3.8 交换函数:

3.9 查询函数:查询单个字符:

3.10 查询函数:查询字符串:


1. String的整体框架:

1.1 成员变量:

       通过前面对于string类的学习以及使用,不难得出,如果要模拟实现string,则需要设置三个成员变量,即:char *_str,_capacity,_size因此,对于类的整体框架,可以有下面的代码表示:

namespace violent
{
	class string
	{
	public:


	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	};
}

在有了整体的框架后,就需要在类中加入需要的成员函数

1.2 构造函数:

       在文章C++类与对象基础(7)-CSDNz中提到了初始化列表的概念,但是从上出给出的三个成员变量不难看出,三个变量全是内置类型,因此,对于内置类型的初始化,直接在函数内部进行,由于string类主要针对于,字符串,因此,构造函数的参数也应该符合类型。

       在文章C++(9)——内存管理-CSDN博客提到了在C++中,管理内存的可以使用C语言的两个函数,即mallloc,free.也可以使用C++中的两个关键字,即:new,delete。文章此处选择利用关键字来完成对于内存的管理。

对于如何获取参数中字符串的内容,可以利用strcpy来完成。

      对于三个关键字的取值,_capacity_size可以利用字符串函数strlen计算得出,对于开辟空间大小,需要注意,字符串的末尾会附带一个\0,strlen函数计算的出的值并不会计算\0因此需要手动+1.具体代码如下:


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

但是,如果需要初始化的对象为空,上述函数并不能完成初始化。对于空的对象,可以看做对象中存储了一个\0可以通过缺省值的方式来完成针对于空对象的构造函数,即:

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

1.3 析构函数:

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

1.4 外部获取:

单独建立一个函数,用于返回指针_str即可:

const char* c_str()
		{
			return _str;
		}

测试: 

      利用下方的代码,对上述函数的功能进行测试:

void test1string()
	{
		string s1;
		string s2("hello world");
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
	}

测试结果如下:

2. 功能函数:

2.1 获取_size:

在遍历string类型的对象时,经常会把对象中内容的长度,即_size作为循环的结束,为了方便获取这个值,文章提供函数size来实现。

size_t size()
		{
			return _size;
		}

2.2 迭代器:

之前的文章介绍迭代器的使用时,提到可以把迭代器看作指针(二者原理并不相同),因此,文章模拟实现迭代器也使用指针的方式,即:

iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

2.3 []访问及修改: 

       在之前的文章,在打印string类型的对象时,会使用[]来完成对于对象的打印,并且,[]还可以完成对于对象中内容的修改。在介绍引用时,提到,引用返回相对于指针返回有两个有点,一是速度更快,二是可以修改返回值。在进行函数实现时,可以加上对于参数给出的位置是否合法的检查。因此,对于本小节的函数,实现如下:

char& operator[](size_t pos)
		{
			assert(pos <= _size);
			return _str[pos];
		}

测试:

通过下面的代码,完成对于上述函数功能的测试:

void test1string()
	{
       string s2("hello world");

		cout << "测试[]的访问功能;";
		for (size_t i = 0; i < s2.size(); i++)
		{
			cout << s2[i] << ' ';
			i++;
		}
		cout << endl;
		cout << "测试[]对于返回值的修改功能:";
		for (size_t i = 0; i < s2.size(); i++)
		{
			s2[i]++;
			cout << s2[i] << ' ';
		}

		cout << endl;
		cout << "测试迭代器访问:";
		string::iterator it1 = s2.begin();
		while (it1 != s2.end())
		{
			cout << *it1 << ' ';
			it1++;
		}
	}

运行结果如下:

2.4 打印函数:

上述打印方式都需要额外编写,为了方便使用,直接将打印封装在一个函数,此处命名为print_str:

void print_str(const string& s)

需要注意,此时参数的类型与之前功能函数的类型并不匹配,因此需要根据前面的功能函数尽心修改,即:

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

		size_t size() const
		{
			return _size;
		}

对于迭代器和[]访问,需要额外提供两个函数,即:

typedef const char* const_iterator;

const_iterator begin() const
		{
			return _str;
		}

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

在对函数功能函数进行修改后,可以得出打印函数:
 

void print_str(const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			cout << s[i] << ' ';
		}

		string::const_iterator it2 = s.begin();

		while (it2 != s.end())
		{
			cout << *it2 << ' ';
			it2++;
		}


	}

测试效果如下:

3 对于对象的修改函数:

(注:对于本部分内的函数实现原理与数据结构中链表的实现的文章一起学数据结构(3)——万字解析:链表的概念及单链表的实现-CSDN博客相同,因此不对原理进行过多介绍)

3.1 扩容函数:

void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[]_str;
				_str = tmp;
				_capacity = n;
			}
		}

3.2 插入函数:单个字符:

void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

3.3 插入函数:字符串:

void append(const char* s)
		{
			size_t len = strlen(s);

			if (_size + len > _capacity)
			{
				reserve(_size + len);

			}
			strcpy(_str + _size, s);
			_size += len;
		}

3.4 运算符重载+=:

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

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

测试:

测试代码如下:

void test2string()
	{
		string s2("hello world");
		cout << s2.c_str() << endl;

		s2 += 'x';
		cout << s2.c_str() << endl;

		s2 += "yyyyyy";
		cout << s2.c_str() << endl;
	}

运行结果如下:

3.5 在指定位置插入单个字符:

在文章一起学数据结构(3)——万字解析:链表的概念及单链表的实现-CSDN博客中,详细介绍了如何实现此函数。在文章本部分,不进行详细的介绍,只给出图片来演示此过程:

如上图可视:加入需要将字符apos 插入,首先需要将pos位置及其后面的字符整体向后移动一位。即:

代码如下:

void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
				if(_size == _capacity)
				{
					size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
					reserve(newcapacity);
				}

				int end = _size;
				while (end >=(int)pos)
				{
					_str[end + 1] = _str[end];
					end--;
				}
				_str[pos] = ch;
				_size++;
		}

3.6 在指定位置插入字符串:

原理与插入单个字符大致相同,代码如下:
 

void insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			int end = _size;
			while (end >= pos + len)
			{
				_str[end + 1] = _str[end];
				end--;
			}
			strncpy(_str + pos, s,len);
            _size += len;

		}


测试:

利用下方的代码对上述函数进行测试:

void test2string()
	{
		string s2("hello world");
		cout << s2.c_str() << endl;

		s2 += 'x';
		cout << s2.c_str() << endl;

		s2 += "yyyyyy";
		cout << s2.c_str() << endl;

		cout << endl << endl;
		s2.insert(0, 'a');
		cout << s2.c_str() << endl;

		s2.insert(3, "wwwww");
		cout << s2.c_str() << endl;
	}

运行结果如下:

3.7  从指定位置开始向后删除任意长度字符串:

对于删除字符,需要分下面几种情况:

1.需要删除的字符长度大于等于npos

2.指定位置pos加上需要删除的字符长度大于_size

对于上述两种情况,从pos位置向后全部删除。

对于其他情况,则根据需要删除的长度进行删除

对于第一种情况的删除,并不需要真正的将字符一个一个进行删除,只需要将pos位置的字符改为\0并且对_size进行修改即可。

对于第二种情况的删除,加入指定为位置为pos,需要删除的字符串长度为len。只需要将利用strcpypos+len位置之后的内容,从pos位置拷贝即可:

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


测试:

用下面给出的代码对上述函数进行测试:

void test3string()
	{
		string s3("hello world");

		s3.erase(5, 3);
		cout << s3.c_str() << endl;

		string s4("hello world");
		s4.erase(5, 100);
		cout << s4.c_str() << endl;	
	}

运行结果如下:

3.8 交换函数:

cplusplus网站中可以找到三种不同的交换函数swap,假如有两组不同类型的变量,即:

int a,b;
string s1,s2;

对于第一组变量,通过前面对于模板的了解得知其会去自动调用模板函数,对于第二组变量,在没有第一、二种swap的情况下会去调用模板函数,对于第一种和第二种,二者的区别是调用的形式不同,第一种的调用方法为:

swap(s1,s2);

对于第二种的调用方法为:

s1.swap(s2);

在有第一、二种函数的情况下,不会调用第三种,而是优先调用第一、二种。

对于第三种方式而言,由于代码T\, c(a)对于string类型的对象需要进行一次拷贝构造,因此效率较慢。对于第二种交换方式,是通过this指针和str来完成的,即交换两个对象的地址,并不交换其中的内容,即:

因此,为了速度,采用第二种交换方式,即:

void swap(string& s1)

 但是,在函数内部,并不能直接再次调用swap,即不能直接调用下面的代码:

void swap(string& s1)
		{
			swap(_str, s1._str);
			swap(_size, s1._size);
		    swap(_capacity, s1._capacity);
		}

按照上述代码的调用会引起歧义,因此,需要调用不同地方的swap,即:

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

3.9 查询函数:查询单个字符:

代码如下:

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;
		 }

3.10 查询函数:查询字符串:


对于字符串的查询可以通过函数strstr,对于函数strstr的原理,可以从C语言——字符串和内存函数_c语言 指针 字符串 内存-CSDN博客进行查看。代码如下:

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

			const char* str = strstr(_str + pos, s);
			if (str == nullptr)
			{
				return npos;
			}
			else
			{
				return str - _str;
			}
		}


 

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

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

相关文章

2023年总结我所经历的技术大变革

&#x1f4e2;欢迎点赞 &#xff1a;&#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff0c;赐人玫瑰&#xff0c;手留余香&#xff01;&#x1f4e2;本文作者&#xff1a;由webmote 原创&#x1f4e2;作者格言&#xff1a;新的征程&#xff0c;我们面对的不仅…

快速玩转 Mixtral 8x7B MOE大模型!阿里云机器学习 PAI 推出最佳实践

作者&#xff1a;熊兮、贺弘、临在 Mixtral 8x7B大模型是Mixtral AI推出的基于decoder-only架构的稀疏专家混合网络&#xff08;Mixture-Of-Experts&#xff0c;MOE&#xff09;开源大语言模型。这一模型具有46.7B的总参数量&#xff0c;对于每个token&#xff0c;路由器网络选…

Acwing 138 周赛 解题报告 | 珂学家 | 偏序 + DP构造

前言 整体评价 很久没做acwing周赛了, 之前vp过一些周赛&#xff0c;感觉风格变了。 这次感觉还可以&#xff0c;都是些眼熟的套路题。 A. 5458. 进水排水问题 思路: 签到题 按题意描述编写 import java.io.*; import java.util.*;public class Main {public static void …

解决 conda新建虚拟环境只有一个conda-meta文件&conda新建虚拟环境不干净

像以前一样通过conda 新建虚拟环境时发现环境一团糟&#xff0c;首先新建虚拟环境 conda create -n newenv这时候activate newenv&#xff0c;通过pip list&#xff0c;会发现有很多很多的包&#xff0c;都是我在其他环境用到的。但诡异的是&#xff0c;来到anaconda下env的目…

openEuler安装KVM

1、关闭防火墙和selinux [rootlocalhost ~]# systemctl stop firewalld[rootlocalhost ~]# setenforce 0 2、下载软件包 libvirt&#xff1a;用于管理虚拟化平台的开源的 API&#xff0c;后台程序和管理工具。 qemu&#xff1a;开源&#xff08;模拟&#xff09;软件&#…

【51单片机】IO 扩展(串转并)--74HC595

0、前言 参考&#xff1a; 普中 51 单片机开发攻略 第12章 【51单片机入门教程-2020版 程序全程纯手打 从零开始入门】 https://www.bilibili.com/video/BV1Mb411e7re/?p21&share_sourcecopy_web&vd_source77e36f24add8dc77c362748ffb980148 nop()是什么语句&#…

算法常用思路总结

思路 1. 求数组中最大最小值思路代码 2. 计算阶乘思路&#xff1a;代码&#xff1a; 3. 得到数字的每一位思路代码 4. 计算时间类型5. 最大公约数、最小公倍数6. 循环数组的思想题目&#xff1a;猴子选大王代码 补充经典例题1. 复试四则运算题目内容题解 2. 数列求和题目内容题…

8.1 Java与数据库连接_XML(❤)

8.1 Java与数据库连接_XML 1. XML介绍与用途2. XML语法规则3. XML语义约束3.1 DTD语法3.2 创建DTD文件3.3 XML Schema语法1. XML介绍与用途 2. XML语法规则

常见的代码生成器使用

常见的代码生成器使用 目录概述需求&#xff1a; 设计思路实现思路分析1.第一部分2.第二部分 参考资料和推荐阅读 Survive by day and develop by night. talk for import biz , show your perfect code,full busy&#xff0c;skip hardness,make a better result,wait for cha…

Django随笔

关于Django的admin 1. 在url中把 from django.contrib import admin 重新解开 把path(admin/,admin.site.urls), 解开 2. 注册app&#xff0c;在配置文件中写 django.contrib.admin, 3.输入命令进行数据库迁移 Django国际化 配置文件中&#xff08;改成中文&#xff09; LA…

【STM32F103】DMA直接存储器访问游戏摇杆模块(ADCDMAEXTI)

前言&#xff08;可忽略&#xff09; 当初下定决心要走嵌入式的时候买了一堆传感器&#xff0c;但是因为懒和忙所以闲置了一堆&#xff0c;今天考完了最后一门&#xff0c;所以打算一个个都玩一遍&#xff0c;今天先从这个摇杆开始&#xff0c;当初买这个是想着以后做个遥控小…

指标异常检测和诊断

检测 是发现问题 诊断 是找到原因 误差的分类 系统误差&#xff1a;系统误差是由于仪器本身不精确&#xff0c;或实验方法粗略&#xff0c;或实验原理不完善而产生的。随机误差&#xff1a;随机误差是由各种偶然因素对实验者、测量仪器、被测物理量的影响而产生的。粗大误差&…

动态规划——数字金字塔【集训笔记】

题目描述 观察下面的数字金字塔。写一个程序查找从最高点到底部任意处结束的路径&#xff0c;使路径经过数字的和最大。每一步可以从当前点走到左下方的点也可以到达右下方的点。 在上面的样例中,从13到8到26到15到24的路径产生了最大的和86。 输入 第一个行包含R(1≤ R≤…

4.servera修改主机名,配置网络,以及在cmd中远程登录servera的操作

1.先关闭这两节省资源 2.对于新主机修改主机名&#xff0c;配置网络 一、配置网络 1.推荐图形化界面nmtui 修改完成后测试 在redhat ping一下 在redhat远程登录severa 2、使用nmcli来修改网络配置 2.1、配置要求&#xff1a;主机名&#xff1a; node1.domain250.exam…

线程的取消学习笔记

目录 取消线程-pthread_cancel: 线程清理&#xff1a; 取消线程-pthread_cancel: int pthread_cancel(pthread_t thread);//杀死一个线程 示例代码&#xff1a; #include <stdio.h> #include <pthread.h> #include <unistd.h>void *func(void *arg) {p…

[医学多模态融合] 医学图像 + 文本数据 多模态模型发展及预训练模型应用

[医学多模态融合] 医学图像 文本数据 多模态模型发展及预训练模型应用 0. 前言1. 图像数据 多模态模型的发展2. ConVIRT2.1 模型设计2.2 数据集及训练2.3 应用及表现2.3.1 分类任务2.3.2 Zero-shot任务 3. CLIP3.1 模型设计3.2 数据集及训练3.2.1 图像编码器3.2.2 文本编码器 …

用日期类增强对几个默认函数的的理解

首先写一个日期类&#xff1a;包括打印&#xff0c;根据月份判断天数 用类创建对象默认需要构造函数&#xff1b;且也要判断构造出的日期是否符合常理&#xff1a; 在程序结束时需要一个析构函数来释放空间&#xff0c;&#xff08;日期类的对象不会开空间这里只是写出来演…

python—01虚拟环境

文档结构 1、概念简介2、环境配置2.1、多版本解释器2.2、指令创建虚拟环境2.3、idea创建虚拟环境2.3.1、pycharm 1、概念简介 虚拟环境 在某些场景下&#xff0c;不同的项目需要基于不同版本的Python解释器来开发&#xff0c;或者不同的项目需要的第三方包或模块版本也不同。当…

C# Socket通信从入门到精通(17)——单个异步UDP服务器监听一个客户端C#代码实现

前言: 我们在开发UDP通信程序时,除了开发UDP同步客户端程序,有时候我们也需要开发异步UDP服务器程序,所谓的异步最常见的应用就是服务器接收客户端数据以后,程序不会卡在数据接收这里,而是可以继续往下执行,这在实际项目中是经常会遇到的,所以说掌握异步UDP服务器程序…

从关键新闻和最新技术看AI行业发展(2024.1.1-1.14第十四期) |【WeThinkIn老实人报】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【WeThinkIn老实人报】旨在整理&挖掘AI行业的关键新闻和最新技术&#xff0c;同时Rocky会对这些关键信息进行解读&#xff0c;力求让读者们能从容跟随AI科技潮流。也欢迎大家提出宝贵的优化建议&#xff0c;一起交流学习&…