【C++】模拟实现string

文章目录

  • 前言
  • 成员变量
  • 成员函数
    • 构造函数
      • 浅拷贝
      • 深拷贝
      • 构造函数实现
    • 析构函数
    • 赋值重载
  • 迭代器
    • begin & end
    • rbegin & rend
  • 空间管理函数
  • 元素访问
  • 元素修改
  • 字符串运算
  • 流提取 & 流插入
    • 流提取
    • 流插入
  • 总结




前言

模拟实现不是为了写得和库里面一样好。而是为了更好的了解底层,从而能够更熟练的使用这些类,同时也能学习大佬们的代码风格。

在学习这章之前,需要对类与对象有一定的知识基础,如果对类与对象有些生疏的话,可以看看这篇文章:《类与对象》
string 类是 C++标准库中的一个重要类,用于表示字符串。

以下是一些关于 string 类的主要特点:

  1. 动态存储:可以自动管理内存,根据字符串的实际长度动态分配和释放内存。
  2. 丰富的操作:提供了很多方便的操作方法,如字符串连接、查找、比较、提取子串等。
  3. 高效性:在性能上表现较好,适用于各种字符串处理场景。


成员变量


在正式模拟实现之前,我们得要先确定我们要实现的类中的成员变量由哪些类型构成:

  1. 首先需要有一个字符指针,用于存储字符串。
  2. 再定义一个变量,用来记录字符串的有效个数。
  3. 再定义一个变量,用来记录该字符指针能存储的有效字符个数大小
  4. 再定义一个全局变量npos,来完成以下操作:
    • npos是一个静态成员常量值,具有size_t类型元素的最大可能值。
    • 当在string的成员函数中作为len(或sublen)参数的值时,该值表示“直到字符串结束”。
    • 作为返回值,它通常用于表示没有匹配。
    • 该常量定义为-1,由于size_t是无符号整型,因此它是该类型的最大可表示值。
class string
{

private:
	char* _str = nullptr;  // 字符指针, 字符串用于存储字符串
	size_t _size = 0; // 用于记录该字符串有几个有效字符(即字符串的长度)
	size_t _capacity = 0; // 用于记录该字符指针能存储的有效字符个数大小

	// Member constants:
	const static size_t npos = -1;
};


成员函数


在这里插入图片描述

构造函数

空字符串构造函数(默认构造函数)
构造一个长度为0个字符的空字符串。

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

拷贝构造函数

这里需要先介绍两个概念:

浅拷贝

浅拷贝:也称位拷贝,编译器只是将对象中的值拷贝过来。如果对象中管理资源,最后就会导致多个对象共享同一份资源,当一个对象销毁时就会将该资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还有效,所以当继续对资源进项操作时,就会发生发生了访问违规。

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我夺,玩具损坏。在这里插入图片描述

深拷贝

在这里插入图片描述
可以采用深拷贝解决浅拷贝问题,即:每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩
子都买一份玩具,各自玩各自的就不会有问题了。

构造函数实现

构造一个str的副本,这里因为涉及到资源管理的问题,所以拷贝构造时,我们需要重新创建一个空间,来存储str的内容。否则如果直接浅拷贝的话,会导致一下两个问题:

  1. 对其中一个字符串改变时,另一个字符串也会跟着被改变(因为它们管理的是同一片空间)
  2. 在调用析构函数时,同一份资源被释放两次
	string(const string& str)
	{
		_str = new char[str._capacity + 1];
		strcpy(_str, str._str);
		_size = str._size;
		_capacity = str._capacity;
	}

string构造函数不止有这两个,如果想要去实现其他的,也不是很困难。只是这两个构造函数,在大部分的情况下就够用了。

析构函数

对于这些参与空间资源分配的类,我们都要自己写析构函数,否则很可能导致内存泄漏。

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

赋值重载

为字符串赋一个新值,替换其当前内容(如果是两个相等的字符串就没必要相互赋值了)。

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

这里还有一种写法,我们可以复用拷贝构造函数:

	string& operator=(const string& str)
	{
		if (str != this->_str)
		{
			string tmp = str;
			swap(tmp);
		}
		return *this;
	}

在这里插入图片描述
当然这里的swap函数是需要我们自己实现的:

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

