string类——常用函数模拟(C++)

news2024/9/22 3:35:09

        本篇中,将会详细的介绍 Cpp 中 string 的使用,以及 string 类常用函数的模拟实现。对于 string 的内置函数来说,存在很多很冗余的用法,很多函数都有很多种用法,本篇将会讲解常用内置函数的常用用法,模拟函数的时候,也只是模拟常用的用法代码。本篇实现的 string ,仅仅只是通过一种很简单的方式实现的,对于 vs 、g++ 下的 string 相比都是小巫见大巫。

        本篇的函数讲解顺序按照 cplusplus 官网的顺序,网站:string - C++ Reference (cplusplus.com)

        目录如下:

目录

1. iterator —— 迭代器

2. capacity —— 容量

3. Element access —— 访问成员

4. modify —— 修改串内容

5. string operations —— string 的操作

 6. 默认成员函数

7. 流提取和其他运算符重载

8. 浅谈本篇中的 string

9. All Code

1. iterator —— 迭代器

        首先我们第一个要讲解的就是我们的迭代器,在string中存在许多的迭代器,但是我们常用的就是:

        因此,我们主要围绕这四个迭代器来实现,先讲解其功能,对于 begin 和 end  来说:

        其主要的作用为:begin:返回 string 串中的第一个元素的位置,end:返回 string 串中的最后一个元素的位置。对于 rbegin 和 rend 来说则是相反的,rbegin 返回逆向串的第一个字符,也就返回正向串的最后一个字符,rend 返回逆向串的最后一个字符,也就是返回正向串的第一个元素,所以从功能上来说,begin == rend,rbegin == end,其使用如下:

        如图所示,一个为正向打印,一个为逆向打印,根据以上实现的功能,模拟出的代码为:

namespace MyString {
	class string {
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		typedef char* reverse_iterator;
		typedef const char* const_reverse_iterator;

		iterator begin() {
			return _str;
		}

		const_iterator begin() const {
			return _str;
		}

		iterator end() {
			return _str + _size;
		}

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

		reverse_iterator rbegin() {
			return _str + _size;
		}

		const_reverse_iterator rbegin() const {
			return _str + _size;
		}
		
