深入剖析String类的底层实现原理

news2024/11/18 15:23:12

嘿嘿,家人们,今天咱们来模拟实现string,好啦,废话不多讲,开干!


1:string.h

1.1:构造函数与拷贝构造函数

1.1.1:写法一

1.1.2:写法二(给缺省值)

1.2:赋值运算符重载与operatror[]获取元素

1.3:容量与迭代器

1.4:reserve与resize

1.5:清空与判断是否为空

1.6:push_back与append

1.7:insert

1.7.1:插入字符

1.7.2:插入字符串

1.8:erase

1.9:operator+=

1.9.1:添加字符

1.9.2:添加字符串

1.91:find

1.91.1:查找字符

1.91.2:查找字符串 

1.92:substr与swap

1.93:非成员函数的重载

1.93.1:流插入与流提取的重载

1.93.2:其他非成员函数的重载

2:Test.cpp

2.1:构造函数与拷贝构造

2.1.1:测试写法一

2.1.2:测试写法二

2.2:测试赋值运算符重载与[]获取元素

2.3:测试迭代器与容量

2.4:测试reserve与resize 

2.4.1:测试resize

2.4.2:测试reserve

2.5:测试push_back与append

2.6:测试insert

2.6.1:测试插入字符

2.6.2:测试插入字符串

2.7:测试erase

2.8:测试operator+=

2.9:测试find

2.91:测试substr与swap

2.92:测试流插入与流提取

2.92.1:第一版流提取

2.92.2:第二版流提取

2.93:测试其他非成员函数

3:知识补充

3.1:深拷贝与浅拷贝

3.1.1:浅拷贝

​3.1.2:深拷贝

4:总代码

4.1:string.h

4.2:Test.cpp


1:string.h

1.1:构造函数与拷贝构造函数

1.1.1:写法一

#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;

//避免与库中的string冲突,因此使用命名空间
namespace MyString
{
	class string
	{
	public:
		string()
			:_str(new char[1])
			, _size(0)
			,_capacity(0)
		{
			//空字符串只有\0,
			_str[0] = '\0';
		}
		//构造非空字符串
		string(const char * str)
			//开辟空间,多开辟个空间存放\0
			:_str(new char[strlen(str) + 1])
		{
			//开辟空间
			_capacity = _size = strlen(str);
			//拷贝数据
			strcpy(_str, str);
		}


		//拷贝构造函数
		//s2(s1),使用s1拷贝构造s2
		string(string & s)
		{
			//开辟空间
			char* temp = new char[s._capacity + 1];
			_str = temp;
			_capacity = s._capacity;
			_size = s._size;
			//拷贝数据
			strcpy(_str, s._str);
		}

		//析构函数
		~string()
		{
			delete[] _str;
			_size = _capacity = 0;
		}
		
	private:
		//定义成员变量并且给缺省值
		char* _str;
		int   _size;
		int   _capacity;
	};

}

1.1.2:写法二(给缺省值)

#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;

//避免与库中的string冲突,因此使用命名空间
namespace MyString
{
	class string
	{
	public:
		//string()
		//	:_str(new char[1])
		//	, _size(0)
		//	,_capacity(0)
		//{
		//	//空字符串只有\0,
		//	_str[0] = '\0';
		//}
		构造非空字符串
		//string(const char * str)
		//	//开辟空间,多开辟个空间存放\0
		//	:_str(new char[strlen(str) + 1])
		//{
		//	//开辟空间
		//	_capacity = _size = strlen(str);
		//	//拷贝数据
		//	strcpy(_str, str);
		//}
		//给缺省值,构造空字符串时使用缺省值
		string(const char* str = "")
			//开辟空间,多开辟个空间存放\0
			:_str(new char[strlen(str) + 1])
		{
			//开辟空间
			_capacity = _size = strlen(str);
			//拷贝数据
			strcpy(_str, str);
		}

		/*拷贝构造函数
		s2(s1),使用s1拷贝构造s2*/
		string(string & s)
		{
			//开辟空间
			char* temp = new char[s._capacity + 1];
			_str = temp;
			_capacity = s._capacity;
			_size = s._size;
			//拷贝数据
			strcpy(_str, s._str);
		}

		//析构函数
		~string()
		{
			delete[] _str;
			_size = _capacity = 0;
		}
		
	private:
		//定义成员变量并且给缺省值
		char* _str = nullptr;
		int   _size = 0;
		int   _capacity = 0;
	};

}

1.2:赋值运算符重载与operatror[]获取元素

		//s1 = s2 this---->s1   s2----->s
		//赋值运算符重载
		string & operator =(string & s)
		{
			//开辟新空间
			char * temp = new char[s._capacity + 1];
			//释放旧空间
			delete _str;
			//拷贝数据
			strcpy(temp, s._str);
			//指向新空间
			_str = temp;
			_size = s._size;
			_capacity = s._capacity;
			return *this;
		}

		//T &,返回引用同样可以提高效率,有返回值的目的是为了支持连续赋值.
		char & operator[](size_t position)
		{
			assert(position < _size);
			return _str[position];
		}
		//const成员函数,给const对象调用,与上面构成函数重载
		const char& operator[](size_t position) const
		{
			assert(position < _size);
			return _str[position];
		}

