String类的模拟实现

提醒:因本文章干货满满,没有什么废话,文字很多,大都在分析过程,仔细看一定会有收获!
  C++最重要的模块之一就是string类,很多人在这一节点被劝退,在本篇文章中小编将逐个为你们突破,分模块将string中几个重要的接口实现,如果对string类有困难的读者们,强烈推荐仔细阅读本文章。
在这里插入图片描述
  本章没有废话,全是干货,请读者们仔细阅读!!!!!!
💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜💜
1、string类的四个默认成员函数
  一个类有6个默认成员函数,其中取地址重载相关的成员函数这里不做讲解,我们实现剩下的四个默认成员函数:
  A:构造函数–主要完成初始化工作
  B:拷贝构造函数–同类对象的初始化
  C:赋值重载函数–把一个对象赋值给另一个对象
  D:析构函数–完成清理工作

  对于上面4个默认成员函数的概念、功能以及调用过程我们不在本章赘述,可以查看小编的其他文章。直接干代码!!!
1.1 构造函数
  构造函数需要对3个成员变量进行初始化,_size代表有效的数据个数,_capacity代表可容纳的有效数据,对于一个5byte大小的内存空间,其size最大为4,如果将’\0’看作标识符,我们认为capacity也为4,所以在初始化的时候,我们将_size,_capacity设置为一样的值,这时始终要注意还有一个’\0’需要留空间。
  此外,在形参中,我们采用缺省值的方式进行初始化:
  a:如果初始化的时候没有给定初始值,我们就默认给’\0’,此时_size,_capacity均为0。
  b:如果初始化的时候给定初始值,我们就按照初始值进行初始化,此时_size,_capacity的大小就是该字符串的有效长度,对于给_str数组初始化的时候,我们先创建一个和str同样大小的数组,这里需要注意的是要给’\0’预留一个字节的空间。然后调用stl中的strcpy函数,至此,构造函数的模拟已经实现。
1.2 拷贝构造
  拷贝构造和赋值重载都涉及深浅拷贝的问题,关于深浅拷贝的问题,小编单独写一篇文章来帮助大家更好的理解,在这里我们需要知道的是,string类完成的是深拷贝,不仅仅是将值进行拷贝,还要在内存中创建一块空间,存储相同的内容。

  拷贝构造完成的是同类型之间的拷贝初始化,关于拷贝构造的模拟实现,我们有两种方式:s2(s1)
方式一: 借用swap函数,首先我们将s2中的成员变量进行置0操作(关键!!),然后创建一个string类的临时对象tmp,并将s1的字符串对象传过去,此时string tmp(s._str);会去调用上面的构造函数,此时临时对象tmp中也存在3个成员变量,然后调用stl中的swap函数,将s2和s1的三个成员变量分别进行交换,这就保证s2的三个成员变量都是s1的了。重点来了,因为tmp是一个临时变量,出了作用域就要调用其析构函数,如果第一步不采用置空的操作,s2的随机值就会交换给s1,此时free、delete就会导致程序崩溃,但是free和delete是可以对空进行操作的,所以置空保证了程序的正常运行。至于s2为什么是随机值,因为s2没有调用构造函数,而且已经被定义出来了,系统会默认给一个随机值。
1.3 赋值重载
  赋值重载也是一种拷贝行为,拷贝构造是创建一个对象时,拿同类对象初始化的拷贝,这里的赋值重载时两个对象已经都存在了,都被初始化过了,现在想把一个对象,赋值拷贝给另一个对象。关于赋值重载的模拟实现,我们有两种方式:s3 = s1
方式一: 赋值重载同样完成的是深拷贝,我们这里采用传引用返回,直接调用swap函数,将s3的三个成员变量与s1的三个成员变量进行交换,这里s采用的传值传参,这里会发生一次深拷贝,将s1的三个成员变量拷贝给s,然后s再和s3进行交换,如果采用传引用传参会导致s1发生变化。最后将s2返回即可。

!!!拷贝构造和赋值重载的模拟实现中大量重复使用了swap函数,这里我们可以写一个swap函数用来实现代码的复用。!!!

1.4 析构函数
  析构函数的实现就比较简单了,就相当于置空操作,代码如下:

