yo!这里是STL::string类简单模拟实现

news2024/9/29 8:58:54

目录

前言

常见接口模拟实现

默认成员函数

1.构造函数

2.析构函数

3.拷贝构造函数

4.赋值运算符重载

迭代器

简单接口

1.size()

2.c_str()

3.clear()

操作符、运算符重载

1.操作符[]

2.运算符==

3.运算符>

扩容接口

1.reserve()

2.resize()

增删查改接口

1.push_back()

2.append()

3.运算符+=

4.insert()

5.erase()

6.find()

7.substr()

流插入&流提取

后记


前言

        我们知道,熟练使用STL是c++程序员的必备技能之一,今天我们来了解STL中的string类,即字符串类型,与c语言中的字符串类似,c++中只是对此做了封装,以及一些接口,基础的知识点这里不再赘述,全部接口功能包括但不限于默认成员函数、运算符操作符重载、reserve、push、pop、insert等等,更多接口介绍参考cplusplus.com/reference/string/string/ ,下面介绍一些常见或者主要接口的实现。

常见接口模拟实现

  • 默认成员函数

1.构造函数

        如下图可知,库中string构造函数很多,适合接受不同参数列表的初始化方式,我们实现一种参数列表全缺省的构造函数,可以满足构造基本要求:

①传c类字符串就是用此字符串给类型初始化;

②不传就是用空字符串初始化对象,

注意:初始化时_size和_capacity都是初始化为字符串的长度,而new申请空间时多申请一个用来存放'\0'。

代码:

String(const char* str = "")
	{
		_size = strlen(str);  //初始化列表有依赖关系时建议不用初始化列表,在大括号内初始化
		_capacity = _size;
		_str = new char[_capacity + 1];

		strcpy(_str, str);
	}

2.析构函数

        析构函数在库中只有下面一种,无参数无返回值,符合特性,我们知道,此类涉及申请内存,所以在析构函数中一定要释放内存,将变量置0,代码如下:

代码:

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

3.拷贝构造函数

        我们知道,拷贝构造函数是构造函数的一种,与普通构造函数写法类型,这是一种传统写法,代码如下,但是,这里讲一种现代写法,也是一种的复用的方法,就是定义一个临时对象,调用其自己的构造函数初始化,之后将初始化的成员交换给目标对象,而临时对象会自动调用析构函数释放,完美地将目标对象初始化完成。

代码:

//现代写法(老板思维)
String(const String& s)
	:_str(nullptr)
	,_size(0)
	,_capacity(0)
{
	String tmp(s._str);
	swap(_str, tmp._str);
	swap(_size, tmp._size);
	swap(_capacity, tmp._capacity);
}

//传统写法
//String(const String& s)
//{
//	_size = s._size;
//	_capacity = _size;
//	_str = new char[_capacity + 1];
//	strcpy(_str, s._str);
//}

4.赋值运算符重载

        库中的赋值运算重载如图,这里实现第一种,传统写法就是赋值_sieze、_capacity之后开空间传元素,考虑还用一下现代写法,去定一个临时对象调用拷贝构造初始化,再将其成员交换给目标对象,目标对象完美完成其初始化。

注意:考虑自己给自己赋值的情况

代码:

	//现代写法(老板思维)
	String& operator=(const String& s)
	{
		if (&s != this)
		{
			String tmp(s);
			/*delete[] _str;
			_str = nullptr;*/  //无需在此释放,交换给tmp后,出了作用域自动调用析构清理
			swap(_str, tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);
		}
		return *this;
	}

    //传统写法
	//String& operator=(const String& s)
	//{
	//	if (&s == this)   //注意:自己给自己赋值的情况
	//		return *this;
	//	_size = s._size;
	//	_capacity = _size;

	//	/*delete[] _str;  //先释放的话万一new失败了就导致释放但未开辟空间成功
	//	_str = new char[_capacity + 1];*/
	//	
	//	char* tmp = new char[_capacity + 1];  //所以先开空间,开成功了再释放原空间
	//	delete[] _str;
	//	_str = tmp;
	//	strcpy(_str, s._str);
	//	return *this;
	//}
  • 迭代器

        迭代器有四种,这里实现两种以及其const型迭代器,string的迭代器就是原生指针(其他类型不一定),所以实现很容易,代码如下。 