这里我复用了std空间里面的swap函数,将string类中的成员变量都交换掉。


迭代器


begin & end

在这里插入图片描述
这里只模拟实现我标记出来的那些,C++11出的那几个迭代器,主要是为了区分普通迭代器(iterator)和常量迭代器(const_iterator),但其实,我们用上面的那些就能够涵盖普通迭代器与常量迭代器了(因为普通迭代器和常量迭代器是可以重载的)

	typedef char* iterator;
	typedef const char* const_iterator;
	// 普通迭代器 (可读可写)
	iterator begin()	
	{
		return _str; // 返回字符串的第一个字符的位置
	}
	iterator end()
	{
		return _str + _size;  // 返回字符串的最后一个字符的下一个位置
	}
	// const迭代器 (只可读不可写)
	const_iterator begin() const
	{
		return _str;   // 返回字符串的第一个字符的位置
	}
	const_iterator end() const
	{
		return _str + _size;    // 返回字符串的最后一个字符的下一个位置
	}

因为string的底层空间是连续的,所以我们可以直接使用原生指针来定义为迭代器。
当然,使用范围for的前提是需要满足满足迭代器beginend,才能满足范围for
在这里插入图片描述
这里我只是将begin改为Begin,就不能支持范围for了

rbegin & rend

再说反向迭代器之前,需要具备适配器模式的知识,如果不是很了解可以先看看这篇文章:适配器模式

解释一下为什么要具有适配器模式的知识再往下看:
如果我们只是单纯的完成string类的反向迭代器的功能,其实就可以直接复制一遍正向迭代器的代码,改一下就能实现这个功能了。

但STL中有很多容器,每个容器的底层实现都不一样,所以C++中的迭代器主要有以下几种类型:

  1. 输入迭代器:只读,只能顺序向前移动。
  2. 输出迭代器:只写,只能顺序向前移动。
  3. 前向迭代器:可读写,能顺序向前移动。
  4. 双向迭代器:可读写,能顺序向前和向后移动。
  5. 随机访问迭代器:可读写,能跳跃式地访问元素。

有了适配器模式,我们写一份代码。就能让与该迭代器模式相似的容器中的反向迭代器复用这里的代码(比如,stringvector的迭代器都是支持随机访问的,所以我们写一份能够支持随机访问的反向迭代器的代码,就能使stringvector都能够复用)

这里使用了三个模板参数,目的如下:
在这里插入图片描述
在使用迭代器时,我们难免会使用->(结构体指针访问成员)操作符,或者使用*(解引用)操作符(关于这两个操作符的重载,在模拟实现list章节会重点讲,因为在stringvector的底层物理结构是连续的空间,我们用原生指针就能当做它的迭代器,所以不需要重载这两个操作符。但是我们在这里实现的反向迭代器不是原生指针,而是用一个结构体封装起来的结构,从上面实现可以看出。所以需要重载这两个操作符。这里只要先知道,重载->操作符的返回值是char*或者const char*,重载*操作符的返回值是char&或者const char&)。

因为 const char& 是只读的,而 char& 可读可写,同理char*可以改变所指向的内容,const char*不能改变所指向的内容。所以我们需要将两个迭代器区分开来,而我们嵌套三个模板参数,就可以用后面两个模板参数来区分了。这样,我们就只用写一份代码,const非const的迭代器就都能使用这一份代码了。我在这里用一个图给大家展示一下:
在这里插入图片描述
但是请注意:在string中,我们并不会用到->操作符,因为->操作符是自定义类型的指针对其成员的一个访问操作符,string类不会去存一个自定义类型,因为在后面模拟实现vector类的时候,反向迭代器我也会使用这段代码,所以这里我设计成三个模板参数。(实际上char&const char&是不用传递过来的,但为了能够使用这串代码传过来也一样的)

再来看看实现部分,这里我是模拟库里面实现了一个对称的结构:
在这里插入图片描述
所以我们在对迭代器进行解引用操作时,是对当前位置的前一个位置解引用(此时迭代器的位置不发生改变,所以我们需要创建一个迭代器用于记录当前位置的前一个位置,再对该位置解引用)。
在这里插入图片描述

而对于前置++/–,和后置++/–的实现,是利用正向迭代器向相反方向移动实现的:
在这里插入图片描述