namespace XD
{
	class string
	{
	public:
		//1.1 构造函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[strlen(str) + 1];
			strcpy(_str, str);
		}
		//未代码复用前:
		/*
		//1.2 拷贝构造
		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);
		}
		//1.3 赋值重载
		string& operator=(string s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
			return *this;
		}
		*/
		//代码复用后:
		void swap(string& s)
		{
			//加一个域作用限定符,这样swap就会去全局域寻找,就会找到库函数中的swap
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		string(const string& s)
		{
			_str = nullptr;
			_size = 0;
			_capacity = 0;
			string tmp(s._str);
			swap(tmp);
		}
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		//1.4 析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

		//输出该对象
		char* c_str()const
		{
			return _str;
		}
		
	private:
		char* _str;
		size_t _capacity;
		size_t _size;
	};
}
//四个默认构造函数测试
void Test1()
{
	XD::string s1("Hello World!");
	cout << s1.c_str() << endl;

	XD::string s2(s1);
	cout << s2.c_str() << endl;

	XD::string s3;
	s3 = s1;
	cout << s3.c_str() << endl;

}

int main()
{
	Test1();

	return 0;
}

1.5 拷贝构造和赋值重载的方法二:
  这两个方法本质一样,唯一不同就是赋值重载的时候要先删除原s3,因为s1的大小不知道,即s1和s3内存空间大小不确定,所以要先删除s3,然后创建一个与s1同样大小的内存空间。

		//a:s2(s1)--拷贝构造
		string(const string& s)
		{
			_size = strlen(s._str);
			_capacity = _size;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);
		}
		//b:s3 = s1--赋值重载
		string& operator=(const string& s)
		{
			if (this != &s)//防止自己给自己赋值s1 = s1
			{
				delete[] _str;
				_size = strlen(s._str);
				_capacity = _size;
				_str = new char[strlen(s._str) + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}

程序运行结果如图:在这里插入图片描述
💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚💚
2、string类的增删改查
2.1 string的各种增操作
  不止是string,只要是数据的增操作,就涉及内存是否够用的问题,因为插入数据可能会存在增容的操作,所以在这里我们同时将reserve函数模拟实现用来增容,除了reserve还有一个resize,二者的区别的联系不用小编多说,大家应该知道,这里resize没有什么实质性的作用,小编还是给出了其模拟实现的代和讲解,帮助大家理解。

2.1.1 模拟实现resize
  string s1(“Linux”);
  s1 += ‘!’;
  resize()的情况分为多种:s1经过构造函数,_size=5,_capacity=5._str=“Linux”,然后+=’!'后,_size=6,_capcacity=10,_str=“Linux!”,即有10个有效空间大小的容量,存储有效数据6个,现在进行resize
a:resize(3) – resize的大小<_size
b:resize(8) – resize的大小>_size,但<_capacity
c:resize(15) – resize的大小>_capacity

//设置size
	void resize(size_t n, char c = '\0')
	{
		if (n < _size)
		{
			_str[n] = '\0';
			_size = n;
		}
		else
		{
			if (n > _capacity)
			{
				reserve(n);
			}
			for (size_t i = _size; i < n; i++)
			{
				_str[i] = c;
			}
			_str[n] = '\0';
			_size = n;
		}
	}

2.1.2 模拟实现reserve
  对于reserve,改变的是capacity,设置一个形参n,即当需要的capacity大于本身的_capacity时就需要扩容,在这里,我们创建一个临时数组tmp,其大小就为我们需要的空间n,切记预留一个’\0’的位置,然后利用strncpy函数。
  这里需要注意为什么不用strcpy,stcpy是以’\0’为拷贝的结束标志,如果原字符串_str后面有多个’\0’,且只有最后一个’\0’是作为标识符的,其它都是有效字符,而strcpy只会讲第一个’\0’拷贝过来,所以我们这里用strncpy,直接讲有效字符的长度拷贝过来,避免出错。
  然后置空原字符串_str,因为其空间不够了,然后改变_capacity参数即可。

//设置capacity
	void reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];
			strncpy(tmp, _str, _size + 1);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

2.1.3 模拟实现push_back
  不管在哪进行增加操作,刚上来就要进行判断是否进行增容,如果_size=_capacity,则表明原数组满了,需要增容,添加字符可以每次扩容原容量的2倍,但是需要注意的是,原容量可能为0,因为我们的默认构造函数的形参给的’\0’,所以这里需要主要,我们使用一个三目运算符来进行避免这个问题。
  不管是否进行扩容,代码只要执行到if语句下面,就说明此时空间够用,我们只需将字符
c添加到尾部,更新其_size即可,这里需要主要不要忘了将’\0’添加,因为原’\0’的位置被我们换成了字符c。

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

2.1.4 模拟实现append
  对于append的扩容就不能采取原容量的2倍,因为我们不知道插入的字符串到底有多大,如果很大,一直2倍扩容多次,效率很低,所以我们直接计算所需要的容量,进行扩容,这是一个需要注意的地方。
  然后将需要插入的字符串str,拷贝到原字符串数组_str的尾部,更新_size即可。

//append字符串
	void append(const char* str)
	{
		size_t len = strlen(str);
		if ((_size + len) > _capacity)
		{
			reserve(_size + len);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

2.1.5 +=重载
  对于+=运算符的重载,完全是复用push_back和append代码,这里不多加赘述

//重载+=字符
	string& operator+=(char c)
	{
		push_back(c);
		return *this;
	}

//重载+=字符串
	string& operator+=(const char* str)
	{
		append(str);
		return *this;
	}

2.1.6 模拟实现insert
  字符: 同样,对于添加字符,仍要判断是否扩容,方法同push_back添加字符,在挪动数据时有多种方式,我们这里采用指针的方式,定义一个尾指针指向数组的尾部,此时指向的是’\0’的位置,然后依次往后挪动数据,直到在pos位置停下来,因为插入数据是用下标的形式,所以就实现了在pos位置之前(pos下标)的位置插入数据。当数据挪动完后,将字符c插入pos下标位置,然后更新_size,并将其以引用的形式返回。
  字符串: 字符串的insert和字符的类似,首先判断是否扩容,这个和上面的append类似。在保证容量够的情况下开始挪动数据,此时挪动数据和添加字符挪动数据不同的是,添加字符挪动数据是后移一位,而添加字符串挪动数据,是往后挪动strlen(str)个,即挪动添加字符串长度,这样挪动完数据后,从pos位置开始,就会空出len长度的字符,用来插入str,此时仍需要注意的是,这里使用的是strncpy原理在2.1.1中讲过,这里不进行赘述。最后更新_size的数据,将结果返回即可。
  我们实现insert后,insert的代码可以让push_back和append进行复用,为了不让大家混淆,这里就不提供复用的代码,避免大家一头雾水,各位读者有没有感觉环环相扣的感觉,这也就是string的魅力之处。

		//在pos位置前插入字符c
		string& insert(size_t pos, char c)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			char* end = _str + _size;
			while (end > (_str + pos))
			{
				*(end + 1) = *end;
				--end;
			}
			_str[pos] = c;
			_size++;
			return *this;
		}
		// 在pos位置前插入字符串str
		string& insert(size_t pos, const char* str)
		{
			size_t len = strlen(str);
			if ((_size + len) > _capacity)
			{
				reserve(_size + len);
			}
			char* end = _str + _size;
			while (end > (_str + pos))
			{
				*(end + len) = *end;
				--end;
			}
			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}

运行结果如下:
在这里插入图片描述
  真的是干货满满,不知道各位读者有没有仔细阅读,能将本文章读完,你对string类将会有更深入的理解。加油!!!!!
在这里插入图片描述

2.2 string类的删除数据的函数模拟实现
  删除数据分两种情况
  a:剩余的字符长度不够删,即要删除的长度大于等于左边剩余的字符(后面全部删完)
  b:剩余的字符长度够删,即要删除的长度小于左边剩余的字符
  对于a情况,直接将pos位置的数据换为’\0’即可,然后更新_size,对于b情况,直接利用strcpy进行拷贝,因为strcpy以’\0’作为拷贝结束的标志,所以把末尾的’\0’标识符一块拷贝过去,再更新_size即可。
  对于b情况,即将len长度后面的数据拷贝到从pos位置开始的len长度即可,然后更新_size,这里我们在传参里面使用缺省参数将len=-1,对于无符号整数,-1是一个很大的整数,我们默认一个字符串的长度没有这么大,所以在没有指定长度的情况下,默认删除到最后。

	// 删除pos位置开始len长度的元素
	string& erase(size_t pos, size_t len = -1)
	{
		//leftnumber代表从pos位置开始到末尾,总共有多少数据
		size_t leftnumber = _size - pos;
		//a情况
		if (len > leftnumber)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		//b情况
		else
		{
			strcpy(_str + pos, _str + pos + len);
			_size -= len;
		}
		return *this;
	}

运行结果如下
在这里插入图片描述

2.3 string类中[]运算符的重载-用于修改数据
  我们知道对于内置类型的数组,我们可以使用下标的方式进行遍历,通过使用[]下标运算符,输出某一下标的数据,还可以通过下标对某一位置的数据进行修改,那么我们的string类可以使用这一功能吗?答案是可以。这时候我们就需要实现对[]运算符的重载。
  这个重载比较简单,我们直接在函数里面访问_str数组,然后使用[]操作符访问这个数组中的元素即可,使用引用返回可以实现对数据的修改。
  下面两个函数构成重载,这样就可以适应const和非const对象的使用。

	//*******************这个接口给const对象,只读******************
	char& operator[](size_t index)
	{
		return _str[index];
	}
	//*******************这个接口给非const对象,可读可写******************
	const char& operator[](size_t index)const
	{
		return _str[index];
	}

测试结果:
在这里插入图片描述
2.4 string类中查找字符或字符串函数模拟实现
  对于string类中的查找操作也分为查找字符和字符串,二者本质上是一样的,只不过查找字符串的时候调用了stl库中的strstr函数帮我们查找,但本质也是遍历数组,找到符合的并返回,strstr如果找到则返回对应字符串的起始位置下标,如果没找到则返回NULL。
  这两个接口比较简单,在这里不过多说明,大家看代码,如果不懂给小编留言,小编给大家解答。

// 返回c在string中第一次出现的位置
	size_t find(char c, size_t pos = 0) const
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == c)
			{
				return i;
			}
		}
		return -1;
	}
