STL源码刨析 string实现

目录

一. string 类介绍

二. string 的简单实现

1. 类内成员变量

2. Member functions

string

~string

operator=

string(const string& str)

3. Capacity

size

capacity

empty

clear

reserve

resize

4.Modifiers

push_back

append

operator+=

insert

erase

swap

5. Iterator 

begin

end

7. Element Access

operator[ ]

8. String operations

c_str

find

substr

9. Non-member function overloads

operator<<

operator>>

10. 字符串比较


一. string 类介绍

C++ 的 string 类是标准库中的一个重要类,它提供了一种方便和灵活的字符串处理方式。使用 string 类,可以方便地创建、操作和管理字符串,而无需手动管理底层的内存。

以下是 string 类的一些主要特点和功能:

1. 字符串的存储和管理:string 类提供了存储任意长度字符串的能力,它会自动管理内存,自动调整容量,并提供了对字符串的访问和修改方法。

2. 字符串的赋值和拷贝:可以通过 string 类的构造函数或赋值运算符将字符串赋值给 string 对象,也可以使用 string 类的成员函数对字符串进行拷贝、插入和连接等操作。

3. 字符串的比较和操作:string 类提供了一系列成员函数和运算符,用于比较和操作字符串。可以比较字符串的大小、判断两个字符串是否相等,以及执行字符串的截取、查找、替换等操作。

4. 字符串的迭代访问:可以像访问数组一样通过下标或迭代器来访问 string 类中的字符。

5. 字符串的输入输出:可以使用标准输入输出流来读取和输出 string 对象。

总之,string 类简化了 C++ 中字符串的处理,提供了更高级、更方便的字符串操作方式,避免了手动管理内存和处理字符串的繁琐性。它是一个常用且强大的工具,特别适用于处理动态长度的字符串。

二. string 的简单实现

1. 类内成员变量

  • 我们的string 类需要存储字符串,所以我们当然需要一个指针指向一个空间,里面保存的是字符串
  • 我们还需要对保存的字符串需要统计个数等,所以我们还需要一个 size 来统计当前对象的大小
  • 我们既然是一个字符串管理的类,所以我们当然也需要一个可以当前可以存贮的最大容量 capacity
private:
		size_t _size;
		size_t _capacity;
		char* _str;

		static size_t npos; //后面会用到,暂时不用管

2. Member functions

string

在我们的库里面 sring 的构造函数有很多种,我们只挑一两种常用的来实现

我们要怎么样实现?

  • 我们的 string 的构造的话,我们会使用一个字符串去构造,所以我们需要一个是字符串的构造函数
  • 我们还可能会用一个无参的构造函数,所以我们需要一个无参的构造函数

由于我们现在要实现上面的两个构造函数,其中一个就是字符串构造,还有一个是无参的,而我们的C++中有缺省值,所以我们可以写成一个函数,让无参的构造变成一个缺省,如果我们什么都不传的话,那么就是默认缺省,而我们的缺省值给什么合适呢?当然是给一个空字符串,详细见下面实现

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

			//strcpy(_str, str);
			memcpy(_str, str, _size + 1);
		}

~string

析构函数就是我们一个对象在生命周期结束的时候自动调用的函数,而我们的 string 里面的空间是 new 出来的,所以我们是有资源需要清理的,所以我们也需要写析构函数,而对于 string 来说析构函数就是比较简单的,我们只需要对里面的资源进行释放就可以了

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

operator=

赋值重载就是我们用一个已有的对象对另一个已有的对象进行赋值,在赋值过程中,我们需要对被赋值的对象进行重新开辟空间,新开的空间和赋值的对象一样大,然后我们将赋值对象的字符串拷贝到新开的空间上,然后我们对之前的空间进行释放,然后我们把行开的空间给给被赋值的对象,然后修改其的 size 和 capacity,当然既然是赋值,我们需要对其进行返回,所以我们还需要将 *this 进行返回

		string& operator=(const string& str)
		{
			if (this != &str)
			{
				char* tmp = new char[str._capacity + 1];
				memcpy(tmp, str._str, str._size + 1);
				delete[] _str;
				_str = tmp;
				_size = str._size;
				_capacity = str._capacity;
			}

			return *this;
		}

string(const string& str)