具体一些操作实现如下:

	template<class Iterator, class Reference, class Point>
	struct __random_reverse_iterator
	{
		typedef __random_reverse_iterator self;
		__random_reverse_iterator(const Iterator& it)
			:_cur(it)
		{}
		Reference operator*()
		{
			Iterator tmp = _cur;
			return *(--tmp);
		}
		Point operator->()
		{
			return &(this->operator*());
		}
		self& operator++()
		{
			--_cur;
			return *this;
		}
		self operator++(int)
		{
			self tmp = *this;
			--_cur;
			return tmp;
		}

		self& operator--()
		{
			++_cur;
			return *this;
		}
		self operator--(int)
		{
			self tmp = *this;
			++_cur;
			return tmp;
		}

		self& operator+=(int nums)
		{
			_cur -= nums;
			return *this;
		}
		self& operator-=(int nums)
		{
			_cur += nums;
			return *this;
		}
		bool operator!=(const self& it)
		{
			return _cur != it._cur;
		}
		bool operator==(const self& it)
		{
			return _cur == it._cur;
		}
	private:
		Iterator _cur;
	};

// 下面是我们实现的string类(只含反向迭代器部分)
class string
{
public:
	typedef __random_reverse_iterator<iterator, char&, char*> reverse_iterator;
	typedef __random_reverse_iterator<const_iterator, const char&, const char*> const_reverse_iterator;
	reverse_iterator rbegin()
	{
		return reverse_iterator(end());
	}
	reverse_iterator rend()
	{
		return reverse_iterator(begin());
	}
	const_reverse_iterator rbegin() const
	{
		return const_reverse_iterator(end());
	}
	const_reverse_iterator rend() const
	{
		return const_reverse_iterator(begin());
	}
};
	

这里我并没有将这个反向迭代器的所用功能实现完,但是基本的框架也是有了。


空间管理函数