1.3:容量与迭代器

迭代器呢其实是一个类指针,因此我们在模拟实现的时候,直接通过指针来进行模拟即可~

#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;

//避免与库中的string冲突,因此使用命名空间
namespace MyString
{
	class string
	{
		typedef char* iterator;
		typedef const char* const_iterator;
	

		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		//设置成员函数
		const_iterator begin() const
		{
			return _str;
		}

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

		//capacity
		size_t size()
		{
			return _size;
		}
	
		size_t capacity()
		{
			return _capacity;
		}
	
		
	private:
		//定义成员变量并且给缺省值
		char* _str = nullptr;
		int   _size = 0;
		int   _capacity = 0;
	};

}

1.4:reserve与resize

我们来回顾下reserve与resize的相关知识 

1:reserve

  • 比size小不变化
  • 比size大但小于capacity也不发生变化
  • 比capacity大才会进行扩容

因此我们只要判断是否比capacity大

2:resize

  • 比size小则进行删除.

  • 比size大但小于capacity会改变size,并用\0插入.
  • 比capacity大,会先改变size的大小同时进行扩容.

		//仅需判断是否比capacity大
		void reserve(size_t n)
		{
			if(n > _capacity)
			{
				//开辟新空间
				char* temp = new char[n + 1];
				//拷贝数据
				strcpy(temp, _str);
				//释放旧空间
				delete[] _str;
				//指向新空间
				_str = temp;
				_capacity = n;
				
			}
		}
		void resize(size_t n,char ch = '\0')
		{
			//比size小则会进行删除
			if (n <= _size)
			{
				_str[n] = ch;
			}
			//比size大比capacity小会改变size,并且用\0插入
			else if( n > _size && n <= _capacity)
			{
				_size = n;
			}
			//比capacity大, 会先改变size的大小同时进行扩容.
			else
			{
				//扩容
				reserve(n);
				for(size_t i = _size; i < _capacity ; i++)
				{
					_str[i] = ch;
				}
				_size = n;
			}
		}

1.5:清空与判断是否为空

		//只清除有效字符,不改变底层空间
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}

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

这里博主就不带着uu们测试啦,感兴趣的uu可以自己下去测试下~ 