		reverse_iterator rend() {
			return _str;
		}
		const_reverse_iterator rend() const {
			return _str;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

        对于如上的代码,我们同样的函数还需要写出两个版本,一个普通版本,一个由 const 修饰的版本,因为对于由 const 修饰的 string 并不能调用普通版本的函数,所以我们需要在写一个由 const 修饰的版本。另外,实现一个属于自己的类,需要使用命名空间封装,否则会与库中自带的 string 相矛盾。

        当然,对于以上的实现,我们更多是基于功能实现的,对于不同的编译器的实现方式存在不同,而且对于不同的类的迭代器实现方式也不同,我们实现出的函数是以简单的方式实现的。

2. capacity —— 容量

        接下来我们将实现与容量相关的类函数,仍然存在很多函数,但是我们只需要掌握使用以下由红框框住的代码即可:

        首先讲解的是最为简单的两个类函数,分别是 size capacity,只需要返回串的尺寸和容量(当前可以容纳多少字符的量),因为 _size 和 _capacity 被 private 封装,不能直接访问,所以我们需要使用函数来获取,在函数中,我们只需要返回 _size 和 _capacity 的值即可。其实 length 和 size 的功能是相同的,返回的都是 _size ,但是最常用的还是 size 函数

        接下来需要掌握的为 clear empty 函数,clear 的功能为:将串的内容清楚,使串变成空串,所以具体实现,我们只需要将串中第 0 个位置的字符置为 '\0' 即可;对于 empty 函数,功能为,判断当前串是否为空串,实现起来仍然轻松,只需要返回 _size 是否 等于 0 即可。

        接下的重点就为 resizereserve 函数了,对于这两个函数的解释如下:

        首先是 reserve 函数,该函数的作用为:将字符串的容量改为 n ,若当前容量已经大于 n 则不需要修改,因为不会将其缩容,只有当 n 大于当前容量时,才会扩容。

        对于 resize 函数,功能为:将字符串的长度修改为长度 n ,但是像比于 reserve 函数,resize 函数可以将其长度缩小,当 n 小于 _size 的时候,就减小长度,大于 _size 的时候就增长长度。并且由图中可以看到,该函数存在两个重载函数,其中一个为,n 大于 _size 之后,在后面追加字符 c。我们在实现的时候,将其改为一个函数,具体的实现如下:

namespace MyString {
	class string {
	public:
		const size_t size() const {
			return _size;
		}

		const size_t capacity() const {
			return _capacity;
		}

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

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

		// 对数据进行扩容
		void reserve(size_t n) {
			// 仅仅 n 大于 capacity 的时候,我们才进行扩容
			if (n > _capacity) {
				// 每一次分配看见,需要多分配一个,给 '\0'腾一个空间
				char* tmp = new char[n + 1];
				// 将原字符串拷贝后删除
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		// 将resize函数改为半缺省函数,不提供字符c的时候,自动在后面补'\0'
		void resize(size_t n, char c = '\0') {
			if (n <= _size) {
				_str[n] = '\0';
				_size = n;
			}
			else {
				reserve(n);
				for (int i = _size; i < n; i++)
					_str[i] = c;
				_size = n;
				_str[_size] = '\0';
			}

		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

3. Element access —— 访问成员

        接下来我们将完成的函数为可以访问到串中元素的函数,如下:

        上图中有四个函数,但是我们我们经常使用用的函数为第一个运算符重载函数。使用该函数可以向访问数组内容一样访问串中的元素,并且还可以对串中的元素进行修改,对于其实现如下:

		// []运算符重载
		const char& operator[](size_t pos) const {
            // 判断获取的位置是否合法
			assert(pos < _size);
			return _str[pos];
		}

		char& operator[](size_t pos) {
			assert(pos < _size);
			return _str[pos];
		}

        对于以上函数,我们仍然重载了两个,一个普通版,一个被 const 修饰的版本。

4. modify —— 修改串内容

        接下来的内容轮到了 string 的重头戏,也就是对 string 中的内容进行修改,具体将要掌握的函数如下:

        在讲解这些函数之前,我们先介绍一个常数 npos = -1,对于该值,若我们将其赋值给一个无符号整数,结果类型转化,该无符号整数将会变为最大的整型数。

        我们先介绍 push_back 函数,其功能为:在串的后面加上压入一个字符元素,实现起来也相对简单,唯一需要注意的是,我们需要判断是否需要扩容,实现如下:

		void push_back(char c) {
			// 需要先判断
			if (_size == _capacity)
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			_str[_size++] = c;
			_str[_size] = '\0';
		}

        接下来介绍 append 函数,append 函数有很多重载函数,其中我们最常用为在后面追加字符串,其中需要注意仍为:判断函数是否需要扩容。具体实现如下:

		// 追加字符串
		void append(const char* str) {
			// 判断是否需要扩容
			int len = strlen(str);
			if (len > _capacity - _size)
				reserve(len + _size + 1);
            // 使用strcpy 库函数进行拷贝
			strcpy(_str + _size, str);
			_size += len;
		}

        接着是我们 swap 函数,对于 swap 函数而言,我们不需要太多的拷贝,我们只需要将指针切换可以了,将 _str 的指针与目标串的指针交换,然后交换 _size 和 _capacity 的值,代码如下:

		void swap(string& str) {
			// 使用 std 中的 swap,若不指定,就会选择就近的 swap
			std::swap(str._str, _str);
			std::swap(str._size, _size);
			std::swap(str._capacity, _capacity);
		}

        接着是我们运算符重载函数 operator+= ,对于这个函数而言,也是一个常用的函数,常用的重载有在后面 += 一个字符或者一个串,我们只需要在函数中套用 push_back 与 append 函数即可,如下:

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

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

        然后介绍一个相对较难实现的函数:insert 函数,该函数的功能为在指定位置插入一个串或者一个一个字符,通常的使用中,我们并不建议使用该函数,因为需要移动串中的元素,效率较为低下,实现的代码如下:

		string& insert(size_t pos, char c) {
			assert(pos <= _size);
			// 开始循环后移,判断是否需要扩容
			if (_size == _capacity)
				reserve(2 * _capacity);
			int end = _size;
			// 需要强制类型转换,防止出现类型转化,进入无限循环
			while (end >= (int)pos) {
				_str[end + 1] = _str[end];
				end--;
			}
			_str[pos] = c;
			_size++;
			return *this;
		}

		string& insert(size_t pos, const char* str) {
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len > _capacity - _size)
				reserve(_size + len + 1);
			// 进行循环拷贝
			int end = len + _size;
			while (end >= (int)pos + len) {
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			//for (int i = 0; i < len; i++)
			//	_str[i + pos] = str[i];
			return *this;
		}

        最后就是我们 erase 函数,该函数的功能为删除从 pos 位置开始的 len 个元素,同时还是一个全缺省函数,若不指定 pos 和 len,那么将会删除所有的元素,我们在删除的时候,也需要判断从 pos 位置开始的 len 个元素是否会大于等于 _size ,若小于,则需要移动覆盖元素,实现如下:

		string& erase(size_t pos = 0, size_t len = npos) {
			assert(pos < _size);
			if (pos + len >= _size) {
				_str[pos] = '\0';
				return *this;
			}
			// 往前覆盖
			int begin = pos + len;
			while (begin <= _size) {
				_str[begin - len] = _str[begin];
				begin++;
			}
			return *this;
		}

5. string operations —— string 的操作

        接下来将要讲解的是 string 中的一些其他操作,如下:

        其中红框框住的这些是我们需要模拟实现的,至于其他的函数,要求掌握的程度不高,需要使用的时候,查看文档即可。

        首先要介绍的是 c_str 函数,对于这个函数而言,其作用为在 string 中得到一个像在 C语言中的一个字符串,设计这个函数的目的之一就是为了兼容 C 语言。对于该函数的实现还是较为的简单,只需要返回 string 中字符串的首地址即可,对于函数的修饰,直接使用 const 修饰,因为传过去的值不能被修改,如下:

		const char* c_str() const {
			return _str;
		}

        接下来将要实现的函数为 findrfind 函数,这两个函数都作用为在 string 中查找从 pos 位置查找字符或者查找字符串,返回查找到的第一个位置,find 和 rfind 的区别为 find 为从前面开始查找, rfind 为从后面开始查找,这两个函数仅仅为查找,所以不需要修改 string 的值,需要使用 const 修饰。实现原理如下:

		size_t find(const char* str, size_t pos = 0) const {
			// 普通写法
			//if (pos >= _size)
			//	return npos;
			//size_t len = strlen(str);
			//
			//for (int i = pos; i < _size; i++) {
			//	int j = 0;
			//	for (; j < len; j++) {
			//		if (_str[i + j] != str[j])
			//			break;
			//	}
			//	if (j == len)
			//		return i;
			//}
			if (pos >= _size)
				return npos;
			// 使用 strstr 函数查找子串,返回子串的位置
			const char* p = strstr(_str + pos, str);
			if (p)
				return p - _str;
			return npos;
		}

		size_t find(char c, size_t pos = 0) const {
			// 若找不到,返回 npos
			if (pos >= _size)
				return npos;
			for (int i = pos; i < _size; i++) {
				if (_str[i] == c)
					return i;
			}
			return npos;
		}

		size_t rfind(const char* str, size_t pos = npos) const {
			int len = strlen(str);
			if (pos >= _size) {
				// 当 pos 大于 _size 直接从 _size - len 位置开始找
				for (int i = _size - len; i >= 0; i--) {
					int j = 0;
					for (; j < len; j++) {
						if (str[j] != _str[i + j])
							break;
					}
					if (j == len)
						return i;
				}
			}
			else {
				for (int i = pos - len; i >= 0; i--) {
					int j = 0;
					for (; j < len; j++) {
						if (str[j] != _str[i + j])
							break;
					}
					if (j == len)
						return i;
				}
			}
			return npos;
		}

		size_t rfind(char c, size_t pos = npos) const {
			// 若 pos == npos 从最后开始找
			if (pos >= _size) {
				for (int i = _size - 1; i >= 0; i--) {
					if (_str[i] == c)
						return i;
				}
			}
			else {
				for (int i = pos; i >= 0; i--) {
					if (_str[i] == c)
						return i;
				}
			}
			return npos;
		}

        最后需要实现的函数为 substr ,拷贝子串,从一个 string 中拷贝这个 string 的一个子串过去,实现如下:

		string substr(size_t pos = 0, size_t len = npos) const {
			// 只需要函数拷贝过去就可以了,需要判断获取子串的位置是否合法,若等于_size,那么就传递一个空串过去
			assert(pos <= _size);

			// 直接传一个串过去,让在返回值是进行构造,
			if (len > _size - pos) 
				return _str + pos;
			

			string tmp;
			strncpy(tmp._str, _str + pos, len);
			// 还需要在后面加上 /0
			tmp._size = len;
			tmp += '\0';
			return tmp;

		}

 6. 默认成员函数

        接下来将会讲解 string 中的默认成员函数,其中包括构造函数,拷贝构造函数,赋值运算符重载,析构函数,如下:

        首先我们先讲解构造函数析构函数,对于构造函数而言,我们只需要将其设置为一个全缺省函数即可,当在初始化不传值的时候,默认初始值为一个空串,然后我们进行开辟空间,赋值即可。析构函数则将空间释放即可,实现如下:

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

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

        接下来为拷贝构造函数,对于拷贝构造函数而言,就是将 string 进行拷贝一份即可,所以我们只需要对 _str 按照 string.capacity() 进行开辟空间然后将传过来的 string._str 拷贝过去即可,然后给容量和size赋值。

        以上这种写法为常规写法,另一种写法为:在拷贝构造函数中创建一个临时 string 变量,然后使用传过来的 string._str,对其进行初始化,也就是使用构造函数,然后调用 swap 函数交换 tmp 和 *this 的值。(不够这种写法需要将成员进行初始化,将 _str 初始化为 nullptr,因为交换之后,会对 tmp 调用析构函数,释放空间时只需要释放空指针处的空间,这样才不会报错)         

        代码如下:

		//string(const string& s) {
		//	_str = new char[s.capacity() + 1];
		//	strcpy(_str, s.c_str());
		//	_size = s.size();
		//	_capacity = s.capacity();
		//}
		string(const string& s) {
			// 调用构造函数
			string tmp(s._str);
			swap(tmp);
			// 出了函数之后,会将tmp进行析构,防止释放野生空间 ---> 成员初始化nullptr
		}

        最后为赋值运算符重载,赋值运算符重载的思路基本和拷贝构造函数一致,创建出空间,然后拷贝,最后将 *this 返回即可。当然这也是普通写法。

        另一种写法如下,在注释中解释:

		//string& operator=(const string& s) {
		//	char* tmp = new char[s.capacity() + 1];
		//	strcpy(tmp, s.c_str());
		//	delete[] _str;
		//	_str = tmp;
		//	_capacity = s.capacity();
		//	_size = s.size();
		//	
		//	return *this;
		//}

		// 对传过来的串进行调用构造函数,将值构造给s,然后交换s和*this的值
		// 析构的时候自然会将原来*this的值给析构掉。
		string& operator=(string s) {
			swap(s);
			return *this;
		}
		
		string& operator=(const string& s) {
			// 调用构造函数
			string tmp(s._str);
			// 然后交换tmp和*this的值
			swap(tmp);
			return *this;
		}

7. 流提取和其他运算符重载

        最后需要介绍的知识点便是流提取和其他运算符的重载,流提取是为了方便的使用string 串,因为流提取的特殊性,我们需要将流提取定义在类外,另外在介绍一个 getline 函数,获取一行的信息放入到 string 中,这个函数定义在类中,实现如下:

	ostream& operator<<(ostream& _cout, const MyString::string& s) {
		//_cout << s.c_str();
		// 使用范围for进行遍历打印
		for (auto ch : s)
			_cout << ch;
		return _cout;
	}

	istream& operator>>(istream& _cin, MyString::string& s) {
		//_cin >> s._str;
		// 以下这样写不需要访问private成员,可以不用友元
		s.clear();
		char buff[128];
		char ch = _cin.get();
		int i = 0;
		while (ch != ' ' && ch != '\n') {
			//s += ch;
			//ch = _cin.get();
			buff[i++] = ch;
			if (i == 127) {
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = _cin.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return _cin;
	}

	istream& getline(istream& _cin, MyString::string& s) {
		s.clear();
		char ch = _cin.get();
		int i = 0;
		char buff[128];
		while (ch != '\n') {
			//s += ch;
			//ch = _cin.get();
			buff[i++] = ch;
			if (i == 127) {
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = _cin.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return _cin;
	}

        如上的流提取我们不需要将其在类中进行友元的声明,因为并没有使用到被 private 修饰的成员变量。对于 >> 流提取而言,我们设置一个 buff 的原因是,将读取进来的字符存入 buff 中,当 buff 满了,获取读取结束了,便将 buff 中的串放入到 string 中去,这样的好处在于,我们不需要频繁的对 string 进行开辟空间,而且每次开辟的空间大小都刚好合适

        加下来便是其他的运算符重载函数,如下:

		bool operator<(const string& s) {
			if (strlen(s.begin()) == 0)
				return false;
			if (strlen(s.begin()) != 0 && _size == 0)
				return true;
			int ret = strcmp(_str, s.begin());
			return ret < 0;
		}

		bool operator<=(const string& s) {
			// 直接判断相等
			return *this < s || *this == s;
		}

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

		bool operator>=(const string& s) {
			return *this > s || *this == s;
		}

		bool operator==(const string& s) {
			if (strlen(s.begin()) != _size)
				return false;
			int ret = strcmp(_str, s.begin());
			return ret == 0;
		}

		bool operator!=(const string& s) {
			return !(*this == s);
		}

8. 浅谈本篇中的 string

        对于本篇中的 string 而言,其实现的原理是最简单的一种原理,只是用一个字符指针和两个常量来定义串,而在 vs 和 linux 下的实现就更显复杂。如下:

        vs 下的 string 结构:

        string 总共占28个字节,内部结构为一个联合体,联合体用来定义 string 中字符串的存储空间:

        1. 当字符串长度小于16的时候,使用从内部固定的字符数组来存放;

        2. 当字符串长度大于等于16时,从堆上开辟出空间。

union _Bxty
{ // storage for small buffer or pointer to larger one
    value_type _Buf[_BUF_SIZE];
    pointer _Ptr;
    char _Alias[_BUF_SIZE]; // to permit aliasing
} _Bx

        28字节的来历:

        大多数情况下字符串的长度都小于16,那string对象创建好之后,内部已经有了16个字符数组的固定空间,不需要通过堆创建,效率高。16

        其次:还有一个size_t字段保存字符串长度,一个size_t字段保存从堆上开辟空间总的容量。  4 + 4
        最后:还有一个指针做一些其他事情。 ---> 16 + 4 + 4 + 4

        g++ 下的 string 结构

        G++下,string是通过写时拷贝实现的,string对象总共占4个字节,内部只包含了一个指针,该指针将来指向一块堆空间,内部包含了如下字段:

        1. 总空间大小

        2. 字符串有效长度

        3. 引用计数

struct _Rep_base
{
    size_type _M_length;
    size_type _M_capacity;
    _Atomic_word _M_refcount;
};

9. All Code

namespace MyString {
	class string {
	public:
		static const int npos = -1;
		typedef char* iterator;
		typedef const char* const_iterator;

		const size_t size() const {
			return _size;
		}
		
		const size_t capacity() const {
			return _capacity;
		}

		// 构造函数
		string(const char* str = "")
			:_size(strlen(str))
		{
			_str = new char[_size + 1];
			_capacity = _size;
			strcpy(_str, str);
		}	

		// 拷贝构造函数 的两种写法
		
		//string(const string& s) {
		//	_str = new char[s.capacity() + 1];
		//	strcpy(_str, s.c_str());
		//	_size = s.size();
		//	_capacity = s.capacity();
		//}
		string(const string& s) {
			// 调用构造函数
			string tmp(s._str);
			swap(tmp);
			// 出了函数之后,会将tmp进行析构,防止释放野生空间 ---> 初始化nullptr
		}

		// 赋值运算符重载 --> 需要修改
		//string& operator=(const string& s) {
		//	char* tmp = new char[s.capacity() + 1];
		//	strcpy(tmp, s.c_str());
		//	delete[] _str;
		//	_str = tmp;
		//	_capacity = s.capacity();
		//	_size = s.size();
		//	
		//	return *this;
		//}

		// 对传过来的串进行调用构造函数,将值构造给s,然后交换s和*this的值
		// 析构的时候自然会将原来*this的值给析构掉。
		string& operator=(string s) {
			swap(s);
			return *this;
		}
		
		string& operator=(const string& s) {
			// 调用构造函数
			string tmp(s._str);
			// 然后交换tmp和*this的值
			swap(tmp);
			return *this;
		}

		// 析构函数
		~string() {
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}
		// c_str 
		const char* c_str() const {
			return _str;
		}

		// 迭代器
		iterator begin() {
			return _str;
		}

		const_iterator begin() const {
			return _str;
		}

		iterator end() {
			return _str + _size;
		}

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

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

		}
		
		// 获取第 pos 个位置的元素
		char& operator[](size_t pos) {
			assert(pos < _size);
			return _str[pos];
		}
		
		const char& operator[](size_t pos) const {
			assert(pos < _size);
			return _str[pos];
		}

		// 对数据进行扩容
		void reserve(size_t n) {
			// 仅仅 n 大于 capacity 的时候,我们才进行扩容
			if (n > _capacity) {
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = n;
			}
		}

		// 追加字符串
		void append(const char* str) {
			// 判断是否需要扩容
			int len = strlen(str);
			if (len > _capacity - _size) 
				reserve(len + _size + 1);
			strcpy(_str + _size, str);
			_size += len;
		}

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

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

		void push_back(char c) {
			// 需要先判断
			if (_size == _capacity)
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			_str[_size++] = c;
			_str[_size] = '\0';
		}

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

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

		//void swap(string& str) {
		//	
		//	// 先进行扩容
		//	if (_capacity > str.capacity())
		//		str.reserve(_capacity);
		//	else
		//		reserve(str.capacity());
		//	// 然后进行交换,还要交换size
		//	size_t max = _capacity > str.capacity() ? _capacity : str.capacity();
		//	char* tmp = new char[max + 1];
		//	strcpy(tmp, str.begin());
		//	str.clear();
		//	for (int i = 0; i <= _size; i++)
		//		str += _str[i];
		//	strcpy(_str, tmp);
		//	_size = str.size();
		//	// 修改str的size
		//}

		void swap(string& str) {
			// 使用 std 中的 swap,若不指定,就会选择就近的 swap
			std::swap(str._str, _str);
			std::swap(str._size, _size);
			std::swap(str._capacity, _capacity);
		}

		// 查找函数
		size_t find(char c, size_t pos = 0) const {
			// 若找不到,返回 npos
			if (pos >= _size)
				return npos;
			for (int i = pos; i < _size; i++) {
				if (_str[i] == c)
					return i;
			}
			return npos;
		}

		size_t find(const char* str, size_t pos = 0) const {
			// 普通写法
			//if (pos >= _size)
			//	return npos;
			//size_t len = strlen(str);
			//
			//for (int i = pos; i < _size; i++) {
			//	int j = 0;
			//	for (; j < len; j++) {
			//		if (_str[i + j] != str[j])
			//			break;
			//	}
			//	if (j == len)
			//		return i;
			//}
			if (pos >= _size)
				return npos;
			// 使用 strstr 函数查找子串,返回子串的位置
			const char* p = strstr(_str + pos, str);
			if (p)
				return p - _str;
			return npos;
		}

		// 从后面开始找,没有找到仍然返回npos
		size_t rfind(char c, size_t pos = npos) const {
			// 若 pos == npos 从最后开始找
			if (pos >= _size) {
				for (int i = _size - 1; i >= 0; i--) {
					if (_str[i] == c)
						return i;
				}
			}
			else {
				for (int i = pos; i >= 0; i--) {
					if (_str[i] == c)
						return i;
				}
			}
			return npos;
		}

		size_t rfind(const char* str, size_t pos = npos) const{
			int len = strlen(str);
			if (pos >= _size) {
				for (int i = _size - len; i >= 0; i--) {
					int j = 0;
					for (; j < len; j++) {
						if (str[j] != _str[i + j])
							break;
					}
					if (j == len)
						return i;
				}
			}
			else {
				for (int i = pos - len; i >= 0; i--) {
					int j = 0;
					for (; j < len; j++) {
						if (str[j] != _str[i + j])
							break;
					}
					if (j == len)
						return i;
				}
			}
			return npos;
		}

		string& insert(size_t pos, char c) {
			assert(pos <= _size);
			// 开始循环后移,判断是否需要扩容
			if (_size == _capacity)
				reserve(2 * _capacity);
			int end = _size;
			// 需要强制类型转换
			while (end >= (int)pos) {
				_str[end + 1] = _str[end];
				end--;
			}
			_str[pos] = c;
			_size++;
			return *this;
		}

		string& insert(size_t pos, const char* str) {
			assert(pos <= _size);
			size_t len = strlen(str);
			if (len > _capacity - _size)
				reserve(_size + len + 1);
			// 进行循环拷贝
			int end = len + _size;
			while (end >= (int)pos + len) {
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			//for (int i = 0; i < len; i++)
			//	_str[i + pos] = str[i];
			return *this;
		}

		string& erase(size_t pos = 0, size_t len = npos) {
			assert(pos < _size);
			if (pos + len >= _size) {
				_str[pos] = '\0';
				return *this;
			}
			// 往前覆盖
			int begin = pos + len;
			while (begin <= _size) {
				_str[begin - len] = _str[begin];
				begin++;
			}
			return *this;
		}

		friend ostream& operator<<(ostream& _cout, const MyString::string& s);

		friend istream& operator>>(istream& _cin, MyString::string& s);

		// 子串
		string substr(size_t pos = 0, size_t len = npos) const {
			string ret;
			if (len >= _size - pos) {
				for (int i = pos; i < _size; i++)
					ret += _str[i];
			}
			else {
				for (int i = pos; i < len + pos; i++)
					ret += _str[i];
			}
			return ret;
		}

		//relational operators
		bool operator<(const string& s) {
			if (strlen(s.begin()) == 0)
				return false;
			if (strlen(s.begin()) != 0 && _size == 0)
				return true;
			int ret = strcmp(_str, s.begin());
			return ret < 0;
		}

		bool operator<=(const string& s) {
			// 直接判断相等
			return *this < s || *this == s;
		}

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

		bool operator>=(const string& s) {
			return *this > s || *this == s;
		}

		bool operator==(const string& s) {
			if (strlen(s.begin()) != _size)
				return false;
			int ret = strcmp(_str, s.begin());
			return ret == 0;
		}

		bool operator!=(const string& s) {
			return !(*this == s);
		}

		istream& getline(istream& _cin, MyString::string& s) {
			s.clear();
			char ch = _cin.get();
			int i = 0;
			char buff[128];
			while (ch != '\n') {
				//s += ch;
				//ch = _cin.get();
				buff[i++] = ch;
				if (i == 127) {
					buff[i] = '\0';
					s += buff;
					i = 0;
				}
				ch = _cin.get();
			}
			if (i > 0) {
				buff[i] = '\0';
				s += buff;
			}
			return _cin;
		}


	private:
		char* _str = nullptr;
		size_t _capacity = 0;
		size_t _size = 0;
	};

	ostream& operator<<(ostream& _cout, const MyString::string& s) {
		//_cout << s.c_str();
		// 使用范围for进行遍历打印
		for (auto ch : s)
			_cout << ch;
		return _cout;
	}

	istream& operator>>(istream& _cin, MyString::string& s) {
		//_cin >> s._str;
		// 以下这样写不需要访问private成员,可以不用友元
		s.clear();
		char buff[128];
		char ch = _cin.get();
		int i = 0;
		while (ch != ' ' && ch != '\n') {
			//s += ch;
			//ch = _cin.get();
			buff[i++] = ch;
			if (i == 127) {
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = _cin.get();
		}
		if (i > 0) {
			buff[i] = '\0';
			s += buff;
		}
		return _cin;
	}

}

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

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

相关文章

Pytest小技巧:高效获取自动化测试结果

自动化测试用例在执行完成后&#xff0c;我们想要很清楚的查看到测试用例的执行结果&#xff0c;我们可以通过Pytest中的Hooks来进行获取吗&#xff1f; 其中Pytest中存在多个Hooks的函数&#xff0c;小编今天先简单介绍其中一种&#xff0c;通过pytest_runtest_makereport 获…

若依vue中关于字典的使用

文章目录 字典管理页面列表点击某个字典类型展示具体字典数据修改某一条字典数据 字典的应用一般用于select多选框中代码实现根据字典Dict的value获取Label&#xff0c;类似于通过key获得value 源码解析 字典管理页面 列表 点击某个字典类型展示具体字典数据 修改某一条字典数…

04_UART串口发送数据

1.配置芯片&#xff0c;如果PA9,PA10的UART引脚被占用&#xff0c;会自动进行重映射 2.代码 int main(void) {uint8_t temp[]"test";/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*…

windows的jar包开机自启动【搬代码】

感觉最方便的就是放到启动项目里操作步骤 winR 输入&#xff1a;shell:startup回车或点击确定 3.将自己jar包右键创建快捷方式 4.然后放进去 5.重启电脑&#xff0c;浏览器输入网址&#xff0c;就可以看到重启成功了 另外一个就是放入.exe文件的快捷方式 首先&#xff0c;…

C语言洛谷题目分享(9)奇怪的电梯

目录 1.前言 2.题目&#xff1a;奇怪的电梯 1.题目描述 2.输入格式 3.输出格式 4.输入输出样例 5.说明 6.题解 3.小结 1.前言 哈喽大家好啊&#xff0c;前一段时间小编去备战蓝桥杯所以博客的更新就暂停了几天&#xff0c;今天继续为大家带来题解分享&#xff0c;希望大…

网络管理实验二、SNMP服务与常用的网管命令

1 常用的网管命令 1.1 网络状态监视命令 包括以下命令&#xff1a;Ipconfig、ping、nslookup、dig、host ipconfig 作用&#xff1a;用来显示本机所有网卡的基本信息&#xff08;IP、掩码、网关、工作状态&#xff09;&#xff1b;用法&#xff1a;ipconfig展示&#xff1a;…

Python的国际化和本地化【第162篇—国际化和本地化】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 随着全球化的发展&#xff0c;多语言支持在软件开发中变得越来越重要。Python作为一种流行的…

软件架构静态演化

1.静态演化需求 软件架构静态演化的需求是广泛存在的&#xff0c;可以归结为两个方面。 &#xff08;1&#xff09;设计时演化需求。在架构开发和实现过程中对原有架构进行调整&#xff0c;保证软件实现与架构的一致性以及软件开发过程的顺利进行。 &#xff08;2&#xff09;运…

二期 1.3 Spring Cloud Alibaba微服务组件Nacos注册中心介绍

文章目录 一、注册中心有什么用?二、注册中心对比三、Nacos是什么?3.1 Nacos 基本概念3.2 Nacos 主要功能3.3 Nacos 优势一、注册中心有什么用? 谈起微服务架构,总会提到注册中心,它是微服务架构必不可少的组件之一,那么注册中心作用到底是什么? 话说微服务架构下 服务…

Qt---控件的基本属性

文章目录 enabled(控件可用状态)geometry(位置和尺寸)简单恶搞程序 windowIcon(顶层 widget 窗口图标)使用 qrc 机制 windowOpacity(窗口的不透明值)cursor(当鼠标悬停空间上的形状)自定义鼠标图标 toolTip(鼠标悬停时的提示)focusPolicy(控件获取焦点的策略)styleSheet(通过CS…

Navicat连接SQL server出现:[IM002] [Microsoft][ODBC 驱动程序管理器] 未发现数据源名称并且未指定默认驱动程序(0)

问题 解决方法 一 找到Navicat的安装路径&#xff0c;然后找到sqlncli_x64.msi文件并安装&#xff0c;安装成功后重启Navicat重新进行连接&#xff0c;看是否成功。 解决方法 二 如果方法一没有找到找到sqlncli_x64.msi 还是Navicat的安装路径&#xff0c;然后找到msodbcsql_64…

【网络编程】Linux网络内核结构以及分布剖析

hello &#xff01;大家好呀&#xff01; 欢迎大家来到我的网络编程系列之Linux网络内核结构以及分布剖析&#xff0c;在这篇文章中&#xff0c;你将会学习到在Linux内核中如何实现网络数据的输入和输出的&#xff0c;并且我会给出源码进行剖析&#xff0c;以及手绘UML图来帮助…

实现iOS App代码混淆

简介 在开发iOS应用程序时&#xff0c;保护代码安全是至关重要的。代码混淆是一种常用的技术&#xff0c;可以增加逆向工程的难度&#xff0c;防止他人对代码的篡改和盗用。本文将介绍如何实现iOS App代码混淆的步骤和操作方法。 整体流程 下面是实现iOS App代码混淆的整体流…

Cosmopolitan Libc 工作原理与多平台使用方法教程(x64 Linux / WSL2 / Windows)

⚠️阅读前请注意 本博客适用于Cosmopolitan Libc 3.X版本&#xff0c;不适用于Cosmopolitan Libc 2.X版本。Cosmopolitan Libc 是一个非常年轻的项目&#xff0c;可能存在各种问题。Cosmopolitan Libc 仍处于快速迭代开发之中&#xff0c;本文内容在一定时期内会持续更新。 Co…

xhci 数据结构

xhci 数据结构 xhci 数据结构主要在手册上有详细的定义&#xff0c;本文根据手册进行归纳总结&#xff1a; 重点关注的包括&#xff1a; device contexttrb ringtrb device context设备上下文 设备上下文数据结构由xHC管理&#xff0c;用于向系统软件报告设备配置和状态信息。…

Java反序列化基础-类的动态加载

类加载器&双亲委派 什么是类加载器 类加载器是一个负责加载器类的对象&#xff0c;用于实现类加载的过程中的加载这一步。每个Java类都有一个引用指向加载它的ClassLoader。而数组类是由JVM直接生成的&#xff08;数组类没有对应的二进制字节流&#xff09; 类加载器有哪…

Qt 3 QVariant类的使用和实例

QVariant, 类本质为 C联合(Union)数据类型&#xff0c;它可以保存很多Qt 类型的值&#xff0c;包括 QBrush、QColor、QString 等等。也能够存放Qt的容器类型的值。QVariant::StringList 是 Qt定义的一个 QVariant::type 枚举类型的变量&#xff0c;其他常用的枚举类型变量如下表…

《QT实用小工具·二十五》日志重定向输出

1、概述 源码放在文章末尾 日志重定向输出&#xff0c;包含如下功能&#xff1a; 支持动态启动和停止。支持日志存储的目录。支持网络发出打印日志。支持输出日志上下文信息比如所在代码文件、行号、函数名等。支持设置日志文件大小限制&#xff0c;超过则自动分文件&#xf…

FreeBuf 全球网络安全产业投融资观察(3月)

综述 据不完全统计&#xff0c;2024年3月&#xff0c;全球网络安全市场共发生投融资事件53起&#xff0c;其中国内4起&#xff0c;国外49起。 3月全球络安全产业投融资统计表&#xff08;数据来源&#xff1a;航行资本、36氪&#xff09; 整体而言&#xff0c;国内4起投融资事…