拷贝构造函数,我们是用一个已有的对象对一个还未定义的对象进行初始化,我们需要对未定义的对象进行开空间,然后将已有对象的字符串拷贝过去,然后初始话未定义对象的 size capacity


		string(const string& str)
		{
			_str = new char[str._capacity + 1];
			memcpy(_str, str._str, str._size + 1);
			_size = str._size;
			_capacity = str._capacity;
		}

其实这里的赋值重载和拷贝构造还可以写的更简便一些,但是这里就不讲了 

3. Capacity

capacity  就是我们常用的关于 string 里面的 size 或者 capacity 或者判断是否为空 empty ...

size

size 的实现很简单,我们只需要返回该对象中的 size 大小就好了

		size_t size() const
		{
			return _size;
		}

capacity

capacity 也只需要返回其对应的 capacity即可

		size_t capacity() const
		{
			return _capacity;
		}

empty

empty 是判断我们的字符串是否为空的,如果为空,我们返回 true 否则返回 false

		bool empty() const
		{
			return _size == 0;
		}

clear

clear 就是我们清理掉我们当前对象里面的所有字符,由于我们的字符串的结尾是 '\0',所以我们可以直接将 '\0'放在 0 位置,然后我们对 size 也进行修改

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

reserve

reserve  就是我们申请空间,但是我们只申请,所以我们只会改变 capacity,当然我们的申请空间就是重新开一块空间,然后将之前字符串的内容拷贝到新的空间,然后释放旧的空间,申请结束后记得修改 capacity

		void reserve(size_t n = 0)
		{
			if (n <= _capacity) return;

			char* tmp = new char[n + 1];
			memcpy(tmp, _str, _size + 1);
			delete[] _str;
			_capacity = n;
			_str = tmp;
		}

resize

resize 其中的一个作用是申请空间,但是我们还回修改其的 size 我们库里面的 resize 有两个重载函数,其中一个是只有一个变量 n,另外一个还有一个变量 c

 其中第一个就是只设置 size 如果 n > 当前的 capacity 的话,那么就会扩容,n < size 就会在 size 位置截断,然后修改 size 为 n, 如果是第二个函数的话,其中一个不同点就是 n > size 将原本的size 前的字符保留,如果超出本来的大小那么就用 c 来补充

