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

       前几篇文章中介绍了关于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;
			}
		}


 

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

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

相关文章

第十一站:多态练习ODU

实现动态切换 ODU.h #pragma once #include <iostream> using namespace std; #define ODU_TYPE_311_FLAG "311" #define ODU_TYPE_335_FLAG "335" enum class ODU_TYPE {ODU_TYPE_311,ODU_TYPE_335,ODU_TYPE_UNKNOW };class ODU{ public:ODU();//发…

Elasticsearch的映射操作

本文来记录下Elasticsearch的映射操作 文章目录 映射的概述 映射的概述 Elasticsearch与mysql数据库对比 映射的概述 有了索引库&#xff0c;等于有了数据库中的 database。索引库(index)中的映射&#xff0c;类似于数据库(database)中的表结构(table)。创建数据库表需要设置字…

SpringBoot实现文件上传和下载实现全过程(值得珍藏)

1. 引言 在Web应用中&#xff0c;文件上传和下载是常见的需求。Spring Boot框架提供了强大的支持和便利的API&#xff0c;使得开发者可以轻松地实现文件上传和下载功能。本文将详细介绍如何在Spring Boot应用中实现文件上传和下载&#xff0c;包括实现原理和完整的代码示例。 …

【js】js 异步机制详解 Generator / Async / Promise

三种语法功能放在一起&#xff0c;是因为他们都有相似特点&#xff1a; 维护某种状态在未来恢复状态并执行 本文重点回答以下几个问题&#xff1a; 为什么 Generator 和 Async 函数的 代码执行流 都可以简化成树形结构&#xff1f;async 函数为什么返回一个 promise&#xf…

list下

文章目录 注意&#xff1a;const迭代器怎么写&#xff1f;运用场合&#xff1f; inserterase析构函数赋值和拷贝构造区别&#xff1f;拷贝构造不能写那个swap,为什么&#xff1f;拷贝构造代码 面试问题什么是迭代器失效&#xff1f;vector、list的区别&#xff1f; 完整代码 注…

3、非数值型的分类变量

非数值型的分类变量 有很多非数字的数据,这里介绍如何使用它来进行机器学习。 在本教程中,您将了解什么是分类变量,以及处理此类数据的三种方法。 本课程所需数据集夸克网盘下载链接:https://pan.quark.cn/s/9b4e9a1246b2 提取码:uDzP 文章目录 1、简介2、三种方法的使用1…

idea运行卡顿优化方案

文章目录 前言一、调整配置1. idea.properties2. idea.vmoptions3.heap size4.Plugins5.Inspections 总结 前言 本人电脑16G内存&#xff0c;处理器i7 10代&#xff0c;磁盘空间也够用&#xff0c;整体配置够用&#xff0c;但运行idea会很卡&#xff0c;记录优化过程&#xff…

Linux中关于head命令详解

head的作用 head用于查看文件的开头部分的内容。 head的参数 -q隐藏文件名-v 显示文件名-c<数目>显示的字节数-n<数目>显示的行数 head的案例 # 查看yum.log前五行内容 head -5 yum.log

文件操作和IO(1)

认识文件 先来认识狭义上的文件(存储在硬盘(磁盘)上).针对硬盘这种持久化的I/O设备,当我们想要进行数据保存时,往往不是保存成一个整体,而是独立成一个个的单位进行保存,这个独立的单位就被抽象成文件的概念,就类似办公桌上的一份份真实的文件一般. 注意:硬盘 ! 磁盘 磁盘属于…

1885_Arduino 1306 OLED屏幕的使用

全部学习汇总&#xff1a; g_arduino: Arduino hack笔记 (gitee.com) 以前我基于Arduino做过一个环境信息采集的小工具&#xff0c;采集到的信息存储到SD卡中。当时的笔记&#xff1a; 341_Arduinopython分析天气变化导致颈椎病发的原因_颈椎病相关数据分析python-CSDN博客 以前…

Jan AI本地运行揭秘:首次体验,尝鲜科技前沿

&#x1f31f;&#x1f30c; 欢迎来到知识与创意的殿堂 — 远见阁小民的世界&#xff01;&#x1f680; &#x1f31f;&#x1f9ed; 在这里&#xff0c;我们一起探索技术的奥秘&#xff0c;一起在知识的海洋中遨游。 &#x1f31f;&#x1f9ed; 在这里&#xff0c;每个错误都…

关于Hive架构原理,尚硅谷

最近学习hive 时候&#xff0c;在做一个实操案例&#xff0c;具体大概是这样子的&#xff1a; 我在dataGip里建了一个表&#xff0c;然后在hadoop集群创建一个文本文件里面存储了数据库表的数据信息&#xff0c;然后把他上传到hdfs后&#xff0c;dataGrip那个表也同步了我上传到…

gin渲染篇

1. 各种数据格式的响应 json、结构体、XML、YAML类似于java的properties、ProtoBuf package mainimport ("github.com/gin-gonic/gin""github.com/gin-gonic/gin/testdata/protoexample" )// 多种响应方式 func main() {// 1.创建路由// 默认使用了2个中…

java idea 中的 Scratches and Consoles

IDEA 中&#xff0c;"Scratches and Consoles" 是一个用于临时代码编辑和交互式开发的工具窗口&#xff0c;作用如下&#xff1a;Scratches&#xff08;草稿&#xff09;&#xff1a;Scratches 是一个用于临时编写和运行代码片段的工具&#xff0c;你可以在其中创建临…

四.Winform使用Webview2加载本地HTML页面并互相通信

Winform使用Webview2加载本地HTML页面并互相通信 往期目录本节目标核心代码实现HTML代码实现的窗体Demo2代码效果图 往期目录 往期相关文章目录 专栏目录 本节目标 实现刷新按钮点击 C# winform按钮可以调用C# winform代码显示到html上点击HTML按钮可以调用C# winform代码更…

2024首更---Web Service 教程

Web Services 简介 Web Services 可使您的应用程序成为 Web 应用程序。 Web Services 通过 Web 进行发布、查找和使用。 您应当具备的基础知识 在继续学习之前&#xff0c;您需要对下面的知识有基本的了解&#xff1a; HTMLXML 如果您希望首先学习这些项目&#xff0c;请在…

万户 ezOFFICE wf_process_attrelate_aiframe.jsp SQL注入漏洞复现

0x01 产品简介 万户OA ezoffice是万户网络协同办公产品多年来一直将主要精力致力于中高端市场的一款OA协同办公软件产品,统一的基础管理平台,实现用户数据统一管理、权限统一分配、身份统一认证。统一规划门户网站群和协同办公平台,将外网信息维护、客户服务、互动交流和日…

lv14 信号量、互斥锁、并发控制机制选择

1 信号量&#xff1a;基于阻塞的并发控制机制 a.定义信号量 struct semaphore sem; b.初始化信号量 void sema_init(struct semaphore *sem, int val); c.获得信号量P int down(struct semaphore *sem);//深度睡眠 int down_interruptible(struct semaphore *sem);//浅度…

【Linux】python版本控制

文章目录 1.查看目前python的版本2.添加软件源并更新3.选择你想要下载的版本4.警示&#xff1a;没必要设置默认版本误区千万千万不要覆盖python3软链接解决办法 5.安装pip换源 6.环境管理 网上有很多教程都是教导小白去官方下载之后编译安装。但是&#xff0c;小白连cmake是什么…

【JavaEE】CAS

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…