在这里插入图片描述
这里只模拟实现我勾选的这些函数。
实现这些功能并不复杂,代码如下:

	size_t capacity() const
	{
		return _capacity;
	}
	size_t size() const
	{
		return _size;
	}
	void reserve(size_t n = 0)
	{
		assert(_str);
		// 我实现的是只考虑扩容的,不考虑缩容
		if (_capacity < n)
		{
			char* tmp = new char[n + 1]; // 开辟空间
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
	void resize(size_t n, char c = '\0')
	{
		assert(_str);
		// 不考虑缩容的情况
		if (n > _capacity)
		{
			reserve(n);
			for (size_t i = _size;i < n;++i)
				_str[i] = c;
			_str[_size = n] = '\0';
		}
	}
	void clear()
	{
		_str[_size = 0] = '\0';
	}
	bool empty() const
	{
		return _size == 0;
	}


元素访问

在这里插入图片描述

要实现[]重载并不复杂,但是需要写两个,一个是可读可写的,一个是只读的:

	char& operator[](size_t n)
	{
		assert(n >= 0);
		assert(n < _size);
		return _str[n];
	}
	const char& operator[](size_t n) const
	{
		assert(n >= 0);
		assert(n < _size);
		return _str[n];
	}


元素修改

在这里插入图片描述

这里有些函数我是重载了几个版本的,具体实现细节我写进注释里了

	void push_back(char c)
	{
		assert(_str);
		// 先判断是否需要扩容
		if (_size == _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 10 : 2 * _capacity;
			reserve(newcapacity);
		}
		_str[_size] = c;
		_str[++_size] = '\0'; // 增加字符以后不要忘记将_size增加
	}
	string& append(const string& str)
	{
		assert(_str && str._str);
		// 先判断是否需要扩容
		size_t len = strlen(str._str);
		if (_size + len >= _capacity)
		{
			size_t newcapacity = _size + len + 10; // 每次扩容都多增加是个容积的容错
			reserve(newcapacity);
		}
		memmove(_str + _size, str._str, len);
		_str[_size += len] = '\0';
		return *this;
	}
	string& operator+=(char c)
	{
		push_back(c);
		return *this;
	}
	string& operator+=(const string& str)
	{
		append(str);
		return *this;
	}
	string& insert(size_t pos, const string& str)
	{
		assert(pos <= _size);
		size_t len = strlen(str._str);
		if (_size + len >= _capacity)
			reserve(_size + len);
		size_t end = _size + len;
		while (end - len + 1 > pos)
		{
			_str[end] = _str[end - len];
			--end;
		}
		memmove(_str + pos, str._str, len);
		_size += len;
		return *this;
	}
	string& insert(size_t pos, char c)
	{
		assert(pos <= _size);
		if (_size >= _capacity)
		{
			size_t newcapacity = _capacity == 0 ? 10 : 2 * _capacity;
			reserve(newcapacity);
		}
		size_t end = _size + 1;
		while (end > pos)
		{
			_str[end] = _str[end - 1];
			--end;
		}
		_str[pos] = c;
		++_size;
		return *this;
	}
	string& erase(size_t pos = 0, size_t len = npos)
	{
		assert(pos < _size);
		if (len == npos || pos + len >= _size)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			size_t end = pos + len;
			while (end <= _size)
			{
				_str[end - len] = _str[end];
				++end;
			}
			_size -= len;
		}
		return *this;
	}
	void swap(string& str)
	{
		std::swap(_str, str._str);
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
	}


字符串运算


在这里插入图片描述

c_strdata的功能都是一样的。这两个函数是为了能够让string类兼容C语言中的一些函数。比如:我们使用C语言中的fopen函数打开一个文件名(文件名是一个常量字符串),而假设我们使用string类来管理文件名,这是,就需要这两个函数来将string类转换成常量字符串了。

	const char* c_str()
	{
		return _str;
	}
	const char* data()
	{
		return _str;
	}
	size_t find(const string& str, size_t pos = 0) const
	{
		char* tmp = strstr(_str + pos, str._str);
		if (tmp == nullptr) // 查找子串失败
			return npos;
		return tmp - _str; // 返回两个指针之间的间隔距离,即表示找到的子串的起始位置
	}
	size_t find(char c, size_t pos = 0) const
	{
		for (size_t i = 0;i < _size;++i)
			if (_str[i] == c) return i;
		return npos;
	}
	size_t rfind(char c, size_t pos = 0) const
	{
		for (size_t i = _size - 1;i != npos;--i)
			if (_str[i] == c) return i;
		return npos;
	}
	string substr(size_t pos = 0, size_t len = npos) const
	{
		assert(pos < _size);
		string str;
		if (len == npos || pos + len >= _size)
			len = _size;
		for (size_t i = pos;i < len;++i)
			str += _str[i];
		return str;
	}


流提取 & 流插入


流提取

因为在STL中string类的对象是可以直接使用cout打印出来的,如果我们也想让我们自己实现的string能够直接打印出来,就需要重载一下<<操作符,具体操作如下:

std::ostream& operator<<(std::ostream& out, const string& str)
{
	for (auto& e : str)
		out << e;
	return out;
}

流插入

STL中string类也是支持cin插入数据的,如果我们也想让我们自己实现的string能够使用cin插入数据,就需要重载一下>>操作符,具体操作如下:

std::istream& operator>>(std::istream& in, string& str)
{
	str.clear();
	char ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		str += ch;
		ch = in.get();
	}
	return in;
}

我们需要先将原string对象中的资源清理一下,再插入。这里是利用了istream类中的get()函数从输入缓冲区获取我们输入的字符,然后再使用我们在上面重载的+=操作符,来将我们输入的一组数据插入到一个string对象中。

但这里存在一个问题:
就是如果我们每次读取一个字符,再用+=这个操作符时,假设输入缓冲区中的数据量很大,那么必定存在很多次扩容,扩容还是会有一定的消耗的,所以我们使用一个字符数组来充当缓冲区,现将字符读入到这个数组中,待数组满了以后,我们再将这个数组中的数据插入到string对象中,这样就能有效的降低扩容的次数了

std::istream& operator>>(std::istream& in, string& str)
{
	str.clear();
	char buffer[128] = {};
	int count = 0;
	for (char ch = in.get();ch != ' ' && ch != '\n';ch = in.get())
	{
		buffer[count++] = ch;
		if (count == 127)
		{
			str += buffer;
			count = 0;
		}
	}
	if (count > 0)
	{
		buffer[count] = '\0';
		str += buffer;
	}
	return in;
}

