Lesson 08 string类 (下)

C++:渴望力量吗,少年?

文章目录

  • 四、模拟实现string
  • 总结


四、模拟实现string

  所谓模拟实现,就是在理解的基础上自己用代码实现相同的功能,所以首先要了解string的特性和相应函数的功能。例如在最开始要考虑的就是string有哪些成员变量,要实现哪些函数,六大默认成员函数是使用默认的还是要自己显式定义……下面的代码已有很详细的注释,就不再另作说明了。

string.h代码如下:

#pragma once
#include

namespace GGYH
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		/*//这里就是C语言的知识点了,这个指针指向的内容不可修改,但是指针本身可以修改,下面内容在main函数中就可以看出区别
		//GGYH::string::const_iterator a = "aaaaa";
        //a = "bb";
        //a++;
        *a = '2';//不能修改指向的内容,但是可以修改指针(比如++、--或者指向别的内容)*/


		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

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

		/*string()//无参构造函数
			:_str(new char[1]{'\0'})//可以这么给,但是也可以利用全缺省和有参构造函数合并在一起写,如下
			,_size(0)
			,_capacity(0)
		{}*/

		//注意不能写string(const char* str = nullptr),因为后面会存在指针的解引用(比如strlen和strcpy都会报错)
        //也不能写string(const char* str = '\0'),因为左右类型都是不匹配的,可以写成string(const char* str = "\0")
		//但是这里是自己给一个斜杠零,string结尾还有一个斜杠零,相当于有两个斜杠零,也没有必要,所以最好的是下面的空串
		string(const char* str = "")//用缺省参数代替上面的无参构造(当string没有给值的时候默认是个空串,但是得开一个字节放斜杠零)
			:_size(strlen(str))//注意初始化列的初始化顺序(空串_size为0)
			, _capacity(_size)
		{
			//记得先开辟空间再拷贝,因为它现在只是一个指针
			_str = new char[_capacity + 1];//加1是给斜杠零的
			strcpy(_str, str);//拷贝时把斜杠零也拷贝过去了
		}

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

		// 传统写法
        // s2(s1)
		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		//	strcpy(_str, s._str);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}

		//现代写法
		void swap(string& s)//注意这是在string类中自己实现的swap
		{
			std::swap(_str, s._str);//调用的是中的函数swap
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}

		// s2(s1)
		string(const string& s)
			: _str(nullptr)//初始化是因为c++没有规定编译器必须对内置类型成员变量初始化,可能是随机值,为了防止调用析构函数时出错
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);//调用有参构造函数(不能写tmp(s),因为和有参构造函数的参数类型不兼容)
			//可以启动调试程序的监视窗口观察这些变量的值
			swap(tmp);//这个函数结束之后tmp首先调用析构
			//swap(s);
		}

		// 传统写法
		 s2 = s3
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[s._capacity + 1];
		//		strcpy(tmp, s._str);

		//		delete[] _str;//注意这个变量其实是s2之前的内容,所以需要先delete
		//		_str = tmp;
		//		_size = s._size;
		//		_capacity = s._capacity;
		//	}

		//	return *this;
		//}

		//现代写法
		// s2 = s3
		string& operator=(string tmp)
		{
			//这里可以不判断是否自己给自己赋值,因为这个函数是传值调用,执行到swap语句时tmp已经生成了,判不判断都无所谓
			//而且一般不会自己给自己赋值
			swap(tmp);//本来出了函数之后临时变量tmp也要调用相应的析构函数,s2刚好把不要的(也就是旧的)内容交给tmp去释放
			//注意这个过程中s3并不会受影响,还是原来的内容
			return *this;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);//空串时_size为0,也就是说不能访问下标为0的位置的字符

			return _str[pos];
		}

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

			return _str[pos];
		}

		size_t capacity() const
		{
			return _capacity;
		}

		size_t size() const
		{
			return _size;
		}

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

		void reserve(size_t n)//异地扩容
		{
			if (n > _capacity)//因为string只需要在空间不够的时候扩容,不需要缩小空间
			{
				char* tmp = new char[n + 1];//开辟新空间,加1是为了存放斜杠零
				strcpy(tmp, _str);//拷贝原来的数据到新的空间
				delete[] _str;//释放旧空间
				_str = tmp;//把指针指向新空间

				_capacity = n;//更新容量
			}
		}

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

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

		size_t find(char ch, size_t pos = 0)//从下标为pos的位置开始查找
		{
			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

			return npos;//根据这个函数原型的设定的返回值
		}

		size_t find(const char* sub, size_t pos = 0)
		{
			const char* p = strstr(_str + pos, sub);
			if (p)
			{
				return p - _str;
			}
			else
			{
				return npos;
			}
		}

		string substr(size_t pos, size_t len = npos)//从下标为pos的位置开始取长度为len的子串
		{
			string s;
			size_t end = pos + len;
			if (len == npos || pos + len >= _size) // 有多少取多少
			{
				len = _size - pos;
				end = _size;
			}

			s.reserve(len);
			for (size_t i = pos; i < end; i++)
			{
				s += _str[i];
			}

			return s;//返回值s是自定义类型,需要调用拷贝构造函数,然后s的生命周期结束会自动调用析构函数,如果没有显式定义拷贝构造函数,就是浅拷贝
		}


		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				//reserve(_capacity * 2);//这里不能写这个,因为test2中的s2就是空串,_capacity是0,这里扩容之后还是0,所以得用三目表达式
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

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

		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 ch)
		{
			push_back(ch);
			return *this;
		}

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

		// insert(0, 'x')
		void insert(size_t pos, char ch)//插入字符
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			size_t end = _size + 1;//要注意类型,无符号是不会小于零的,比较时容易出错,这里加1就是为了防止下面使用end作为下标出错
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

			_str[pos] = ch;
			_size++;
		}

		void insert(size_t pos, const char* str)//插入字符串
		{
			assert(pos <= _size);

			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			// 挪动数据
			int end = _size;
			while (end >= (int)pos)//注意无符号类型不小于零,所以可以类型转换,也可以像上一个函数那样处理
			{
				_str[end + len] = _str[end];
				--end;
			}

			strncpy(_str + pos, str, len);//不会把str的斜杠零一起拷贝过来
			_size += len;
		}

		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);

			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else//用后面的字符覆盖想要erase的字符
			{
				size_t begin = pos + len;
				while (begin <= _size)//主要要注意斜杠零
				{
					_str[begin - len] = _str[begin];
					++begin;
				}
				_size -= len;
			}
		}

		bool operator<(const string& s) const
		{
			return strcmp(_str, s._str) < 0;
		}

		bool operator==(const string& s) const
		{
			return strcmp(_str, s._str) == 0;
		}

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

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

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

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

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
	private:
		char* _str;//指向字符串的指针
		size_t _size;//string实际上的已经使用的空间(也就是不包括斜杠零在内的字符个数)
		size_t _capacity;//string的容量(可以理解为string目前能够存放多少个字节的数据)

		//const static size_t npos = -1;  // 特例,只有const的可以在这里定义
		//const static double npos = 1.1;  // 报错
    public:
		const static size_t npos;//为了方便后续实现某些函数的功能而定义
	};

	const size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)//流插入
	{
		/*for (size_t i = 0; i < s.size(); i++)
		{
			out << s[i];
		}*/
		//上面的和下面的都可以
		for (auto ch : s)//这里的范围for因为参数s为const所以需要先实现 const_iterator begin() const 和 const_iterator end() const
			out << ch;

		return out;
	}

	//istream& operator>>(istream& in, string& s)//流提取
	//{
	//	s.clear();//应该先清空原来的字符串的内容,否则就变成追加了

	//	char ch;
	//	//in >> ch;//之所以不用in是因为它取不到空格和换行符,会直接跳过这两个字符,导致下面的循环一直停不下来
	//	ch = in.get();//用get就可以解决上面的问题
	//	s.reserve(128);

	//	while (ch != ' ' && ch != '\n')//cin遇到空格或者回车会停止
	//	{
	//		s += ch;
	//		//in >> ch;
	//		ch = in.get();
	//	}

	//	return in;
	//}
    //上面的函数不够好,因为使用s.reserve(128)可能会造成资源浪费,不使用s.reserve(128)就得多次扩容,导致效率下降,所以改进一下
	istream& operator>>(istream& in, string& s)
	{
		s.clear();

		char buff[129];//这里虽然也是开辟了空间,但是它是一个局部变量,函数结束就释放资源,如果s.reserve(128)可能会使整个string太大
		size_t i = 0;

		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 128)
			{
				buff[i] = '\0';
				s += buff;//先把写的保存下来再继续往数组中写入数据
				i = 0;
			}
			ch = in.get();
		}

		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}

		return in;
	}

	void test_string1()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		string s2;
		cout << s2.c_str() << endl;

		for (size_t i = 0; i < s1.size(); i++)
		{
			cout << s1[i] << " ";
		}
		cout << endl;

		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			(*it)++;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto& ch : s1)
		{
			ch++;
			cout << ch << " ";
		}
		cout << endl;

		cout << s1.c_str() << endl;
	}

	void test_string2()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;
		s1.push_back(' ');
		s1.append("hello GGYH hello GGYH");

		cout << s1.c_str() << endl;

		s1 += '#';
		s1 += "*********************";
		cout << s1.c_str() << endl;

		string s2;
		s2 += '#';
		s2 += "*********************";
		cout << s2.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		cout << s1.c_str() << endl;

		s1.insert(5, '%');//在下标为5的位置插入%,原来下标为5以及之后的数据往后挪
		cout << s1.c_str() << endl;

		s1.insert(s1.size(), '%');
		cout << s1.c_str() << endl;

		s1.insert(0, '%');
		cout << s1.c_str() << endl;
	}

	void test_string4()
	{
		string s1("hello world");
		string s2("hello world");

		cout << (s1 >= s2) << endl;

		s1[0] = 'z';
		cout << (s1 >= s2) << endl;

		cout << s1 << endl;
		cin >> s1;
		cout << s1 << endl;

		/*char ch1, ch2;//通过这里的测试,可以发现其实换行和空格都只会被跳过,并不会被读取
		cin >> ch1 >> ch2;*/
	}

	void test_string5()
	{
		string s1("hello world");
		s1.insert(5, "abc");
		cout << s1 << endl;

		s1.insert(0, "xxx");
		cout << s1 << endl;

		s1.erase(0, 3);
		cout << s1 << endl;

		s1.erase(5, 100);
		cout << s1 << endl;

		s1.erase(2);
		cout << s1 << endl;
	}

	void test_string6()
	{
		string s1("hello world");
		cout << s1 << endl;

		s1.resize(5);
		cout << s1 << endl;

		s1.resize(25, 'x');
		cout << s1 << endl;
	}

	void test_string7()
	{
		string s1("test.cpp.tar.zip");
		//size_t i = s1.find('.');

		//string s2 = s1.substr(i);
		//cout << s2 << endl;

		string s3("https://legacy.cplusplus.com/reference/string/string/rfind/");
		//string s3("ftp://www.baidu.com/?tn=65081411_1_oem_dg");

		string sub1, sub2, sub3;
		size_t i1 = s3.find(':');
		if (i1 != string::npos)
			sub1 = s3.substr(0, i1);
		else
			cout << "没有找到i1" << endl;

		size_t i2 = s3.find('/', i1 + 3);//加3是为了跳过:之后的//
		if (i2 != string::npos)
			sub2 = s3.substr(i1 + 3, i2 - (i1 + 3));
		else
			cout << "没有找到i2" << endl;

		sub3 = s3.substr(i2 + 1);//剩下的全部取出来

		cout << sub1 << endl;
		cout << sub2 << endl;
		cout << sub3 << endl;
	}

	void test_string8()
	{
		string s1("hello world");//调用有参构造函数
		string s2 = s1;//调用拷贝构造
		cout << s1 << endl;
		cout << s2 << endl;

		string s3("xxxxxxxxxxxxxxxxxxx");
		s2 = s3;//先拷贝构造,再调用运算符重载

		cout << s2 << endl;
		cout << s3 << endl;
	}

	void test_string9()
	{
		string s1("hello world");
		cin >> s1;
		cout << s1 << endl;
		cout << s1.size() << endl;
		cout << s1.capacity() << endl;
	}

}


Test.cpp代码如下:

#define _CRT_SECURE_NO_WARNINGS 1
#include
#include
#include
#include
using namespace std;

#include"string.h"
int main()
{
	GGYH::test_string7();

	return 0;
}

总结

  string还有部分功能需要我们后面学习容器适配器等知识才能实现 ~

你可能感兴趣的:(Class,养成{C++};,算法,c++,开发语言)