1.6:push_back与append


		//Modifiers
		void push_back(char ch)
		{
			//检查容量
			if (_size == _capacity)
				//二倍扩容
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		void append(const char * str)
		{
			assert(str);
			//求出添加的字符串的长度
			int length = strlen(str);
			//判断添加后的有效字符数量是否越过容量
			if (_size + length > _capacity)
				reserve(_size + length);
			//_str + _size的位置为\0
			strcpy(_str + _size, str);
            _size += length;
		}

1.7:insert

1.7.1:插入字符

	
		string & insert(size_t position,char ch)
		{
			assert(position <= _size);
			if (_size == _capacity)
				//二倍扩容
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			//从后向前挪动覆盖
			for(int i = _size - 1; i >= position ;i--)
			{
				_str[i + 1] = _str[i];
			}
			_str[position] = ch;
			_size++;
			_str[_size] = '\0';
			return *this;
		}

1.7.2:插入字符串


		string & insert(size_t position,const char * str)
		{
			assert(position <= _size);
			int length = strlen(str);
			if (_size + length > _capacity)
				reserve(_size + length);
			//从后向前挪动覆盖
			//_size + length - 1表示挪动的字符串的重点,positon + length表示挪动的字符串的起点
			for(int i = _size + length - 1; i >= position + length; i--)
			{
				_str[i] = _str[i - length];
			}
			//拷贝数据
			strncpy(_str + position, str, length);
			_size += length;
			_str[_size] = '\0';
			return *this;
		}

1.8:erase

		void erase(size_t position = 0,size_t len = -1)
		{
			//防止越界
			assert(position < _size);
			//len == -1:如果 len 为 -1,表示删除到字符串末尾。
			//len >= _size - pos,如果 len 超过从 pos 开始的剩余字符长度,同样认为是删除到末尾。
			if(len == -1 || len >= _size - position)
			{
				_str[position] = '\0';
				_size = position;
			}

			else
			{
				//将 pos + len 后的字符串内容复制到 pos 位置,从而覆盖中间 len 长度的字符,实现删除操作
				strcpy(_str + position, _str + position + len);
			}
		}

1.9:operator+=

1.9.1:添加字符

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

1.9.2:添加字符串

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

1.91:find

1.91.1:查找字符


		//添加const修饰,令const成员与非const成员均可调用
		size_t find(char ch, size_t pos = 0) const
		{
			for(size_t i = pos; i < _size;i++)
			{
				if (_str[i] == ch)
					return i;
			}
			return -1;
		}

1.91.2:查找字符串 

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

		//添加const修饰,令const成员与非const成员均可调用
		size_t find(const char * str,size_t pos = 0	)const
		{
			const char* p = strstr(_str + pos, str);
			if(p != NULL)
			{
				//指针 - 指针得到对应的首字符下标
				return p - _str;
			}
			return -1;
		}

1.92:substr与swap

		string substr(size_t pos = 0, size_t len = -1)
		{
			string substr;
			//len == -1:如果 len 为 -1,表示全部获取。
			//len > _size - pos,如果 len 超过从 pos 开始的剩余字符长度,同样认为是全部获取
			if(len > _size - pos || len == -1)
			{
				for(size_t i = 0; i < _size; i++)
				{
					substr += _str[i];
				}
			}
			else
			{
				for(size_t i = pos; i < pos + len;i++)
				{
					substr += _str[i];
				}
			}
			return substr;
		}
		//s1.swap(s2)
		void swap(string & str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

1.93:非成员函数的重载

1.93.1:流插入与流提取的重载

	ostream& operator<<(ostream& _cout,MyString::string& str)
	{
		for (auto ch : str)
		{
			cout << ch;
		}
		return _cout;
	}

    istream& operator>>(istream& _cin, MyString::string& str)
	{
		第一版:会导致空间的浪费
		//char ch;
		_cin是无法读到\n与' '的
		//ch = _cin.get();
		清空字符
		//str.clear();
		//while (ch != '\n' && ch != ' ')
		//{
		//	str += ch;
		//	ch = _cin.get();
		//}
		//return _cin;

		//第二版
		char ch;
		ch = _cin.get();
		//底层给个buff
		char buff[128];
		size_t i = 0;
		//将原本的字符清空
		str.clear();
		//cin无法读取' '与'\n'
		while (ch != '\n' && ch != ' ')
		{
			buff[i++] = ch;
			//最后一个字符设置为\0
			if(i == 127)
			{
				buff[i] = '\0';
				str += buff;
			}
			//持续读取字符
			ch = _cin.get();
		}
		if(i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return _cin;
	} 
}
按照常规方式,流提取按照上面的第一版方式进行重载即可,但是,底层其实是给了buff,目的是: 防止空间的浪费,因为按照常规方式重载的话,那么在 扩容的时候一般是1.5倍或者2倍扩容,而通过给一个buff,能够最大程度地防止空间的浪费.

1.93.2:其他非成员函数的重载

	//会先调用此swap,有现成的,吃现成的,不使用模版里面的swap
	void swap(string& x, string& y)
	{
		x.swap(y);
	}

	//自定义类型传值传参会调用拷贝构造,因此需要传引用
	bool operator==(const MyString::string& str1, const  MyString::string& str2)
	{
		int result = strcmp(str1.c_str(), str2.c_str());
		return result == 0;
	}

	bool operator<(const  MyString::string& str1, const MyString::string& str2)
	{
		int result = strcmp(str1.c_str(), str2.c_str());
		return result < 0;
	}

	bool operator>(const  MyString::string& str1, const  MyString::string& str2)
	{
		int result = strcmp(str1.c_str(), str2.c_str());
		return result > 0;
	}

	bool operator<=(const  MyString::string& str1, const  MyString::string& str2)
	{
		return (str1 < str2) || (str1 == str2);
	}

	bool operator>=(const  MyString::string& str1, const  MyString::string& str2)
	{
		return !(str1 < str2);
	}

	bool operator!=(const  MyString::string& str1, const  MyString::string& str2)
	{
		return !(str1 == str2);
	}

2:Test.cpp

2.1:构造函数与拷贝构造

2.1.1:测试写法一

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestConstructionAndCopyConstruction()
{
	MyString::string s1;
	MyString::string s2("hello world");
	MyString::string s3(s2);
}
int main()
{
	TestConstructionAndCopyConstruction();
	return 0;
}

2.1.2:测试写法二

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestConstructionAndCopyConstruction()
{
	MyString::string s1;
	MyString::string s2("hello world");
	MyString::string s3(s2);
}
int main()
{
	TestConstructionAndCopyConstruction();
	return 0;
}

2.2:测试赋值运算符重载与[]获取元素

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"


void TestAssignmentOperatorOverloadingAndElementAccess()
{
	MyString::string s1;
	MyString::string s2("hello world");
	const MyString::string s3("hello Linux");
	s1 = s2;
	cout <<"s1[0]:>" << s1[0] << endl;
	cout <<"s3[6]:>" << s3[6] << endl;
}
int main()
{
	TestAssignmentOperatorOverloadingAndElementAccess();
	return 0;
}

2.3:测试迭代器与容量

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"


void TestIteratorAndCapacity()
{
	MyString::string s1;
	MyString::string s2("hello world");
	s1 = s2;
	for(auto & element : s2)
	{
		cout << element << endl;
	}
	cout <<"s2.size()" << s2.size() << endl;
	cout <<"s2.capacity()" << s2.capacity() << endl;
}

int main()
{
	TestIteratorAndCapacity();
	return 0;
}

2.4:测试reserve与resize 

2.4.1:测试resize

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestResize()
{
	MyString::string str("hello world");
	//比size小则进行删除
	str.resize(10);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;

	cout << endl;
	//比size大但小于capacity会用改变size,并用\0插入
	str.resize(13);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
	cout << endl;

	//比capacity大,会先改变size的大小同时进行扩容
	str.resize(20);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
}

int main()
{
	TestResize();
	return 0;
}

2.4.2:测试reserve

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestRserve()
{
	MyString::string str("hello world");
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
	cout << "reserver后" << endl;

	cout << endl;

	//比size小不变化
	str.reserve(10);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;

	cout << endl;
	//比size大但小于capacity也不发生变化
	str.reserve(13);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;

	cout << endl;

	//比capacity大才会进行扩容
	str.reserve(20);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
}

int main()
{
	TestRserve();
	return 0;
}

2.5:测试push_back与append

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"


void TestPushBackAndAppend()
{
	MyString::string str;
	str.append("hello world");
	str.push_back('x');
}

int main()
{
	TestPushBackAndAppend();
	return 0;
}

2.6:测试insert

2.6.1:测试插入字符

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestInsert() 
{
	MyString::string str("hello world");
	//在下标为5的位置插入字符h
	str.insert(5, 'h');
}

int main()
{
	TestInsert();
	return 0;
}

2.6.2:测试插入字符串

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestInsert() 
{
	MyString::string str("hello world");
	//在下标为5的位置插入字符h
	str.insert(5, 'h');
	str.insert(2, "Linux");
}

int main()
{
	TestInsert();
	return 0;
}

2.7:测试erase

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"


void TestErase()
{
	MyString::string str("hello world");
	str.erase(2, 4);
}

int main()
{
	TestErase();
	return 0;
}

 

2.8:测试operator+=

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"


void Test()
{
	MyString::string str("hello");
	str += ' ';
	str += "Linux";
}

int main()
{
	Test();
	return 0;
}

2.9:测试find

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"


void TestFind() 
{
	MyString::string str("hello Linux");
	cout << str.find('l', 2) << endl;
	cout << str.find("Linux", 2) << endl;
}

int main()
{
	TestFind();
	return 0;
}

2.91:测试substr与swap

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestSwapAndSubstr()
{
	MyString::string s1("hello Linux");
	MyString::string s2("hello bit");
	MyString::string temp = s1.substr(6, 5);
	cout << "交换前" << endl;
	cout << "s1:>" << s1 << endl;
	cout << "s2:>" << s2 << endl;
	cout <<"temp:>" << temp << endl;
	s1.swap(s2);
	cout << "交换后" << endl;
	cout <<"s1:>" << s1 << endl;
	cout <<"s2:>" << s2 << endl;
}
int main()
{
	TestSwapAndSubstr();
	return 0;
}

2.92:测试流插入与流提取

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestStreamInsertionAndStreamExtraction()
{
	MyString::string s1;
	cin >> s1;
	cout << s1 << endl;
	cout << "s1.size:>" << s1.size() << endl;
	cout << "s1.capacity:>" << s1.capacity() << endl;
}
int main()
{
	TestStreamInsertionAndStreamExtraction();
	return 0;
}

2.92.1:第一版流提取

2.92.2:第二版流提取

2.93:测试其他非成员函数

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"
void TestOtherFunction()
{
	MyString::string s1("hello world");
	MyString::string s2("hello bit");
	swap(s1, s2);
	s1.swap(s2);
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;

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

int main()
{
	TestOtherFunction();
	return 0;
}

3:知识补充

3.1:深拷贝与浅拷贝

在讲深浅拷贝之前,我们来看一个现象

我们可以清晰地看到,s2与s3的地址一样,这是为什么呢,因为博主将显示定义的拷贝构造函数给屏蔽了,因此在拷贝构造s3时会调用编译器默认的拷贝构造函数,那么这就会导致一个问题:

s2与s3共用同一块内存空间,在释放时同一块内存空间被释放多次而会引起程序崩溃,这种方式被称作浅拷贝.

3.1.1:浅拷贝

浅拷贝:又被称作位拷贝,编译器直接是将另外一个对象的值拷贝复制过来.如果对象中管理资源,那么最后就会导致多个对象共享一份资源,当一个对象销毁时就会将资源释放掉,而此时另一些对象不知道该资源已经被释放,以为还是有效的,那么因此当继续对资源进行操作时,就会发生访问违规~

举一个简单例子

就像一个家庭中有两个孩子,但父母只买了一份玩具,两个孩子愿意一块玩,则万事大吉,万一不想分享就你争我抢,玩具损坏.
那么该如何解决浅拷贝的问题呢,用深拷贝就可以即每个对象都有一份独立的资源,不要和其他对象共享。父母给每个孩子都买一份玩具,各自玩各自的就不会有存在任何矛盾.  

 3.1.2:深拷贝

如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出,一般情况都是按照深拷贝方式提供.

4:总代码

4.1:string.h

#include <iostream>
#include <string.h>
#include <assert.h>
using namespace std;

//避免与库中的string冲突,因此使用命名空间
namespace MyString
{
	class string
	{
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		//string()
		//	:_str(new char[1])
		//	, _size(0)
		//	,_capacity(0)
		//{
		//	//空字符串只有\0,
		//	_str[0] = '\0';
		//}
		构造非空字符串
		//string(const char * str)
		//	//开辟空间,多开辟个空间存放\0
		//	:_str(new char[strlen(str) + 1])
		//{
		//	//开辟空间
		//	_capacity = _size = strlen(str);
		//	//拷贝数据
		//	strcpy(_str, str);
		//}


		//给缺省值,构造空字符串时使用缺省值
		string(const char* str = "")
			//开辟空间,多开辟个空间存放\0
			:_str(new char[strlen(str) + 1])
		{
			//开辟空间
			_capacity = _size = strlen(str);
			//拷贝数据
			strcpy(_str, str);
		}

		/*拷贝构造函数
		s2(s1),使用s1拷贝构造s2*/
		string(string& s)
		{
			//开辟空间
			char* temp = new char[s._capacity + 1];
			_str = temp;
			_capacity = s._capacity;
			_size = s._size;
			//拷贝数据
			strcpy(_str, s._str);

		}

		//s1 = s2 this---->s1   s2----->s
		//赋值运算符重载
		string& operator =(string& s)
		{
			//开辟新空间
			char* temp = new char[s._capacity + 1];
			//释放旧空间
			delete _str;
			//拷贝数据
			strcpy(temp, s._str);
			//指向新空间
			_str = temp;
			_size = s._size;
			_capacity = s._capacity;
			return *this;
		}

		//T &,返回引用同样可以提高效率,有返回值的目的是为了支持连续赋值.
		char& operator[](size_t position)
		{
			assert(position < _size);
			return _str[position];
		}
		//const成员函数,给const对象调用,与上面构成函数重载
		const char& operator[](size_t position) const
		{
			assert(position < _size);
			return _str[position];
		}
		///
		//iteraor
		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}
		//设置成员函数
		const_iterator begin() const
		{
			return _str;
		}

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

		//capacity
		size_t size()
		{
			return _size;
		}

		size_t capacity()
		{
			return _capacity;
		}

		//仅需判断是否比capacity大
		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				//开辟新空间
				char* temp = new char[n + 1];
				//拷贝数据
				strcpy(temp, _str);
				//释放旧空间
				delete[] _str;
				//指向新空间
				_str = temp;
				_capacity = n;

			}
		}
		void resize(size_t n, char ch = '\0')
		{
			//比size小则会进行删除
			if (n <= _size)
			{
				_str[n] = ch;
			}
			//比size大比capacity小会改变size,并且用\0插入
			else if (n > _size && n <= _capacity)
			{
				_size = n;
			}
			//比capacity大, 会先改变size的大小同时进行扩容.
			else
			{
				//扩容
				reserve(n);
				for (size_t i = _size; i < _capacity; i++)
				{
					_str[i] = ch;
				}
				_size = n;
			}
		}

		//Modifiers
		void push_back(char ch)
		{
			//检查容量
			if (_size == _capacity)
				//二倍扩容
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			_str[_size] = ch;
			_size++;
			_str[_size] = '\0';
		}

		void append(const char* str)
		{
			assert(str);
			//求出添加的字符串的长度
			int length = strlen(str);
			//判断添加后的有效字符数量是否越过容量
			if (_size + length > _capacity)
				reserve(_size + length);
			//_str + _size的位置为\0
			strcpy(_str + _size, str);
			_size += length;
		}

		string& insert(size_t position, char ch)
		{
			assert(position <= _size);
			if (_size == _capacity)
				//二倍扩容
				reserve(_capacity == 0 ? 4 : 2 * _capacity);
			//从后向前挪动覆盖
			for (int i = _size - 1; i >= position; i--)
			{
				_str[i + 1] = _str[i];
			}
			_str[position] = ch;
			_size++;
			_str[_size] = '\0';
			return *this;
		}

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

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

		string& insert(size_t position, const char* str)
		{
			assert(position <= _size);
			int length = strlen(str);
			if (_size + length > _capacity)
				reserve(_size + length);
			//从后向前挪动覆盖
			//_size + length - 1表示挪动的字符串的重点,positon + length表示挪动的字符串的起点
			for (int i = _size + length - 1; i >= position + length; i--)
			{
				_str[i] = _str[i - length];
			}
			//拷贝数据
			strncpy(_str + position, str, length);
			_size += length;
			_str[_size] = '\0';
			return *this;
		}

		void erase(size_t position = 0, size_t len = -1)
		{
			//防止越界
			assert(position < _size);
			//len == -1:如果 len 为 -1,表示删除到字符串末尾。
			//len >= _size - pos,如果 len 超过从 pos 开始的剩余字符长度,同样认为是删除到末尾。
			if (len == -1 || len >= _size - position)
			{
				_str[position] = '\0';
				_size = position;
			}

			else
			{
				//将 pos + len 后的字符串内容复制到 pos 位置,从而覆盖中间 len 长度的字符,实现删除操作
				strcpy(_str + position, _str + position + len);
			}
		}

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

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

		//添加const修饰,令const成员与非const成员均可调用
		size_t find(char ch, size_t pos = 0) const
		{
			for(size_t i = pos; i < _size;i++)
			{
				if (_str[i] == ch)
					return i;
			}
			return -1;
		}

		//添加const修饰,令const成员与非const成员均可调用
		size_t find(const char * str,size_t pos = 0	)const
		{
			const char* p = strstr(_str + pos, str);
			if(p != NULL)
			{
				//指针 - 指针得到对应的首字符下标
				return p - _str;
			}
			return -1;
		}

		string substr(size_t pos = 0, size_t len = -1)
		{
			string substr;
			//len == -1:如果 len 为 -1,表示全部获取。
			//len > _size - pos,如果 len 超过从 pos 开始的剩余字符长度,同样认为是全部获取
			if(len > _size - pos || len == -1)
			{
				for(size_t i = 0; i < _size; i++)
				{
					substr += _str[i];
				}
			}
			else
			{
				for(size_t i = pos; i < pos + len;i++)
				{
					substr += _str[i];
				}
			}
			return substr;
		}
		//s1.swap(s2)
		void swap(string & str)
		{
			std::swap(_str, str._str);
			std::swap(_size, str._size);
			std::swap(_capacity, str._capacity);
		}

		//析构函数
		~string()
		{
			delete[] _str;
			_size = _capacity = 0;
		}
		
	private:
		//定义成员变量并且给缺省值
		char* _str = nullptr;
		int   _size = 0;
		int   _capacity = 0;
	};
	//Non-member_function_overloads
	ostream& operator<<(ostream& _cout,MyString::string& str)
	{
		for (auto ch : str)
		{
			cout << ch;
		}
		return _cout;
	}

	istream& operator>>(istream& _cin, MyString::string& str)
	{
		第一版:会导致空间的浪费
		//char ch;
		_cin是无法读到\n与' '的
		//ch = _cin.get();
		清空字符
		//str.clear();
		//while (ch != '\n' && ch != ' ')
		//{
		//	str += ch;
		//	ch = _cin.get();
		//}
		//return _cin;

		//第二版
		char ch;
		ch = _cin.get();
		//底层给个buff
		char buff[128];
		size_t i = 0;
		//将原本的字符情况
		str.clear();
		//cin无法读取' '与'\n'
		while (ch != '\n' && ch != ' ')
		{
			buff[i++] = ch;
			//最后一个字符设置为\0
			if(i == 127)
			{
				buff[i] = '\0';
				str += buff;
			}
			//持续读取字符
			ch = _cin.get();
		}
		if(i > 0)
		{
			buff[i] = '\0';
			str += buff;
		}
		return _cin;
	}

	//会先调用此swap,有现成的,吃现成的,不使用模版里面的swap
	void swap(string& x, string& y)
	{
		x.swap(y);
	}

	//自定义类型传值传参会调用拷贝构造,因此需要传引用
	bool operator==(const MyString::string& str1, const  MyString::string& str2)
	{
		int result = strcmp(str1.c_str(), str2.c_str());
		return result == 0;
	}

	bool operator<(const  MyString::string& str1, const MyString::string& str2)
	{
		int result = strcmp(str1.c_str(), str2.c_str());
		return result < 0;
	}

	bool operator>(const  MyString::string& str1, const  MyString::string& str2)
	{
		int result = strcmp(str1.c_str(), str2.c_str());
		return result > 0;
	}

	bool operator<=(const  MyString::string& str1, const  MyString::string& str2)
	{
		return (str1 < str2) || (str1 == str2);
	}

	bool operator>=(const  MyString::string& str1, const  MyString::string& str2)
	{
		return !(str1 < str2);
	}

	bool operator!=(const  MyString::string& str1, const  MyString::string& str2)
	{
		return !(str1 == str2);
	}
}