详细见代码

		void resize(size_t n, const char ch = '\0')
		{
			if (n < _size)
			{
				_size = n;
				_str[_size] = '\0';
			}
			else
			{
				reserve(n);
				for (int i = _size; i < n; ++i)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

4.Modifiers

modify 里面的函数就是对 string 里面的一种修改

push_back

push_back 就是尾插,在插入之前,我们需要判断我们的容量是否充足,如果不充足的话,我们需要进行扩容,扩容后我们就直接插入

		void push_back(const char c)
		{
			if (_size == _capacity)
			{
				//扩容
				reserve(_capacity * 2);
			}

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

append

append 就是在字符串后面追加一个字符串,当然如果容量不够的话,我们也需要进行扩容

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

operator+=

在 string 里面 += 是一个很好用的函数,我们既可以加等一个字符,也可以加等一个字符串,而加等的实现我们可以复用我们的 push_back 和 append

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

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

insert

insert 就是随机位置插入,而我们在插入之前我们需要进行判断插入位置是否合法,然后我们就需要进行判断是否需要扩容,以及扩容多少,如果我们的插入位置在中间或者前面的话我们还需要进行对里面的元素进行挪动,等将位置挪动结束后我们需要将要插入的字符串插入进去

		void insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			
			size_t len = strlen(str);
			if (len + _size > _capacity)
			{
				//扩容
				reserve(len + _size);
			}
			size_t end = _size;
			//挪动数据
			while (end >= pos && end != npos) // 由于我们的 end 和 pos 都是 size_t 类型的所
            以最小值就是 0, 那么这是如果我们 的 pos 位置就是 0 的话我们的 end 需要小于 0 才能
            结束,但是 size_t  类型不会小于0 , 所以这时就会陷入死循环,当我们的 end 现在是 0
            时,那么下一次就是 size_t 类型的最大值,所以我们当 end 为 npos 时就停止
			{
				_str[end + len] = _str[end--];
			}
			//插入数据
			for (size_t i = pos; i < pos + len; ++i)
			{
				_str[i] = str[i - pos];
			}
			_size += len;
		}

我们看到我们的 insert 函数里面还有一个参数的缺省值为 npos 我们的 npos 是一个size_t 类型的最大值,但是该参数有什么用?

erase

erase 就是删除指定位置的值,删除的是中间的元素或者是前面的元素的话,我们也是需要对删除元素后面的元素进行挪动的

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

我们看到我们的 erase 函数里面还有一个参数的缺省值为 npos 我们的 npos 是一个size_t 类型的最大值,但是该参数有什么用?

就是如果我们不传这个参数的话,我们的默认就是从 pos 位置开始全部删除 。

swap

string 类型的 swap 函数,就是简单的交换 string 对象里面的成员变量

		void swap(lxy::string str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

5. Iterator 

iterator是迭代器,我们使用起来很方便,而 string 的迭代器实现比较简单,就是原生指针,而  iterator 就是一个 typedef 得到的,我们的变量调用的时候,我们的变量即可能是 const 也可能是普通的,所以我们还需要实现一个 const 的函数,const的变量由于不能被修改,所以我们的*this指针也是 const 的,所以我们在调用的时候默认的 this 指针由于权限问题const 的对象只能调用 const 的函数,所以我们还需要一个 const 的迭代器

		typedef char* iterator;
		typedef const char* const_iterator;

begin

begin 就是返回字符串的起始位置,所以我们只要返回 str 指针就好

		iterator begin()
		{
			return _str;
		}

		const_iterator begin() const
		{
			return _str;
		}

end

end 就是返回字符串最后一个位置的下一个位置,也是'\0'位置

		iterator end()
		{
			return _str + _size;
		}

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

7. Element Access

这是对里面元素的访问,这里只将一个 operator[ ]

operator[ ]

operator[ ]是对方括号的重载,可以让我们的 string 类可以像我们的数组一样用下标访问,而我的字符串本来就可以用下标访问,所以我们直接用下标访问字符串就可以了

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

			return _str[pos];
		}

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

			return _str[pos];
		}

8. String operations

这个是我们对 string 操作

c_str

c_str 就是返回我们 C形式的字符串

		char* c_str() const
		{
			return _str;
		}

find

find 就是返回第一次遇到要查找的字符或者字符串,如果没有,返回 npos

		size_t find(const char ch, size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = 0; i < _size; ++i)
			{
				if (_str[i] == ch) return i;
			}
			//没有找到,返会 npos
			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			char* ptr = strstr(_str + pos, str);
			if (ptr)
			{
				return ptr - _str;
			}
			else return npos;
		}

substr

substr 是对一个 string 进行截取,然后返回一个新的字符串,该函数也是从 pos 位置开始截取,然后我对截取的位置进行定义一个新的 string 对象,然后保存这些字符,最后返回该对象

		string substr(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			int n = len;
			string tmp;
			if (len == npos || pos + len > _size)
			{
				return _str + pos;
			}
			else
			{
				for (size_t i = pos; i < pos + len; ++i)
				{
					tmp += _str[i];
				}
			}

			return tmp;
		}

9. Non-member function overloads

非成员函数

我们的 string 如果我们像打印的话,我们只能调用 c_str 打印,但是为了方便我们还想要直接可以使用流插入和重载

operator<<

流插入,我们只需要打印出其str里面的值就好了,但是我们需要打印到 size 位置,而为了我们可以连续的打印,我们还需要返回我们的 cout

	ostream& operator<<(ostream& out, const lxy::string& str)
	{
		for (char ch : str)
			out << ch;

		return out;
	}

operator>>

流提取,我们可以之间向 string 类对象输入,但是我们的 cin 如果遇到 空格 或者 换行的话就会停止,所以我们需要遇到 空格换行就停止,但是我们的 cin 又读不懂空格和换行,因为我们的cin默认是以空格和换行座位字符的分隔标志,所以我们需要使用 instream 里面的 get函数,该函数可以读到 空格和换行,而我们在一个输入的字符串中可能会遇到前导空格和换行,这些我们都是需要去掉的,我们插入完之后我们还是可能会连续输入,所以我们还是需要返回 in

	istream& operator>>(istream& in, lxy::string& str)
	{
		char ch = '\0';
		ch = in.get();
		//去除前面的空格或者换行
		while (ch == ' ' || ch == '\n') ch = in.get();
		
		char buffer[128] = { 0 };
		int i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buffer[i++] = ch;
			if (i == 127)
			{
				buffer[i] = '\0';
				i = 0;
				str += buffer;
			}
			ch = in.get();
		}

		if (i)
		{
			buffer[i] = '\0';
			str += buffer;
		}

		return in;
	}