// 返回子串s在string中第一次出现的位置
	size_t find(const char* s, size_t pos = 0) const
	{
		const char* ret = strstr(_str + pos, s);
		if (ret)
		{
			return ret - _str;
		}
		else
		{
			return -1;
		}
	}

运行结果如下:
在这里插入图片描述

💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛💛
3、string类重载各种比较关系运算符
  关于各种运算符的重载,我们只需要实现<和==,其它的都复用即可。
  这里我们比较两个运算符,使用的是stl中的strcmp函数。

//比较关系的运算符重载
	bool operator<(const string& s)
	{
		return strcmp(_str,s._str) < 0;
	}
	bool operator==(const string& s)
	{
		return strcmp(_str, s._str) == 0;
	}
	bool operator<=(const string& s)
	{
		return ((_str < s._str) || (_str == s._str));
	}
	bool operator>(const string& s)
	{
		return !((_str < s._str) || (_str == s._str));
	}
	bool operator>=(const string& s)
	{
		return !(strcmp(_str, s._str) < 0);
	}
	bool operator!=(const string& s)
	{
		return !(strcmp(_str, s._str) == 0);
	}

运算结果在这里插入图片描述
💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙💙
4、string类的清空、计算大小、容量和判断空的接口实现
4.1 string类中的清空接口
  直接将数组第一个位置的元素改为’\0’,然后更改_size的值。

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