4.2:Test.cpp

#define _CRT_SECURE_NO_WARNINGS
#include "String.h"

void TestConstructionAndCopyConstruction()
{
	MyString::string s1;
	MyString::string s2("hello world");
	MyString::string s3(s2);
}

void TestAssignmentOperatorOverloadingAndElementAccess()
{
	MyString::string s1;
	MyString::string s2("hello world");
	const MyString::string s3("hello Linux");
	s1 = s2;
	cout <<"s1[0]:>" << s1[0] << endl;
	cout <<"s3[6]:>" << s3[6] << endl;
}

void TestIteratorAndCapacity()
{
	MyString::string s1;
	MyString::string s2("hello world");
	s1 = s2;
	for(auto & element : s2)
	{
		cout << element;
	}
	cout << endl;
	cout <<"s2.size():>" << s2.size() << endl;
	cout <<"s2.capacity():>" << s2.capacity() << endl;
}


void TestResize()
{
	MyString::string str("hello world");
	//比size小则进行删除
	str.resize(10);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;

	cout << endl;
	//比size大但小于capacity会用改变size,并用\0插入
	str.resize(13);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
	cout << endl;

	//比capacity大,会先改变size的大小同时进行扩容
	str.resize(20);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
}

void TestRserve()
{
	MyString::string str("hello world");
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
	cout << "reserver后" << endl;

	cout << endl;

	//比size小不变化
	str.reserve(10);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;

	cout << endl;
	//比size大但小于capacity也不发生变化
	str.reserve(13);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;

	cout << endl;

	//比capacity大才会进行扩容
	str.reserve(20);
	cout << "size:>" << str.size() << endl;
	cout << "capacity:>" << str.capacity() << endl;
}