这里还需要重载一个函数getline()获取一行字符串。因为>>操作符,遇到空格或者回车就结束读取了,如果我们需要读取一行字符串(里面可能含有空格),这是就需要使用getline()

std::istream& getline(std::istream& in, string& str)
{
	str.clear();
	char buffer[128] = {};
	int count = 0;
	for (char ch = in.get();ch != '\n';ch = in.get())
	{
		buffer[count++] = ch;
		if (count == 127)
		{
			str += buffer;
			count = 0;
		}
	}
	if (count > 0)
	{
		buffer[count] = '\0';
		str += buffer;
	}
	return in;
}


总结


我在这里将我模拟实现string类的源码附在下面,供大家参考一下如何封装的(因为是在学习阶段,所以封装做得可能不是太好,以下只是我个人的理解):

#include <cstring>
#include <cassert>

namespace hyt
{
	template<class Iterator, class Reference, class Point>
	struct __random_reverse_iterator
	{
		typedef __random_reverse_iterator self;
		__random_reverse_iterator(const Iterator& it)
			:_cur(it)
		{}
		Reference operator*()
		{
			Iterator tmp = _cur;
			return *(--tmp);
		}
		Point operator->()
		{
			return &(this->operator*());
		}
		self& operator++()
		{
			--_cur;
			return *this;
		}
		self operator++(int)
		{
			self tmp = *this;
			--_cur;
			return tmp;
		}

		self& operator--()
		{
			++_cur;
			return *this;
		}
		self operator--(int)
		{
			self tmp = *this;
			++_cur;
			return tmp;
		}

		self& operator+=(int nums)
		{
			_cur -= nums;
			return *this;
		}
		self& operator-=(int nums)
		{
			_cur += nums;
			return *this;
		}


		bool operator!=(const self& it)
		{
			return _cur != it._cur;
		}
		bool operator==(const self& it)
		{
			return _cur == it._cur;
		}
	private:
		Iterator _cur;
	};


	class string
	{
	public: // 声明友元函数
		friend bool operator==(const string& lhs, const string& rhs);
		friend bool operator!=(const string& lhs, const string& rhs);
		friend bool operator>(const string& lhs, const string& rhs);
		friend bool operator>=(const string& lhs, const string& rhs);
		friend bool operator<(const string& lhs, const string& rhs);
		friend bool operator<=(const string& lhs, const string& rhs);
	public: // 迭代器
		typedef char* iterator;
		typedef const char* const_iterator;
		typedef __random_reverse_iterator<iterator, char&, char*> reverse_iterator;
		typedef __random_reverse_iterator<const_iterator, const char&, const char*> const_reverse_iterator;
		// 普通迭代器
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		// const迭代器
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		
		// 反向普通迭代器
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}
		