代码:

	typedef char* iterator;  //字符串类中迭代器就是原生指针
	typedef const char* const_iterator;
	iterator begin()
	{
		return _str;
	}
	iterator end()
	{
		return _str + _size;
	}
	const_iterator begin() const
	{
		return _str;
	}
	const_iterator end() const
	{
		return _str + _size;
	}
  • 简单接口

1.size()

        size()就是获取字符串字符个数,很简单不过多赘述,设置成const成员函数,是因为可以接受普通对象,也可以接受const对象。 

代码: 

	size_t size() const
	{
		return _size;
	}

2.c_str()

        c_str()是获取c类型字符串,有一些场合可能需要传递指针型字符串,但是其是私有成员不能直接访问,此接口可以实现访问。

代码: 

	char* c_str() const
	{
		return _str;
	}

3.clear()

        clear()是清除字符串中字符,使其成为空串。 

代码: 

	void clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
  • 操作符、运算符重载

1.操作符[]

        操作符[]重载是很重要的重载,实现字符串对象可以像数组一样使用[]加下标访问,代码实现也很简单,注意普通对象和const对象权限不同,需要分开写。

代码: 

	char& operator[](size_t pos)  //普通对象,[]访问元素可读可写
	{
		assert(pos < _size);

		return _str[pos];
	}
	const char& operator[](size_t pos) const  //const对象,[]访问元素只可读
	{
		assert(pos < _size);

		return _str[pos];
	}

2.运算符==

        看过笔者上篇文章(http://t.csdn.cn/xT4nq)可以知道,先重载==、>,其他关系运算符可以复用它们方便的实现出来,不多赘述,不理解的可以翻翻看。

代码: 

    bool operator==(const String& s) const
	{
		return strcmp(_str, s._str) == 0;
	}

3.运算符>

        参考==运算符介绍。

代码: 

	bool operator>(const String& s) const
	{
		return strcmp(_str, s._str) > 0;
	}
  • 扩容接口

1.reserve()

        reverse()是预留空间,但不初始化,即只改变_capacity,传进想要预留的字符个数n,比_capacity大就会释放空间重新申请并拷贝元素。

注意:为string申请空间一定要多申请一个空间放'\0'

代码: 

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

2.resize()

        resize()也是预留空间,并且初始化,即改变_capacity和_size,不传需要初始化的字符则默认是'\0',实现过程比reserve()多考虑一个赋值情况即可。

代码: 

    void resize(size_t n, char ch = '\0')
	{
		if (n > _size)
		{
			reserve(n + 1);
			for (size_t i = _size; i < n; i++)
			{
				_str[i] = ch;
			}
			_str[n] = '\0';
			_size = n;
		}
		else
		{
			_str[n] = '\0';
			_size = n;   //只需要改变size,不需要改变capacity
		}
	}
  • 增删查改接口

1.push_back()

        push_back()是在字符串结尾插入指定字符,首先检查是否需要扩容,需要扩容则要看是不是刚初始化的空字符串,空字符串默认先给4个地址空间,不是空字符串则扩容空间至二倍。

注意:

        ①莫忘_size加一;

        ②莫忘加'\0'在结尾。        

代码: 

	void push_back(char ch)
	{
		if (_size == _capacity)
			reserve(_capacity ? _capacity * 2 : 4);
		_str[_size] = ch;
		_str[_size + 1] = '\0';
		_size++;
	}

2.append()

        append()是在字符串尾部插入一个字符串,实现逻辑很简单,可以直接复用push_back,但避免重复向操作系统申请空间,提前预留好空间用来插入字符,代码如下。

代码: 

	void append(const char* str)
	{
		size_t i = 0;
		size_t n = strlen(str);
		if (_size + n > _capacity)
			reserve(_size + n);   //要尽量少的重复申请空间
		for (i = 0; i < n; i++)
		{
			push_back(str[i]);
		}
	}

3.运算符+=

        +=运算符重载则是在字符串尾部既可以插入字符,也可以插入字符串,调用上面俩函数即可。

代码: 

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

4.insert()

        上面的插入操作只能作用在字符串尾部,而insert()函数是指定位置插入字符或者字符串,极端位置可以复用上面函数,普通位置需要移动元素,经历过c语言的学习,实现此功能并不难。

注意:代码中的标记点处是向后移动元素,注意移动过程中对下标的控制,不要越界访问。

代码: 

	String& insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
			reserve(_capacity ? _capacity * 2 : 4);
		if (pos == _size)
			push_back(ch);
		else
		{
			size_t end = _size + 1;
			while (end > pos)   //标记点
			{
				_str[end] = _str[end - 1];
				end--;
			}
			_str[pos] = ch;
			_size++;
		}
		return *this;
	}
	String& insert(size_t pos, const char* str)
	{
		size_t i = 0;
		size_t n = strlen(str);
		if (_size + n > _capacity)
			reserve(_size + n);
		if (pos == _size)
			append(str);
		else
		{
			size_t end = _size + n;
			while (end >= pos + n)   //标记点
			{
				_str[end] = _str[end - n];
				end--;
			}
			for (i = 0; i < n; i++)
			{
				_str[i + pos] = str[i];
			}
			_size += n;
		}
		return *this;
	}