4.2 string类中的计算大小接口
  直接返回_size

//计算大小
	size_t size()const
	{
		return _size;
	}

4.3 string类中的计算容量接口
  直接返回_capacity

//计算容量
	size_t capacity()const
	{
		return _capacity;
	}

4.4 string类中的判断是否为空接口
  判断_size是否为0,然后将结果返回

//判断对象是否为空
	bool empty()const
	{
		return (_size == 0);
	}

运行结果如图:
在这里插入图片描述

  over!!!!坚持到这里的小伙伴相信一定会有所收获,我们已经将string类中常用的接口进行了模拟实现,文章末尾小编会给出本章的全部源代码,供大家学习,源代码中还会有迭代器的实现,范围for的解释,这几个知识点不作为本章内容的研究,有感兴趣的小伙伴可以私聊小编。
在这里插入图片描述
❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️❤️

完整代码:

namespace XD
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		//迭代器的本质是指针
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		const_iterator begin() const
		{
			return _str;
		}
		const_iterator end()const
		{
			return _str + _size;
		}

		//string的四个默认成员函数
		string(const char* str = "")
		{
			_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}
		//法一:
		//未代码复用前:
		/*
		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& operator=(string s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
			return *this;
		}
		*/
		//代码复用后:
		/*
		void swap(string& s)
		{
			//加一个域作用限定符,这样swap就会去全局域寻找,就会找到库函数中的swap
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}
		string(const string& s)
		{
			_str = nullptr;
			_size = 0;
			_capacity = 0;
			string tmp(s._str);
			swap(tmp);
		}
		string& operator=(string s)
		{
			swap(s);
			return *this;
		}
		*/
		//法二:
		//a:s2(s1)--拷贝构造
		string(const string& s)
		{
			_size = strlen(s._str);
			_capacity = _size;
			_str = new char[strlen(s._str) + 1];
			strcpy(_str, s._str);
		}
		//b:s3 = s1--赋值重载
		string& operator=(const string& s)
		{
			if (this != &s)//防止自己给自己赋值s1 = s1
			{
				delete[] _str;
				_size = strlen(s._str);
				_capacity = _size;
				_str = new char[strlen(s._str) + 1];
				strcpy(_str, s._str);
			}
			return *this;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = 0;
			_capacity = 0;
		}

		//输出该对象
		const char* c_str()const
		{
			return _str;
		}

		//设置capacity
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];//永远留一个位置给\0
				strncpy(tmp, _str, _size + 1);
				delete[] _str;

				_str = tmp;
				_capacity = n;
			}
		}

		//设置size
		/*
		string s1("Linux");
		s1 += '!';
		resize()的情况分为多种:s1经过构造函数,_size=5,_capacity=5._str="Linux",然后+='!'后,_size=6,_capcacity=10,_str="Linux!",即有10个有效空间大小的容量,存储有效数据6个,现在进行resize
		a:resize(3) -- resize的大小<_size
		b:resize(8) -- resize的大小>_size,但<_capacity
		c:resize(15) -- resize的大小>_capacity
		*/
		void resize(size_t n, char c = '\0')
		{
			if (n < _size)
			{
				_str[n] = '\0';
				_size = n;
			}
			else
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = c;
				}
				_str[n] = '\0';
				_size = n;
			}
		}


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

		//append字符串
		void append(const char* str)
		{
			size_t len = strlen(str);
			if ((_size + len) > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;
		}

		//重载+=字符
		string& operator+=(char c)
		{
			push_back(c);
			return *this;
		}

		//重载+=字符串
		string& operator+=(const char* str)
		{
			append(str);
			return *this;
		}

		// 在pos位置前插入字符c
		string& insert(size_t pos, char c)
		{
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			char* end = _str + _size;
			while (end > _str + pos)
			{
				*(end + 1) = *end;
				--end;
			}
			_str[pos] = c;
			_size++;
			return *this;
		}

		// 在pos位置前插入字符串str
		string& insert(size_t pos, const char* str)
		{
			size_t len = strlen(str);
			if ((_size + len) > _capacity)
			{
				reserve(_size + len);
			}
			char* end = _str + _size;
			while (end > (_str + pos))
			{
				*(end + len) = *end;
				--end;
			}
			strncpy(_str + pos, str, len);
			_size += len;

			return *this;
		}

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

		//计算大小
		size_t size()const
		{
			return _size;
		}

		//计算容量
		size_t capacity()const
		{
			return _capacity;
		}

		//判断对象是否为空
		bool empty()const
		{
			return (_size == 0);
		}



		//[]运算符的重载-可以用来遍历
		char& operator[](size_t index)
		{
			return _str[index];
		}
		const char& operator[](size_t index)const
		{
			return _str[index];
		}

		//比较关系的运算符重载
		bool operator<(const string& s)
		{
			return strcmp(_str,s._str) < 0;
		}
		bool operator==(const string& s)
		{
			return strcmp(_str, s._str) == 0;
		}
		bool operator<=(const string& s)
		{
			return ((_str < s._str) || (_str == s._str));
		}
		bool operator>(const string& s)
		{
			return !((_str < s._str) || (_str == s._str));
		}
		bool operator>=(const string& s)
		{
			return !(strcmp(_str, s._str) < 0);
		}
		bool operator!=(const string& s)
		{
			return !(strcmp(_str, s._str) == 0);
		}



		// 返回c在string中第一次出现的位置
		size_t find(char c, size_t pos = 0) const
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == c)
				{
					return i;
				}
			}
			return -1;
		}

		// 返回子串s在string中第一次出现的位置
		size_t find(const char* s, size_t pos = 0) const
		{
			const char* ret = strstr(_str + pos, s);
			if (ret)
			{
				return ret - _str;
			}
			else
			{
				return -1;
			}
		}


		//删除pos位置开始len长度的元素
		/*
		删除数据分两种情况
		a:剩余的字符长度不够删,即要删除的长度大于等于左边剩余的字符(后面全部删完)
		b:剩余的字符长度够删,即要删除的长度小于左边剩余的字符
		*/
		string& erase(size_t pos, size_t len = -1)
		{
			//leftnumber代表从pos位置开始到末尾,总共有多少数据
			size_t leftNum = _size - pos;
			//a情况
			if (len >= leftNum)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			//b情况
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

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


//输入输出运算符!!!!
ostream& operator<<(ostream& out, const string& s)
{
	for (auto ch : s)
	{
		out << ch;
	}
	return out;
}
istream& operator>>(istream& in, string& s)
{
	//要先把原来的东西清空,而不是加在后面,库里面有这个clear,我们上面自己实现
	s.clear();
	char ch;
	//这个地方会有问题,忽略空格或者回车,在while循环中,最后一个是回车或者空格,应该结束,但是没有结束,而是忽略掉了回车或者空格,即根本没拿到换行符,而是等待下一次接收,所以不对
	//因为默认回车或者空格为字符间的间隔符,in>>ch,直接会忽略
	//in >> ch;
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		//每进行一次+=,'\0'会自动添加到尾部,直到遇到退出的条件
		s += ch;
		//in >> ch;
		ch = in.get();
	}

	return in;
}