		// 反向const迭代器
		const_reverse_iterator rbegin() const
		{
			return const_reverse_iterator(end());
		}
		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(begin());
		}
	public:
		// 构造函数
		string(const char* str = "")
		{
			size_t size = strlen(str);
			_size = _capacity = size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		// 拷贝构造
		string(const string& str)
		{
			_str = new char[str._capacity + 1];
			strcpy(_str, str._str);
			_size = str._size;
			_capacity = str._capacity;
		}
		// 赋值操作符重载
		string& operator=(const string& str)
		{
			if (str != this->_str)
			{
				string tmp = str;
				swap(tmp);
			}
			return *this;
		}

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

		// Capacity:
		size_t capacity() const
		{
			return _capacity;
		}
		size_t size() const
		{
			return _size;
		}
		void reserve(size_t n = 0)
		{
			assert(_str);
			// 我实现的是只考虑扩容的,不考虑缩容
			if (_capacity < n)
			{
				char* tmp = new char[n + 1]; // 开辟空间
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}
		void resize(size_t n, char c = '\0')
		{
			assert(_str);
			// 不考虑缩容的情况
			if (n > _capacity)
			{
				reserve(n);
				for (size_t i = _size;i < n;++i)
					_str[i] = c;
				_str[_size = n] = '\0';
			}
		}
		void clear()
		{
			_str[_size = 0] = '\0';
		}
		bool empty() const
		{
			return _size == 0;
		}

		// String operations:
		const char* c_str()
		{
			return _str;
		}
		const char* data()
		{
			return _str;
		}
		size_t find(const string& str, size_t pos = 0) const
		{
			char* tmp = strstr(_str + pos, str._str);
			if (tmp == nullptr) // 查找子串失败
				return npos;
			return tmp - _str; // 返回两个指针之间的间隔距离,即表示找到的子串的起始位置
		}
		size_t find(char c, size_t pos = 0) const
		{
			for (size_t i = 0;i < _size;++i)
				if (_str[i] == c) return i;
			return npos;
		}
		size_t rfind(char c, size_t pos = 0) const
		{
			for (size_t i = _size - 1;i != npos;--i)
				if (_str[i] == c) return i;
			return npos;
		}
		string substr(size_t pos = 0, size_t len = npos) const
		{
			assert(pos < _size);
			string str;
			if (len == npos || pos + len >= _size)
				len = _size;
			for (size_t i = pos;i < len;++i)
				str += _str[i];
			return str;
		}

		// Element access:
		char& operator[](size_t n)
		{
			assert(n >= 0);
			assert(n < _size);
			return _str[n];
		}
		const char& operator[](size_t n) const
		{
			assert(n >= 0);
			assert(n < _size);
			return _str[n];
		}

		//Modifiers:
		void push_back(char c)
		{
			assert(_str);
			// 先判断是否需要扩容
			if (_size == _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 10 : 2 * _capacity;
				reserve(newcapacity);
			}
			_str[_size] = c;
			_str[++_size] = '\0'; // 增加字符以后不要忘记将_size增加
		}
		string& append(const string& str)
		{
			assert(_str && str._str);
			// 先判断是否需要扩容
			size_t len = strlen(str._str);
			if (_size + len >= _capacity)
			{
				size_t newcapacity = _size + len + 10; // 每次扩容都多增加是个容积的容错
				reserve(newcapacity);
			}
			memmove(_str + _size, str._str, len);
			_str[_size += len] = '\0';
			return *this;
		}
		string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}
		string& operator+=(const string& str)
		{
			append(str);
			return *this;
		}
		string& insert(size_t pos, const string& str)
		{
			assert(pos <= _size);
			size_t len = strlen(str._str);
			if (_size + len >= _capacity)
				reserve(_size + len);
			size_t end = _size + len;
			while (end - len + 1 > pos)
			{
				_str[end] = _str[end - len];
				--end;
			}
			memmove(_str + pos, str._str, len);
			_size += len;
			return *this;
		}
		string& insert(size_t pos, char c)
		{
			assert(pos <= _size);
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 10 : 2 * _capacity;
				reserve(newcapacity);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = c;
			++_size;
			return *this;
		}
		string& erase(size_t pos = 0, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				size_t end = pos + len;
				while (end <= _size)
				{
					_str[end - len] = _str[end];
					++end;
				}
				_size -= len;
			}
			return *this;
		}
		void swap(string& str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
		
		// Member constants:
		const static size_t npos = -1;
	};

	bool operator==(const string& lhs, const string& rhs)
	{
		return (!(strcmp(lhs._str, rhs._str)) && lhs._size == rhs._size);
	}
	bool operator!=(const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}
	bool operator>(const string& lhs, const string& rhs)
	{
		return strcmp(lhs._str, rhs._str) > 0;
	}
	bool operator>=(const string& lhs, const string& rhs)
	{
		return (lhs > rhs || lhs == rhs);
	}
	bool operator<(const string& lhs, const string& rhs)
	{
		return !(lhs >= rhs);
	}
	bool operator<=(const string& lhs, const string& rhs)
	{
		return (lhs < rhs || lhs == rhs);
	}

	// 重载 流插入 & 流提取
	std::ostream& operator<<(std::ostream& out, const string& str)
	{
		for (auto& e : str)
			out << e;
		return out;
	}

	std::istream& operator>>(std::istream& in, string& str)
	{
		str.clear();
		char buffer[128] = {};
		int count = 0;
		for (char ch = in.get();ch != ' ' && ch != '\n';ch = in.get())
		{
			buffer[count++] = ch;
			if (count == 127)
			{
				str += buffer;
				count = 0;
			}
		}
		if (count > 0)
		{
			buffer[count] = '\0';
			str += buffer;
		}
		return in;
	}
	std::istream& getline(std::istream& in, string& str)
	{
		str.clear();
		char buffer[128] = {};
		int count = 0;
		for (char ch = in.get();ch != '\n';ch = in.get())
		{
			buffer[count++] = ch;
			if (count == 127)
			{
				str += buffer;
				count = 0;
			}
		}
		if (count > 0)
		{
			buffer[count] = '\0';
			str += buffer;
		}
		return in;
	}
}

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

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

