STL源码刨析 string实现

news2024/11/15 15:56:39

目录

一. 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仓库里面,需要的话可以自取

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

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

相关文章

【回溯算法part05】| 491.递增子序列、46.全排列、47.全排列||

目录 &#x1f388;LeetCode491.递增子序列 &#x1f388;LeetCode46.全排列 &#x1f388;LeetCode47.全排列|| &#x1f388;LeetCode491.递增子序列 链接&#xff1a;491.递增子序列 给你一个整数数组 nums &#xff0c;找出并返回所有该数组中不同的递增子序列&#xf…

【玩转Linux操作】详细讲解Linux的 权限 操作

&#x1f38a;专栏【​​​​​​​玩转Linux操作】 &#x1f354;喜欢的诗句&#xff1a;更喜岷山千里雪 三军过后尽开颜。 &#x1f386;音乐分享【Love Story】 &#x1f970;欢迎并且感谢大家指出小吉的问题&#x1f970; 文章目录 &#x1f354;权限的基本介绍⭐具体分析&…

【复习1-2天的内容】【我们一起60天准备考研算法面试(大全)-第六天 6/60】

专注 效率 记忆 预习 笔记 复习 做题 欢迎观看我的博客&#xff0c;如有问题交流&#xff0c;欢迎评论区留言&#xff0c;一定尽快回复&#xff01;&#xff08;大家可以去看我的专栏&#xff0c;是所有文章的目录&#xff09;   文章字体风格&#xff1a; 红色文字表示&#…

网络编程---day4

广播发送方&#xff1a; 广播接收方&#xff1a; 组播发射方&#xff1a; 组播接收方&#xff1a;

OpenCV的remap实现图像垂直翻转

以下是完整的代码: #include <opencv2/highgui/highgui.hpp> #include <opencv2/imgproc/imgproc.hpp> #include <iostream>int main() {

从“存算一体”到“存算分离”:金融核心数据库改造的必经之路

科技云报道原创。 近年来&#xff0c;数据库国产化趋势愈发明显&#xff0c;上百家金融业试点单位在数据库国产化的进程中&#xff0c;进一步增强信心&#xff0c;向50%国产化率大步迈进。 但随着数据库国产化的深入&#xff0c;一些金融机构采用国产数据库服务器本地盘的“存…

【微信小程序创作之路】- 小程序中WXML、JS、JSON、WXSS作用

【微信小程序创作之路】- 小程序中WXML、JS、JSON、WXSS作用 第三章 微信小程序WXML、JS、JSON、WXSS作用 文章目录 【微信小程序创作之路】- 小程序中WXML、JS、JSON、WXSS作用前言一、WXML是什么&#xff1f;二、JS是什么&#xff1f;三、JSON是什么&#xff1f;四、WXSS是什…

IPC 进程间通讯 (1)

目录 1.1 为什么要通信 1.2 为什么能通信 2.1 进程间通信机制的结构 2.2 进程间通信机制的类型 2.3 进程间通信机制的接口设计 3.1 SysV共享内存 3.2 POSIX共享内存 3.3 共享内存映射 3.4 Android ION 3.5 dma-buf heaps 3.6 匿名管道 3.7 命名管道 3.8 SysV消息队列…

基于Java实验室考勤管理系统设计实现(源码+lw+部署文档+讲解等)

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

buuctf pwn入门1

目录 1. test_your_nc(简单nc ) pwn做题过程 2. rip(简单栈溢出) 3. warmup_csaw_2016(栈溢出 覆盖Return_Address) 4. ciscn_2019_n_1(栈溢出 浮点数十六进制) (1) 覆盖v2值 (2) 利用system("cat /flag"); 5. pwn1_sctf_2016(字符逃逸栈溢出 32位) 6. jarvis…

实现【Linux--NTP 时间同步服务搭建】

实现【Linux--NTP 时间同步服务搭建】 &#x1f53b; 前言&#x1f53b; 一、NTP 校时&#x1f530; 1.1 NTP 服务校时与 ntpdate 校时的区别&#x1f530; 1.2 NTP 校时服务搭建&#x1f530; 1.2.1 确认 ntp 的安装&#x1f530; 1.2.2 配置 ntp 服务&#x1f530; 1.2.3 启动…

QTday1

#include "widget.h"Widget::Widget(QWidget *parent): QWidget(parent) {this->resize(500,600);this->setFixedSize(500,600);//设置窗口标题this->setWindowTitle("盗版qq");//设置窗口图标this->setWindowIcon(QIcon("D://QQ下载//ic…

【C++STL】list的反向迭代器

list的反向迭代器 文章目录 list的反向迭代器reverse.h疑问1&#xff1a;为什么在迭代器当中不需要写深拷贝、析构函数疑问2&#xff1a;为什么在迭代器当中需要三个模板参数&#xff1f;疑问3&#xff1a;反向迭代器是怎么实现的&#xff1f;疑问4&#xff1a;为什么*解引用不…

LCD_1602 显示单个字符

目录 效果图&#xff1a;​ 硬件接线&#xff1a; 源代码&#xff1a; Lcd1602.c lcd1602.h main.c 硬件&#xff1a;lcd1602 51单片机 串口 软件&#xff1a;stc keil 效果图&#xff1a; 硬件接线&#xff1a; LCD1602 RW RS E 分别接51单片机的P25 P26 P27 LCD1602…

决策树分析特征重要性可视化无监督特征筛选

from sklearn.tree import DecisionTreeClassifierdtc DecisionTreeClassifier() # 初始化 dtc.fit(x_train, y_train) # 训练# 获取特征权重值 weights dtc.feature_importances_ print(>>>特征权重值\n, weights)# 索引降序排列 sort_index np.argsort(weights…

【学习】ChatGPT对问答社区产生了哪些影响?

引用 StackExchange 社区 CEO Prashanth Chandrasekar 的一篇博客标题 “Community is the future of AI”&#xff0c;引出本文的观点&#xff0c;即ChatGPT对问答社区产生了颠覆性影响&#xff0c;问答社区必须釜底抽薪、涅槃重生&#xff0c;但我们必须坚信“社区才是AI的未…

慎用QGraphicsDropShadowEffect绘制阴影,会导致部分控件一直resizeEvent、重新绘制

我的程序还在创作中&#xff0c;代码还只是UI部分&#xff0c;数据都是固定的&#xff0c;也没有定时刷新之类代码&#xff0c;样式也只是使用了一小部分。有一天我发现我在QTableWidget添加自定义控件的时候&#xff0c;效应特别慢&#xff0c;而自定义控件只是在鼠标进入或离…

活动策划大揭秘:如何制定执行方案

对于刚转行做活动策划的小白&#xff0c;我对你的建议&#xff0c;就是两个字“借鉴”&#xff01; 小白要写出一份优秀的活动策划与执行方案&#xff0c;“借鉴”其实是唯一的方式。 而且而且越资深&#xff0c;借鉴的越多。 当我是小白的时候&#xff0c;我做一个案子只看…

vue3模型代码

效果&#xff1a; 代码 <template><div class"json_box"><json-viewer :value"jsonData" :boxed"false" :expand-depth"5" :expanded"true" ></json-viewer></div> </template><sc…

哈利波特!AI动画已经这么稳定了?MJ控制角色统一性5种技巧;百度大模型Prompt开发与应用新课上线;SD进阶万字长文 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 哈利波特动画视频&#xff0c;使用 TemporalNet 制作 img2img 动画 这是 Reddit 论坛小伙伴分享的自制动画&#xff0c;内容选自哈利波…