void TestPushBackAndAppend()
{
	MyString::string str;
	str.append("hello world");
	str.push_back('x');
}
void TestInsert() 
{
	MyString::string str("hello world");
	//在下标为5的位置插入字符h
	str.insert(5, 'h');
	str.insert(2, "Linux");
}

void TestErase()
{
	MyString::string str("hello world");
	str.erase(2, 4);
}

void Test()
{
	MyString::string str("hello");
	str += ' ';
	str += "Linux";
}

void TestFind() 
{
	MyString::string str("hello Linux");
	cout << str.find('l', 2) << endl;
	cout << str.find("Linux", 2) << endl;
}
void TestSwapAndSubstr()
{
	MyString::string s1("hello Linux");
	MyString::string s2("hello bit");
	MyString::string temp = s1.substr(6, 5);
	cout << "交换前" << endl;
	cout << "s1:>" << s1 << endl;
	cout << "s2:>" << s2 << endl;
	cout <<"temp:>" << temp << endl;
	s1.swap(s2);
	cout << "交换后" << endl;
	cout <<"s1:>" << s1 << endl;
	cout <<"s2:>" << s2 << endl;
}

void TestStreamInsertionAndStreamExtraction()
{
	MyString::string s1;
	cin >> s1;
	cout << s1 << endl;
	cout << "s1.size:>" << s1.size() << endl;
	cout << "s1.capacity:>" << s1.capacity() << endl;
}