相关文章

一行代码将文件存储到本地或各种存储平台

一行代码将文件存储到本地或各种存储平台 这里我们介绍的是一个开源项目。 这个是他的官网 简介 (xuyanwu.cn) 下面来看他的一个介绍&#xff1a; 一行代码将文件存储到本地、FTP、SFTP、WebDAV、阿里云 OSS、华为云 OBS、七牛云 Kodo、腾讯云 COS、百度云 BOS、又拍云 USS…

R语言学习—1—将数据框中某一列数据改成行名

将数据框中某一列数据改成行名 代码 结果

【大语言模型LLM】-基于大语言模型搭建客服助手(2)

&#x1f525;博客主页&#xff1a;西瓜WiFi &#x1f3a5;系列专栏&#xff1a;《大语言模型》 很多非常有趣的模型&#xff0c;值得收藏&#xff0c;满足大家的收集癖&#xff01; 如果觉得有用&#xff0c;请三连&#x1f44d;⭐❤️&#xff0c;谢谢&#xff01; 长期不…

408数据结构-树的基本概念与性质 自学知识点整理

树的定义 树是 n n n&#xff08; n ≥ 0 n≥0 n≥0&#xff09;个结点的有限集。当 n 0 n0 n0时&#xff0c;称为空树。 任意一棵非空树应具有以下特性&#xff1a; 有且仅有一个特定的被称为根的结点&#xff08;根结点&#xff09;。当 n &#xff1e; 1 n&#xff1e;1 …

【C语言】/*数据类型和变量*/

目录 前言 一、数据类型的介绍 二、内置数据类型的介绍 2.1 字符型 2.2 整型 2.3 浮点型 2.4 布尔类型 三、数据类型长度的计算 3.1 sizeof 操作符 3.2 数据类型的长度(VS2022) 3.3 sizeof中表达式不计算 四、signed 和 unsigned 五、数据类型的取值范围 六、变…

.360勒索病毒的威胁:如何恢复您的数据?

引言&#xff1a; 近年来&#xff0c;网络安全威胁层出不穷&#xff0c;其中.360勒索病毒以其独特的攻击方式和广泛的传播能力&#xff0c;成为了众多企业和个人面临的重大挑战。本文将对.360勒索病毒进行深入剖析&#xff0c;并探讨应对此类病毒的有效策略&#xff0c;以帮助…

UE5入门学习笔记(六)——编译低版本插件

对于有些低版本的插件&#xff0c;可以通过此方法自己编译到高版本而无需等待插件作者更新 使用工具&#xff1a;如图所示 步骤1&#xff1a;打开cmd&#xff0c;并使用cd命令切换到此目录 步骤2&#xff1a;输入如下指令 RunUAT.bat BuildPlugin -Plugin“路径1” -Package“…

Java中的进程和线程

进程定义 进程是正在运行的程序&#xff0c;是系统进行资源分配和调用的独立单位&#xff0c;每一个进程都有它自己的内存空间和系统资源 线程的定义 线程是进程中单个顺序控制流&#xff0c;是一种执行路径 单线程&#xff1a; 一个进程如果只有一条路径则被称为单线程 多…

python学习笔记----面向对象(十)

一、什么是类 类是一个抽象的模板&#xff0c;用于创建具体的实例。可以将类理解为一个蓝图&#xff0c;它定义了一系列对象共有的属性&#xff08;数据&#xff09;和方法&#xff08;函数&#xff09;。类是对一组具有相同属性和功能的对象的抽象。例如&#xff0c;你可以定…

数据结构——循环结构:for循环

今天是星期五&#xff0c;明天休息&#xff0c;后天补课&#xff0c;然后就是运动会&#xff0c;接着是放假。&#xff08;但这些都和我没关系啊&#xff0c;哭死&#xff01;&#xff09;今天脑袋难得清醒一会儿&#xff0c;主要是醒的比较早吧&#xff0c;早起学了一会&#…