10. 字符串比较

字符串比较是比较简单的,我们只要写出两个就可以进行复用,我们的字符串比较还可以复用 memcmp函数,但是还有一些细节,这里就不多说了

		bool operator<(const lxy::string& str)
		{
			int ret = memcmp(_str, str._str, _size < str._size ? _size : str._size);

			return ret == 0 ? _size < str._size : ret < 0;
		}

		bool operator==(const lxy::string& str)
		{
			return _size == str._size && memcmp(_str, str._str, _size);
		}

		bool operator<=(const lxy::string& str)
		{
			return *this < str || *this == str;
		}

		bool operator>(const lxy::string& str)
		{
			return !(*this <= str);
		}

		bool operator>=(const lxy::string& str)
		{
			return !(*this < str);
		}

		bool operator!=(const lxy::string& str)
		{
			return !(*this == str);
		}

所有代码 https://gitee.com/naxxkuku/bit_c 在这个gitee仓库里面,需要的话可以自取

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

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

相关文章

微服务:Springboot集成Hystrix实现熔断、降级、隔离

文章目录 前言知识积累Springboot集成Hystrix1、maven依赖引入2、application开启feign的hystrix支持&#xff08;客户端配置限流降级熔断&#xff09;3、入口类增加EnableFeignClients EnableHystrix 开启feign与hystrix4、feign调用增加降级方法服务端配置限流降级熔断(选择使…

MySQL基础篇第1章(数据库概述)

文章目录 1、为什么要使用数据库2、数据库与数据库管理系统2.1 数据库的相关概念2.2 数据库与数据库管理系统的关系2.3 常见的数据库管理系统排名2.4 常见的数据库介绍 3、MySQL介绍3.1 概述3.2 MySQL发展史重大事件3.3 关于MySQL 8.03.4 Oracle VS MySQL 4、RDBMS 与 非RDBMS4…

基于Python热门旅游景点数据分析系统设计与实现

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精彩专…

管理执行系统-亿发MES智能制造系统赋能制造企业信息化,实现工业现代化

在制造技术领域&#xff0c;质量控制信息集成建设需要健全的管理体系&#xff0c;加强全过程管理。虽然管理执行系统 (MES) 背后的理论思维已经取得了重大进展&#xff0c;但在软件应用集成和分析能力方面仍有改进的空间。本文将探讨MES系统如何赋能制造企业信息化&#xff0c;…

Camera API1 使用说明

Camera API2 使用说明 目录 一、开启相机 1.1创建项目 1.2注册权限 1.3配置相机特性要求 1.4 获取摄像头的个数 1.5 根据 ID 获取 CameraInfo facing 1.6 开启相机 1.7 关闭相机 二、预览 2.1认识 Parameters 2.2 设置预览尺寸 2.3添加预览 Surface 2.4 开启和关…

指针的进阶(1)

指针的回顾 在C语言中&#xff0c;指针是一个变量&#xff0c;与其他数据不同的是&#xff0c;它的作用是用来存储其它变量的内存地址。指针可以指向不同类型的数据&#xff0c;包括整数、浮点数 、字符、数组等。通过使用指针&#xff0c;我们可以直接访问和修改存储在内存中…

Zabbix 的使用

Zabbix 的使用 一、添加 zabbix 客户端主机1.1 环境准备1.2 服务端和客户端都配置时间同步1.3 服务端和客户端都设置 hosts 解析1.4 设置 zabbix 的下载源&#xff0c;安装 zabbix-agent21.5 修改 agent2 配置文件1.6 启动 zabbix-agent21.7 在服务端验证 zabbix-agent2 的连通…

Android Studio实现内容丰富的安卓校园新闻浏览平台

如需源码可以添加q-------3290510686&#xff0c;也有演示视频演示具体功能&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动。 项目编号070 1.开发环境 android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看新闻列表…

【爬虫】百度FengXiangBiao(完全爬虫卡住了,是爬虫+文本提取方式)

学习使用。爬虫有风险。使用需谨慎。切记切记。 参考链接&#xff1a;学习python爬虫—爬虫实践&#xff1a;爬取B站排行榜 都是排行榜反正 网页细节 按F12&#xff0c;打开控制台。前端就是这点好&#xff0c;非常直观。 找到排行的具体位置&#xff0c;如下图&#xff0c;这…