//有可能字符串本身就有空格,这时候我们需要获取一行的数据,即库中的getline
istream& getline(istream& in, string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	while (ch != '\n')
	{
		s += ch;
		ch = in.get();
	}

	return in;
}



//四个默认构造函数测试
void Test1()
{
	XD::string s1("Hello World!");
	cout << s1.c_str() << endl;

	XD::string s2(s1);
	cout << s2.c_str() << endl;

	XD::string s3;
	s3 = s1;
	cout << s3.c_str() << endl;
}
//添加字符、字符串测试--尾部插入字符push_back、尾部插入字符串append、重载+=、任意位置插入字符字符串insert
void Test2()
{
	XD::string s1("Hello World!");
	cout << s1.c_str() << endl;

	s1.push_back('x');
	s1.push_back('x');
	s1.push_back('x');
	cout << s1.c_str() << endl;

	s1.append("yyy");
	cout << s1.c_str() << endl;

	s1 += 'C';
	s1 += 'P';
	s1 += 'P';
	cout << s1.c_str() << endl;

	s1 += "hello everyone";
	cout << s1.c_str() << endl;

	s1.insert(0, 'q');
	s1.insert(3, 'q');
	s1.insert(8, 'q');
	cout << s1.c_str() << endl;

	s1.insert(0, "pppp");
	s1.insert(10, "pppp");
	cout << s1.c_str() << endl;
}
//重载比较运算符的测试
void Test3()
{
	XD::string s1("Hello");
	XD::string s2("Hello");
	cout << (s1 > s2) << endl;
	cout << (s1 == s2) << endl;

	XD::string s3("Hell");
	XD::string s4("Hello");
	cout << (s3 > s4) << endl;
	cout << (s3 < s4) << endl;

}
//重载[]运算符的测试
void Test4()
{
	XD::string s1("Hello");
	cout << s1[0] << endl;
	s1[1] = 'H';
	cout << s1.c_str() << endl;
}
//删除某一位置数据的测试
void Test5()
{
	XD::string s1("Hello");
	s1.erase(0,2);
	cout << s1.c_str() << endl;

	XD::string s2("Hello World!!!!!!!!!!");
	s2.erase(5);
	cout << s2.c_str() << endl;

	XD::string s3("Hello World");
	s3.erase(3,15);
	cout << s3.c_str() << endl;
}
//查找字符或字符串的测试
void Test6()
{
	XD::string s1("Hello World,My name is xd!");
	cout << s1.find('o') << endl;
	cout << s1.find("mm") << endl;
	cout << s1.find("My") << endl;
	cout << s1.find("My",20) << endl;
}
//清空、计算大小、容量和判断空的测试
void Test7()
{
	XD::string s1("Hello World!!");
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	cout << s1.empty() << endl;
	s1.clear();
	cout << s1.empty() << endl;
}
//迭代器的测试
void Test8()
{
	XD::string s1("Hello World!!");
	XD::string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//范围for是由迭代器支持的
	//依次取s1里面的值,依次赋值给ch,自动判断结束,自动迭代++
	//看起来很神奇,但是原理很简单,这个范围for会被编译器替换成迭代器形式
	//也就是说范围for是由迭代器支持的
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

int main()
{
	//Test1();
	//Test2();
	//Test3();
	//Test4();
	//Test5();
	Test6();
	//Test7();
	//Test8();

	return 0;
}
  • 26
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

做1个快乐的程序员

感谢支持,一起加油努力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值