【VueUse】超越基本功能的高级 Vue 元素操作

在vue开发中我们经常需要操作DOM元素&#xff0c;从简单的添加类到动态创建元素&#xff0c;这些操作都是不可避免的。而在VueUse库中&#xff0c;Elements相关API函数为我们提供了一系列强大而灵活的工具&#xff0c;帮助我们更轻松地处理DOM元素。无论是优雅地处理元素、动态…

[XYCTF新生赛]-PWN:fmt解析(scanf格式化字符串漏洞,任意地址写)

查看保护 查看ida 这里没什么好说的 完整exp&#xff1a; from pwn import* context(log_leveldebug) #pprocess(./fmt) premote(gz.imxbt.cn,20975) backdoor0x4012BEp.recvuntil(bgift: ) printf_addrint(p.recv(14),16) print(hex(printf_addr)) libcELF(./libc-2.31.so) …

HTML5实用大全(Part.2)

引言&#xff1a; 哈喽&#xff0c;各位小伙伴们大家好呀&#xff0c;学习了上一篇关于HTML5的文章后&#xff0c;你是否对于入门HTML5有了一定的基础了呢&#xff0c;本篇博客我们将继续学习HTML5的不同标签&#xff0c;跟上队伍&#xff0c;准备出发咯&#xff01; 1.标签之…

js APIS part2

什么是事件&#xff1f; 事件是在编程时系统内发生的 动作 或者发生的事情。比如用户在网页上 单击 一个按钮 什么是事件监听&#xff1f; 就是让程序检测是否有事件产生&#xff0c;一旦有事件触发&#xff0c;就立即调用一个函数做出响应&#xff0c;也称为 绑定事件或者注册…

2024年钉钉群直播回放如何永久保存

工具我已经打包好了&#xff0c;有需要的自己取一下 链接&#xff1a;百度网盘 请输入提取码 提取码&#xff1a;1234 --来自百度网盘超级会员V10的分享 1.首先解压好我给大家准备好的压缩包 2.再把逍遥一仙下载器压缩包也解压一下 3.打开逍遥一仙下载器文件夹里面的M3U8…

python实验一 简单的递归应用

实验一 实验题目 1、兔子繁殖问题(Fibonacci’s Rabbits)。一对兔子从出生后第三个月开始&#xff0c;每月生一对小兔子。小兔子到第三个月又开始生下一代小兔子。假若兔子只生不死&#xff0c;一月份抱来一对刚出生的小兔子&#xff0c;问一年中每个月各有多少只兔子。 &…

软件工程全过程性文档(软件全套文档整理)

软件项目相关全套精华资料包获取方式①&#xff1a;进主页。 获取方式②&#xff1a;本文末个人名片直接获取。 在软件开发的全过程中&#xff0c;文档是记录项目进展、决策、设计和测试结果的重要工具。以下是一个简要的软件全过程性文档梳理清单&#xff1a; 需求分析阶段…

基于 AI 的数据库助手-Chat2DB

序言 现在已经开始步入 AI 时代&#xff0c;AI 产品也已经络绎不绝。今天&#xff0c;给大家介绍一款数据库的 AI 产品 —— Chat2DB。 一、什么是 Chat2DB Chat2DB 由阿里提供的一个数据库管理、数据开发、数据分析的工具&#xff0c;它是一个 AI 原生的数据库管理工具&…

Spring 当中的Bean 作用域

Spring 当中的Bean 作用域 文章目录 Spring 当中的Bean 作用域每博一文案1. Spring6 当中的 Bean的作用域1.2 singleton 默认1.3 prototype1.4 Spring 中的 bean 标签当中scope 属性其他的值说明1.5 自定义作用域&#xff0c;一个线程一个 Bean 2. 总结:3. 最后&#xff1a; 每…

JavaScript基础(三)

JS的数据类型 数据类型&#xff0b;解释 undefined 如var num;变量num没有初始值将被赋予undefined[基本数据类型]。 null 表示一个空值&#xff0c;与undefined值相等[对象]。 number 例:var num10; //整数&#xff0c;var num10.5; //浮点型。 boolean 布尔型&…