5.erase()

        erase()则是删除指定位置指定长度的字符串,比插入的实现要简单,其中可以调用strcpy函数,npos是size_t类型的最大值,作为长度缺省值,实现不指定长度时默认是删除至结尾,注意删除完以后莫忘改变_size变量,其他并无难度。

代码: 

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

6.find()

        find()是查找指定字符或字符串第一次出现的位置下标,找不到返回npos,即无符号整型最大值,查找字符从头向后遍历查找即可,查找字符串调用strstr函数,注意此函数找到了是返回字符串地址,返回空是没找到,而find()是需要返回下标,注意控制。

代码: 

	size_t find(char ch, size_t pos = 0) const
	{
		assert(pos < _size);
		size_t i = 0;
		for (i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
				return i;
		}
		return npos;
	}
	size_t find(const char* substr, size_t pos = 0) const
	{
		assert(substr);
		assert(pos < _size);
		char* ptr = strstr(_str + pos, substr);
		if (ptr == nullptr)
			return npos;
		return ptr - _str;
	}

7.substr()

        substr()是提取指定位置指定长度的子串,形成返回string类对象,复用+=运算符简化了实现逻辑,大体实现逻辑与其他函数并无不同,不多赘述。

代码: 

	String substr(size_t pos, size_t len = npos)
	{
		assert(pos < _size);
		String tmp;
		if (_size - pos <= len)
		{
			tmp += (_str + pos);
		}
		else
		{
			for (size_t i = 0; i < len; i++)
			{
				tmp += _str[pos + i];
			}
		}
		return tmp;
	}
  • 流插入&流提取

        在上一篇文章Date类型的简单实现中,使用友元的方式将流插入、流提取编写成了全局函数,因为当时是因为需要访问私有成员变量,那么这里我们发现,可以不需要访问私有成员变量,所以这里不在需要成为友元。

        对于流插入,定义一个临时数组,大小固定为32个元素的地址空间,循环从键盘获取字符插入,当临时数组满了之后,就整体插入到对象中,当获取到空格或者回车就停止,符合流插入的特性。

注意:

        ①临时数组没满时就遇到空格或回车的情况;

        ②这里使用istream类的get函数获取字符,不仅可以接收到普通字符也可以收到空格和回车,用以判断,而不直接使用cin<<,是因为cin输入过程中遇到空格或回车就停止了,接收不到它们。

代码: 

istream& operator>>(istream& in, String& s)   //无需成为友元函数,因为不需要访问私有成员
{
	s.clear();

	const size_t N = 32;
	char tmp[N];
	size_t i = 0;

	char ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		tmp[i++] = ch;
		if (i == N - 1)
		{
			tmp[i] = '\0';
			s += tmp;
			i = 0;
		}
		ch = in.get();
	}
	tmp[i] = '\0';
	s += tmp;

	return in;
}

ostream& operator<<(ostream& out, const String& s)   //无需成为友元函数,因为不需要访问私有成员
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