void TestOtherFunction()
{
	MyString::string s1("hello world");
	MyString::string s2("hello bit");
	swap(s1, s2);
	s1.swap(s2);
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;

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

int main()
{
	TestConstructionAndCopyConstruction();
	TestAssignmentOperatorOverloadingAndElementAccess();
	TestIteratorAndCapacity();
	TestResize();
	TestRserve();
	TestPushBackAndAppend();
	TestInsert();
	TestErase();
	Test();
	TestFind();
	TestSwapAndSubstr();
	TestStreamInsertionAndStreamExtraction();
	TestOtherFunction();
	return 0;
}

好啦,uu们,string的模拟实现这部分滴详细内容博主就讲到这里啦,如果uu们觉得博主讲的不错的话,请动动你们滴小手给博主点点赞,你们滴鼓励将成为博主源源不断滴动力,同时也欢迎大家来指正博主滴错误~

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

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

相关文章

【Go】-bufio库解读

目录 Reader和Writer接口 bufio.Reader/Writer 小结 其他函数-Peek、fill Reader小结 Writer Scanner结构体 缓冲区对于网络数据读写的重要性 Reader和Writer接口 在net/http包生成的Conn 接口的实例中有两个方法叫做Read和Write接口 type Conn interface {Read(b []b…

el-form el-table 前端排序+校验+行编辑

一、页面 <template><div class"bg" v-if"formData.mouldData?.length 0">当前暂无模板&#xff0c;点击<view class"add" click"addMould">立即创建</view></div><div v-else><el-col :x…

解决Docker环境变量的配置的通用方法

我们部署的很多服务都是以Docker容器的形式存在的。 在运行Docker容器前&#xff0c;除了设置网络、数据卷之外&#xff0c;还需要设置各种各样的环境变量。 有时候&#xff0c;由于容器版本的问题&#xff0c;一些文档没有及时更新&#xff0c;可能同时存在多个新旧版本的环…

使用win32com将ppt(x)文件转换为pdf文件

本文来记录下如何使用win32com将ppt(x)文件转换为pdf文件 文章目录 win32com概述win32com优缺点代码实例本文小结 win32com概述 Pywin32 是一个用于与 Microsoft Windows 操作系统交互的 Python 扩展模块&#xff0c;它提供了对多个 Windows API 的访问&#xff0c;包括对 Mic…

【nginx】client timed out和send_timeout的大小设置

websocket连接会断开&#xff0c;抓包检查后发现是中间的代理服务器nginx断开的&#xff0c;同时将后端和浏览器都断开了。将nginx日志调到debug级别后&#xff0c;有下面的断开信息。 [info] 125923#125923: *34 client timed out (110: Connection timed out) while proxyin…

代码段数据段的划分

DPL DPL存储在段描述符中&#xff0c;规定访问该段的权限级别(Descriptor Privilege Level) CPL CPL是当前进程的权限级别(Current Privilege Level)&#xff0c;是当前正在指向的代码段所在段的成绩&#xff0c;也就是CS段的DPL RPL RPL说明的是进程对段访问的请求权限(Re…

游戏引擎学习第14天

视频参考:https://www.bilibili.com/video/BV1iNUeYEEj4/ 1. 为什么关注内存管理&#xff1f; 内存分配是潜在的失败点&#xff1a; 每次进行内存分配&#xff08;malloc、new等&#xff09;时&#xff0c;都可能失败&#xff08;例如内存不足&#xff09;。这种失败会引入不稳…

基于Java Springboot电商个性化推荐系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

react中如何在一张图片上加一个灰色蒙层,并添加事件?

最终效果&#xff1a; 实现原理&#xff1a; 移动到图片上的时候&#xff0c;给img加一个伪类 &#xff01;&#xff01;此时就要地方要注意了&#xff0c;因为img标签是闭合的标签&#xff0c;无法直接添加 伪类&#xff08;::after&#xff09;&#xff0c;所以 我是在img外…

基于Java Springboot拍卖行系统

一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术&#xff1a;Html、Css、Js、Vue、Element-ui 数据库&#xff1a;MySQL 后端技术&#xff1a;Java、Spring Boot、MyBatis 三、运行环境 开发工具&#xff1a;IDEA/eclipse 数据…

HTML5+CSS前端开发【保姆级教学】+前端介绍和软件安装

学习了基础编程刚刚开始学习计算机的程序员&#xff0c;你是否会这样的想法:前端和后端是什么呢&#xff1f;如果你是刚上大学的大一大二基础小白&#xff0c;但是身边的卷王同学已经超前知道之后要从事前后端开发了&#xff0c;并且在学习各种框架的课程&#xff0c;Aahhahah,…

Android Framework层介绍

文章目录 前言一、Android Framework 层概述二、主要组件1. 应用程序接口&#xff08;API&#xff09;2. 系统服务3. Binder4. 资源管理5. Content Provider6. 广播接收器&#xff08;BroadcastReceiver&#xff09;7. 服务&#xff08;Service&#xff09; 三、与 Linux Kerne…

【C++滑动窗口】1248. 统计「优美子数组」|1623

本文涉及的基础知识点 C算法&#xff1a;滑动窗口及双指针总结 LeetCode1248. 统计「优美子数组」 给你一个整数数组 nums 和一个整数 k。如果某个连续子数组中恰好有 k 个奇数数字&#xff0c;我们就认为这个子数组是「优美子数组」。 请返回这个数组中 「优美子数组」 的数…

【paper】分布式无人水下航行器围捕智能目标

An Effective Strategy for Distributed Unmanned Underwater Vehicles to Encircle and Capture Intelligent Targets2022.8IEEE TRANSACTIONS ON INDUSTRIAL ELECTRONICS【Q1 7.5】Mingzhi Chen 上海理工大学 Q1 Background&#xff1a;本文试图解决一个什么样的问题&#xf…

【更新中】《硬件架构的艺术》笔记(三):处理多个时钟

介绍 单时钟设计更易于实现&#xff0c;也更少出现亚稳态、建立和保持时间违例方面的问题。但在实践中&#xff0c;很少有设计只在一个时钟下运行。 多时钟域 多个始终可以有以下一种或多种时钟关系&#xff1a; 1、时钟频率不同。 2、时钟频率相同&#xff0c;但相位不同…

Python_爬虫1_Requests库入门

目录 Requests库 7个主要方法 Requests库的get()方法 Response对象的属性 爬取网页的通用代码框架 理解requests库的异常 HTTP协议及Requests库方法 HTTP协议 HTTP协议采用URL作为定位网络资源的标识。 HTTP协议对资源的操作 理解PATCH和PUT的区别 HTTP协议与Requse…

从客户需求视角去认识ZLG | 边缘计算网关多种应用

在工业领域&#xff0c;串行总线与EtherNET总线广泛应用&#xff0c;物联网的兴起带来众多智能应用。尽管应用多样&#xff0c;但底层技术逻辑却殊途同归&#xff0c;本文将介绍ZLG致远电子串行总线和EtherNET总线之间的联动应用。 本文将从系统集成需求出发&#xff0c;以ZLG致…

Koa进阶:掌握中间件和参数校验的艺术

目录 一、首先下载依赖 二、在index.js中引入koa-parameter&#xff0c;一般挂载这个中间件时会放在注册请求体的后面 三、使用实例 四、如果跟我们所需求的参数不同&#xff0c;返回结果直接会返回422 koa-parameter一般是用来校验请求传过来的参数是否是自己所需要的的 G…

Linux下使用miniconda构建python运行环境

文章目录 miniconda安装构建python运行环境 miniconda安装 miniconda在linux环境下载安装&#xff1a; # Linux环境下使用wget命令下载选定的miniconda # 这里使用的是清华镜像&#xff0c;这个命令每次下载的是最新版本的miniconda wget -c https://mirrors.tuna.tsinghua.e…

解决failed to execute PosixPath(‘dot‘) 或者GraphViz‘s executables not found

在网上找了很多方法都没解决&#xff0c;所以写一篇文章帮助和我遇到同样问题的人 解决方法&#xff1a; 因为python解释器会解释转移字符&#xff0c;因此在环境变量中把\bin换成\\bin即可 解决过程&#xff1a; 系统&#xff1a;win10 已安装pip install graphviz&#xff0…