【如何成功加载 HuggingFace 数据集】不使用Colab,以ChnSentiCorp数据集为例

【如何成功加载 HuggingFace 数据集】不使用Colab&#xff0c;以ChnSentiCorp数据集为例 前置加载数据集尝试一&#xff1a;标准加载数据库代码尝试二&#xff1a;科学上网尝试三&#xff1a;把 Huggingface 的数据库下载到本地尝试3.5 创建 state.json彩蛋 前置 Huggingface …

找不到 配置管理器。sql server 2008 r2 在win10下

win10 打开sqlserver2008r2的SQL Server 配置管理器 &#xff0c;通过开始程序中找不到“SQL Server 配置管理器”。去自己电脑上此目录找&#xff0c;“C:\Windows\SysWOW64\SQLServerManager10.msc”&#xff0c;直接运行此文件就可以调出“SQL Server 配置管理器”。 在win1…

c语言内存

程序是保存在硬盘中的&#xff0c;要载入内存才能运行&#xff0c;CPU也被设计为只能从内存中读取数据和指令。 对于CPU来说&#xff0c;内存仅仅是一个存放指令和数据的地方&#xff0c;并不能在内存中完成计算功能&#xff0c; 如&#xff1a;计算abc,必须将a,b,c都读取到CPU…

CodeMirror 对 XML 文档熟悉及元素控制自定义

CodeMirror 是一个网络代码编辑器组件。它可以在网站中用于实现支持多种编辑功能的文本输入字段&#xff0c;并具有丰富的编程接口以允许进一步扩展。 本文为 xml 格式的代码提示约束格式规范的自定义示例内容。 先看效果&#xff0c;如下&#xff1a; 官方 Demo 的完整代码如…

十八、Jenkins(centos7)执行接口自动化测试脚本,飞书推送测试结果消息

十八、Jenkins&#xff08;centos7&#xff09;执行接口自动化测试脚本&#xff0c;飞书推送测试结果消息 1.创建 Freestyle project 项目 2. 输入git仓库地址 https://gitee.com/HP_mojin/pytest_allure_request_20220811 3. 增加构建步骤-Execute shell&#xff08;Jenkins…

怎样做好客户自助服务?

在当前高速发展的信息化时代&#xff0c;人们已经习惯了即时满足的方式。对于品牌来说&#xff0c;当客户遇到问题时&#xff0c;他们希望能够获得即时细致的解答。如果客户需要等待很长时间才能获取答案&#xff0c;他们的满意度就会降低。因此&#xff0c;企业是否提供客户自…

Java - 异常处理

异常介绍 对异常进行捕获&#xff0c;保证程序可以继续运行&#xff0c;提升程序的健壮性。 执行过程中所发生的异常时间可分为两大类&#xff1a; Error&#xff1a; Java虚拟机无法解决的严重问题。如&#xff1a;JVM系统内部错误&#xff0c;资源耗尽等严重情况。比如&…

mysql的一些练习题

1. 第1题 mysql> create database Market charset utf8; Query OK, 1 row affected (0.01 sec)第二题 mysql> use Market Database changed mysql> mysql> create table customers(-> c_num int(11) primary key auto_increment,-> c_name varchar(50),-&…

mac怎么把m4a转换成mp3?

mac怎么把m4a转换成mp3&#xff1f;大家都知道m4a是苹果公司专属的音频文件格式&#xff0c;因此它是可以直接在mac电脑上打开播放的&#xff0c;但这并不代表m4a音频文件可以在其他播放器或者播放设备上直接打开和使用&#xff0c;相信这个问题大家都遇到过&#xff0c;造成这…

黑马头条-day02

文章目录 前言一、文章列表加载1.1 需求分析1.2 表结构分析1.3 导入文章数据库1.4 实现思路1.5 接口定义1.6 功能实现 二、freemarker2.1 freemarker简介2.2 环境搭建&&快速入门2.2.1 创建测试工程 2.3 freemarker基础2.3.1 基础语法种类2.3.2 集合指令2.3.3 if指令2.3…

使用LiteSpeed缓存插件将WordPress优化到100%的得分

页面速度优化应该是每个网站所有者的首要任务&#xff0c;因为它直接影响WordPress SEO。此外&#xff0c;网站加载的时间越长&#xff0c;其跳出率就越高。这可能会阻止您产生转化并为您的网站带来流量。 使用正确的工具和配置&#xff0c;缓存您的网站可以显着提高其性能。因…