后记

        今天介绍了一些string常见常用接口的使用及底层实现,希望对于以上接口的使用可以熟练掌握,对于不常见的可以了解,知道有这样一个函数,在后面需要使用的时候,也可以查阅文档即时了解一下(cplusplus.com/reference/string/string/),对于以上接口的使用或者实现不懂的地方,欢迎可以在评论区评论或者私我,一起学习进步哦!


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

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

相关文章

JS常用操作数组的方法整理

JavaScript提供了许多用于操作数组的方法。以下是其中一些常见的方法&#xff1a; 1. push() : 将一个或多个元素添加到数组的末尾&#xff0c;并返回新数组的长度。 2. pop() : 移除并返回数组的最后一个元素。 3. unshift() : 将一个或多个元素添加到数组的开头&#xff0…

一做PPT就抓耳挠腮?老码农掏心分享,选对工具才能事半功倍

今天老陈闲来无事摸个鱼&#xff0c;逛社区的时候看到一条有趣的帖子&#xff0c;HR怒喷UI设计师垃圾&#xff0c;说他们还不如应届生毕业生有能力&#xff0c;设计能力菜的惊人&#xff0c;给老陈平淡的生活增加了不少乐趣。 底下的评论也很有意思&#xff0c;有人说视觉表现…

JavaScript正则表达式之座机号/手机号验证校验规则

引用:https://www.bilibili.com/read/cv18300539/ 本文对利用正则表达式对手机号码进行了验证 支持格式&#xff1a; 座机 &#xff1a;xxx-xxxxxxxx、xxxxxxxxxxxx …座机区号的横杠可有可无 手机&#xff1a;xxxxxxxxxxx JavaScript&#xff1a; var: checkPhone (rule,…

想要理解Linux内核是什么

我们可以思考一下进程何时才能感知到内核的存在。例如&#xff0c;在进行内存分配时&#xff0c;最终需要调用内核的sys_mmap系统调用来申请虚拟内存空间&#xff1b;在创建子进程时&#xff0c;最终需要调用内核的sys_fork来复制父进程&#xff1b;在打开文件时&#xff0c;最…

10家国外直播加速CDN厂商PK2023版

以下是针对流媒体加速的10家主要CDN厂商的比较&#xff0c;我们将根据每家公司发展史、他们提供的功能以及他们的CDN定价比较&#xff0c;这样您就可以选择最适合您需求的 CDN。 1.Akamai Akamai Technologies是一家美国CDN厂商&#xff0c;它是全球领先的媒体和软件交付CDN…

dbscan

dbscan Density-based spatial clustering of applications with noise (DBSCAN), 基于密度的聚类方法。 算法的阈值只有两个&#xff0c;距离阈值eps&#xff0c; 最小点个数minPts。如果一个点在eps为半径的园内&#xff0c;包含点的个数>minPts,那么这个点以及这个圆内的…

JavaWeb教程笔记

JavaWeb Java Web 1、基本概念 1.1、前言 web开发&#xff1a; web&#xff0c;网页的意思 &#xff0c; www.baidu.com静态web html&#xff0c;css提供给所有人看的数据始终不会发生变化&#xff01; 动态web 淘宝&#xff0c;几乎是所有的网站&#xff1b;提供给所有人…

VBA_MF系列技术资料1-133

MF系列VBA技术资料 为了让广大学员在实际VBA编程中有切实可行的思路及有效的提高自己的编程技巧&#xff0c;我参考大量的资料&#xff0c;并结合自己的经验总结了这份MF系列VBA技术综合资料&#xff0c;而且开放源码&#xff08;MF04除外&#xff09;&#xff0c;其中MF01-04属…

【LeetCode热题100】打卡第45天:倒数第24~20题

文章目录 【LeetCode热题100】打卡第45天&#xff1a;倒数第24~20题⛅前言 最佳卖股票时机含冷冻期&#x1f512;题目&#x1f511;题解 戳气球&#x1f512;题目&#x1f511;题解 零钱兑换&#x1f512;题目&#x1f511;题解 打家劫舍III&#x1f512;题目&#x1f511;题解…

【高级数据结构】并查集

目录 修复公路&#xff08;带扩展域的并查集&#xff09; 食物链&#xff08;带边权的并查集&#xff09; 修复公路&#xff08;带扩展域的并查集&#xff09; 洛谷&#xff1a;修复公路https://www.luogu.com.cn/problem/P1111 题目背景 A 地区在地震过后&#xff0c;连接…

ReactRouterv5在BrowserRouter和HashRouter模式下对location.state的支持

结论&#xff1a;HashRouter不支持location.state 文档&#xff1a;ReactRouter v5 从文档可看到history.push()方法支持2个参数&#xff1a;path, [state] state即是location.state&#xff0c;常用于隐式地传递状态参数 但文档未提的是&#xff0c;仅适用于BrowserRouter&am…

面试-杨辉三角python递归实现,二进制转换

杨辉三角 def yang_hui(x,y):xint(x)yint(y)assert x>y,列数不应该大于行数# x 表示行&#xff0c;y表示列if y1 or yx:return 1else:return yang_hui(x-1,y-1)yang_hui(x-1,y)xinput(输入第几行) yinput(输入第几列) resultyang_hui(int(x),int(y)) print(result) #inclu…

微信聊天记录监管有多重要?

在现代企业中&#xff0c;微信成为了主流的沟通工具。越来越多企业开始关注员工聊天记录的监管问题&#xff0c;因为这直接关系到信息泄露的风险。监管员工聊天记录可以保障公司形象、保护员工的安全&#xff0c;并有助于提高员工的工作效率。 监管员工聊天记录到底有多重要&am…

算法通关村第一关——链表白银挑战笔记

文章目录 两个链表的第一个重合节点判断回文链表 两个链表的第一个重合节点 同LeetCode 160.相交链表 解法一&#xff1a;Hash和Set(集合&#xff09;&#xff0c;此处用Set合适。 把其中一个链表的所有节点引用放入set&#xff0c;再从头遍历另一个链表第一次重合的地方就是答…

【flutter】flutter如何让app内字体大小不随着系统改变而改变

如果我们不特意设置&#xff0c;flutter开发的app他的字体大小是会跟着系统设置的字体大小而改变&#xff0c;这样就会导致页面出现布局错乱问题&#xff0c;那么如何解决这个问题呢&#xff1f;我也搜索了相关资料&#xff0c;有两个常用也是网络上搜集到比较多的方法&#xf…

PostgreSQL 简洁、使用、正排索引与倒排索引、空间搜索、用户与角色

PostgreSQL使用 PostgreSQL 是一个免费的对象-关系数据库服务器(ORDBMS)&#xff0c;在灵活的BSD许可证下发行。PostgreSQL 9.0 &#xff1a;支持64位windows系统&#xff0c;异步流数据复制、Hot Standby&#xff1b;生产环境主流的版本是PostgreSQL 12 BSD协议 与 GPL协议 …

三. 多传感器标定方案(空间同步)--3

前面内容&#xff1a; 一. 器件选型心得&#xff08;系统设计&#xff09;--1_goldqiu的博客-CSDN博客 一. 器件选型心得&#xff08;系统设计&#xff09;--2_goldqiu的博客-CSDN博客 二. 多传感器时间同步方案&#xff08;时序闭环&#xff09;--1 三. 多传感器标定方案&…

UE5初学者快速入门教程

虚幻引擎是一系列游戏开发工具&#xff0c;能够将 2D 手机游戏制作为 AAA 游戏机游戏。虚幻引擎 5 用于开发下一代游戏&#xff0c;包括Senuas Saga: Hellblade 2、Redfall&#xff08;来自 Arkane Austin 的合作射击游戏&#xff09;、Dragon Quest XII: The Flames of Fate、…

AI 医疗:MONAI用于医疗影像领域的深度学习

软件介绍 一套开源、免费的协作框架&#xff0c;旨在加速医学成像领域的研究和临床协作。目标是通过构建一个强大的软件框架来加快创新和临床转化的步伐&#xff0c;该框架有利于几乎各个级别的医学成像、深度学习研究和部署。 MONAI利用 3D Slicer 和 DeepEdit 算法来注释您的…

IDEA Writing classes... 比较慢

IDEA配置修改如下&#xff1a; 1、File -> Settings… 2、Build&#xff0c;Execution&#xff0c;Deployment -> Compiler Build process heap size 配置为 20483、Build&#xff0c;Execution&#xff0c;Deployment -> Compiler -> ActionScript & Flex C…