【C++】string的模拟实现

在本篇博客中,作者将会带领你模拟实现简单的STL中的string类的。至于string的常规使用,这里不做讲解。

string类的c++参考文档

string - C++ Reference (cplusplus.com)

 一.string的基本结构

string类的大致结构可以分为三个变量来表示,一个是字符指针,一个是size,还有一个是capacity。这三个变量分别表示:字符指针指向第一个字符串的第一个位置,size存放字符串的长度,capacity表示动态内存开辟空间的大小。如下图所示。

所以string类的成员变量可以像下面一样设计。 

namespace string_blog
{
	class string
	{
	public:

	private:
		char* _str;
		size_t size;
		size_t capacity;
	};
}

二.Member functions的实现

 Member functions可以分为三类:构造函数析构函数赋值=重载

1.构造函数 

构造函数又可以分为三种:无参构造函数有参构造函数拷贝构造 

无参构造函数和带参构造函数实现

首先说明,其实string类就是一个字符串数组,而在C语言中,字符串数组是用'\0'来结尾的,所以string类中的字符串也是用'\0'来结尾的。 

同时,由于C++支持缺省参数,所以无参构造函数和带参构造函数可以同时实现。

		//无参与带参构造函数
        //string s1;
		//string s2("hello world");
		string(const char* tmp = "")//缺省值给"",代表一个空字符串,而空字符串中只有一个\0
			:_str(new char[strlen(tmp)+1])//多开一个空间是为了存\0
			,_size(strlen(tmp))
			,_capacity(strlen(tmp)+1)
		{
			strcpy(_str, tmp);
		}

拷贝构造函数实现 

虽然我们不显式定义拷贝构造函数,编译器也会帮我们默认生成一个默认的拷贝构造函数但是这个拷贝构造函数是一个浅拷贝,浅拷贝在析构时会出现问题,原因可以看下面这篇博客,所以这里我们需要字节手动实现一个拷贝构造函数,来实现深拷贝。 

【C++】类与对象 (中篇)(6个默认成员函数)-CSDN博客

		//拷贝构造函数
		//string s2(s1);
		string(const string& tmp)
			:_str(new char[tmp.size()+1])//size函数是获取字符串长度,在后面实现
			,_size(tmp._size)
			,_capacity(tmp._capacity)
		{
			strcpy(_str, tmp._str);
		}

2.析构函数的实现 

		//析构函数
		~string()
		{
			delete[] _str;//释放_str指向的空间
			_str = nullptr;//把_str指针置成nullptr
			_size = _capacity = 0;
		}

3.赋值=重载

赋值=重载同样需要注意深浅拷贝问题。 

		//赋值=重载
		//string s1("hello world");
		//s1=s2;
		string& operator=(const string& tmp)
		{
			if (this != &tmp)//防止自己给自己赋值
			{
				delete[] _str;//先释放原来的字符串
				char* ptr = new char[tmp.size() + 1];//开一块新空间给_str
				strcpy(ptr, tmp._str);//将tmp的内容拷贝给ptr
				_str = ptr;
				_size = tmp._size;
				_capacity = tmp._capacity;
			}
			return *this;
		}

三.Iterators的实现

 迭代器可以分为四种:正向的,反向的,const的,非const的,在这里先不讨论反向迭代器

对于string类来说,迭代器的底层其实就是一个char类型的指针,所以string的迭代器可以这样实现。 

		//对char* 重命名为iterator迭代器
		typedef char* iterator;
		typedef const char* const_iterator;

		//begin迭代器
		iterator begin()
		{
			return _str;//返回第一个字符的指针
		}

		//end迭代器
		iterator end()
		{
			return _str + size();//返回\0位置的指针
		}

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

		//const end迭代器
		const_iterator end() const
		{
			return _str + size();
		}

四.capacity的实现

 capacity有以上这么多个函数,我们都来把常用的实现一下。

1.size、length和capacity的函数实现

其实size和length本质上是同一个功能,都是获取字符串的长度,只不过函数名不同而已,所以我们在这里只实现size。同时,capacity是去获取字符串的容量

		//返回string的字符个数
		size_t size() const
		{
			return _size;
		}

		//返回string的容量
		size_t capacity() const
		{
			return _capacity;
		}

2. resize和reserve的实现

resize是将字符串大小调整到n个字符的长度,        reserve是调整string的容量大小

reserve的实现

在这里,我们先实现reserve函数,这有助于我们在实现resize时,去复用reserve。 

		//调整string的容量
		void reserve(const size_t n = 0)
		{
			if (n > size())//如果要调整到的容量大于原来的容量,才进行调整
			{
				char* tmp = new char[n + 1];//预留一个位置存放\0
				strcpy(tmp, _str);//将原来的字符串拷贝到新空间中
				delete[] _str;//释放原来的空间
				_str = tmp;//把新空间_str
				_capacity = n;
			}
		}

resize的实现

 resize的实现要分下面这三种情况

		//调整string的size
		void resize(const size_t n, const char& ch = '\0')
		{
			if (n > _size)
			{
				if (n > _capacity)//如果size大于容量,则需要扩容
				{
					reserve(n);//扩容到n个容量
				}

				size_t left = _size;
				size_t right = n;
				while (left < right)//将ch字符内容赋值到没有内容的位置中
				{
					_str[left++] = ch;
				}
			}
			//将最后一个位置赋值\0
			_str[n] = '\0';
		}

3.clear和empty的实现

clear清空string中所有的内容,empty判断string是否为空。 

		//clear实现
		void clear()
		{
			_str[0] = '\0';//将第一个字符的位置给\0
			_size = 0;//并将长度给0
		}

		//empty实现
		bool empty() const 
		{
			if (size() == 0)
				return true;
			else
				return false;
		}

五. Element access的实现

 operator[]与at的作用时一样的,都是获取第n个位置字符的引用,所有这里只实现operator[]

		//重载[]
		char& operator[](const size_t pos)
		{
			assert(pos < size());//如果要返回的位置不存在,则断言
			return *(_str + pos);
		}

		//重载const[]
		const char& operator[](const size_t pos) const
		{
			assert(pos < size());//如果要返回的位置不存在,则断言
			return *(_str + pos);
		}

注意,重载[]有两个,一个是非const的,可读可写,一个是const的,只能读。 

 六.Modifiers的实现

 在实现这些函数时,我们可以优先实现insert和erase,这样在后面尾插尾删时可以复用insert和erase。

1.insert和erase的实现 

		//在pos位置插入一个字符串
		void insert(const size_t pos, const char* s)
		{
			assert(pos <= size());//断言 插入的位置要<=size

			if ((size() + strlen(s)) > capacity())//判断追加字符串是否需要扩容
			{
				reserve(size() + strlen(s));
			}

			//将pos位置后的字符串后挪
			memmove(_str + pos + strlen(s), _str + pos, end() - (_str + pos));
			//将s字符串插入到pos的位置
			memmove(_str + pos, s, strlen(s));
			//将最后一个位置赋值\0
			*(_str + capacity()) = '\0';
		}

		//在pos位置插入n个字符
		void insert(const size_t pos, size_t n, const char& ch)
		{
			assert(pos <= size());
			if ((size() + n) > capacity())
			{
				reserve(size() + n);
			}
			memmove(_str + pos + n, _str + pos, end() - (_str + pos));
			memset(_str + pos, ch, n);
			*(_str + capacity()) = '\0';
		}

		//在pos位置删除n个字符
		void erase(const size_t pos, size_t len)
		{
			assert(pos < size());
			
			if (len >= (end() - (_str + pos)))//如果要删除的长度长于pos后面的字符串长度,则直接删到尾
			{
				*(_str + pos) = '\0';
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
			}
		}

2.push_back、pop_back和append的实现

这几个函数的实现,可以直接复用insert和erase。 

		//尾插一个字符
		void push_back(const char& ch)
		{
			insert(size(), 1, ch);
		}

		//尾删一个字符
		void pop_back()
		{
			assert(size());
			erase(size() - 1, 1);
		}

		//尾插一个字符串
		void append(const char* s)
		{
			insert(size(), s);
		}

3.operator+=的实现

operator+=其实就是尾插的意思,这里我直接复用push_back和append就行。 

		//重载operator+=
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}

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

 七.String operations的实现

在这里我们只挑最常用的几个来实现。 

1.c_str的实现

c_str是返回一个C语言形式的字符串,即一个char类型的指针。 

		//转换为C形字符串
		char* c_str() const
		{
			return _str;
		}

2.find的实现

find函数是一个字符串查找函数,在模拟实现里面,我们可以调用C语言的strstr来实现。 

		//字符串查找    从第pos个位置开始查找
		int find(const char* s, const size_t pos =0) const
		{
			char* tmp = strstr(_str + pos, s);
			if (tmp == nullptr)
			{
				return -1;
			}
			else
			{
				return tmp - _str;
			}
		}

八.Non-member function overloads的实现

 最后是一些字符串比较和重载输入输出的实现。

1.各种比较的实现 

		bool operator==(const char* s) const 
		{
			int ret = strcmp(_str, s);
			if (ret == 0)
				return true;
			else
				return false;
		}

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

		bool operator>(const char* s) const
		{
			int ret = strcmp(_str, s);
			if (ret > 0)
				return true;
			else
				return false;
		}

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

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

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

2.重载输入和输出

	//重载cout<<
	ostream& operator<<(ostream& out, string& s)
	{
		out << s.c_str() << endl;
		return out;
	}

	//重载cin>>
	istream& operator>>(istream& in, string& s)
	{
		//输入之前,需要清空字符串
		s.clear();
		while (1)//进去循环,一个一个字符的插入
		{
			char ch;
			//cin >> ch;
			ch = in.get();//读取一个字符
			if (ch == ' ' || ch == '\n')//判断这个字符是否结束
			{
				break;
			}
			else
			{
				s += ch;
			}
		}
		s += '\0';
		return in;
	}

3.getline的实现

 getline也是实现字符串的输入,但是getline和operator>>的区别是,getline可以输入空格

operator>>不能输入空格。

		void getline()
		{
			//输入之前,需要清空字符串
			clear();
			while (1)
			{
				char ch;
				//cin >> ch;
				ch = cin.get();
				if (ch == '\n')
				{
					break;
				}
				else
				{
					(*this) += ch;
				}
			}
			(*this) += '\0';
		}

写到这里,我们模拟实现的string类基本上就已经完成了,当然,STL库中的string类不止这么点接口,但是有很多是很相似的,有兴趣的朋友可以继续完善。 

九.所有源代码

string.h文件

namespace string_blog
{
	class string
	{
	public:
		//对char* 重命名为iterator迭代器
		typedef char* iterator;
		typedef const char* const_iterator;
	public:
		//无参与带参构造函数
		//string s1;
		//string s2("hello world");
		string(const char* tmp = "")//缺省值给"",代表一个空字符串,而空字符串中只有一个\0
			:_str(new char[strlen(tmp)+1])//多开一个空间是为了存\0
			,_size(strlen(tmp))
			,_capacity(strlen(tmp))
		{
			strcpy(_str, tmp);
		}

		//拷贝构造函数
		//string s2(s1);
		string(const string& tmp)
			:_str(new char[tmp.size()+1])//size()函数在后面实现
			,_size(tmp._size)
			,_capacity(tmp._capacity)
		{
			strcpy(_str, tmp._str);
		}

		//析构函数
		~string()
		{
			delete[] _str;//释放_str指向的空间
			_str = nullptr;//把_str指针置成nullptr
			_size = _capacity = 0;
		}

		//赋值=重载
		//string s1("hello world");
		//s1=s2;
		string& operator=(const string& tmp)
		{
			if (this != &tmp)//防止自己给自己赋值
			{
				delete[] _str;//先释放原来的字符串
				char* ptr = new char[tmp.size() + 1];//开一块新空间给_str
				strcpy(ptr, tmp._str);//将tmp的内容拷贝给ptr
				_str = ptr;
				_size = tmp._size;
				_capacity = tmp._capacity;
			}
			return *this;
		}

		//begin迭代器
		iterator begin()
		{
			return _str;//返回第一个字符的指针
		}

		//end迭代器
		iterator end()
		{
			return _str + size();//返回\0位置的指针
		}

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

		//const end迭代器
		const_iterator end() const
		{
			return _str + size();
		}

		//返回string的字符个数
		size_t size() const
		{
			return _size;
		}

		//返回string的容量
		size_t capacity() const
		{
			return _capacity;
		}

		//调整string的容量
		void reserve(const size_t n = 0)
		{
			if (n > size())//如果要调整到的容量大于原来的容量,才进行调整
			{
				char* tmp = new char[n + 1];//预留一个位置存放\0
				strcpy(tmp, _str);//将原来的字符串拷贝到新空间中
				delete[] _str;//释放原来的空间
				_str = tmp;//把新空间_str
				_capacity = n;
			}
		}

		//调整string的size
		void resize(const size_t n, const char& ch = '\0')
		{
			if (n > _size)
			{
				if (n > _capacity)//如果size大于容量,则需要扩容
				{
					reserve(n);//扩容到n个容量
				}

				size_t left = _size;
				size_t right = n;
				while (left < right)//将ch字符内容赋值到没有内容的位置中
				{
					_str[left++] = ch;
				}
			}
			//将最后一个位置赋值\0
			*_str = '\0';
		}

		//clear实现
		void clear()
		{
			_str[0] = '\0';//将第一个字符的位置给\0
			_size = 0;//并将长度给0
		}

		//empty实现
		bool empty() const 
		{
			if (size() == 0)
				return true;
			else
				return false;
		}

		//重载[]
		char& operator[](const size_t pos)
		{
			assert(pos < size());//如果要返回的位置不存在,则断言
			return *(_str + pos);
		}

		//重载const[]
		const char& operator[](const size_t pos) const
		{
			assert(pos < size());//如果要返回的位置不存在,则断言
			return *(_str + pos);
		}

		//尾插一个字符
		void push_back(const char& ch)
		{
			insert(size(), 1, ch);
		}

		//尾删一个字符
		void pop_back()
		{
			assert(size());
			erase(size() - 1, 1);
		}

		//尾插一个字符串
		void append(const char* s)
		{
			insert(size(), s);
		}

		//重载operator+=
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}

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

		//在pos位置插入一个字符串
		void insert(const size_t pos, const char* s)
		{
			assert(pos <= size());//断言 插入的位置要<=size

			if ((size() + strlen(s)) > capacity())//判断追加字符串是否需要扩容
			{
				reserve(size() + strlen(s));
			}

			//将pos位置后的字符串后挪
			memmove(_str + pos + strlen(s), _str + pos, end() - (_str + pos));
			//将s字符串插入到pos的位置
			memmove(_str + pos, s, strlen(s));
			//将最后一个位置赋值\0
			*(_str + capacity()) = '\0';
			_size = size() + strlen(s);//调整新_size值
		}

		//在pos位置插入n个字符
		void insert(const size_t pos, size_t n, const char& ch)
		{
			assert(pos <= size());
			if ((size() + n) > capacity())
			{
				reserve(size() + n);
			}
			memmove(_str + pos + n, _str + pos, end() - (_str + pos));
			memset(_str + pos, ch, n);
			*(_str + capacity()) = '\0';
			_size = _size + n;
		}

		//在pos位置删除n个字符
		void erase(const size_t pos, size_t len)
		{
			assert(pos < size());
			
			if (len >= (end() - (_str + pos)))//如果要删除的长度长于pos后面的字符串长度,则直接删到尾
			{
				*(_str + pos) = '\0';
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
			}

			_size = _size - len;
		}

		//字符串查找    从第pos个位置开始查找
		int find(const char* s, const size_t pos =0) const
		{
			char* tmp = strstr(_str + pos, s);
			if (tmp == nullptr)
			{
				return -1;
			}
			else
			{
				return tmp - _str;
			}
		}

		//转换为C形字符串
		char* c_str() const
		{
			return _str;
		}

		bool operator==(const char* s) const 
		{
			int ret = strcmp(_str, s);
			if (ret == 0)
				return true;
			else
				return false;
		}

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

		bool operator>(const char* s) const
		{
			int ret = strcmp(_str, s);
			if (ret > 0)
				return true;
			else
				return false;
		}

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

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

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

		void getline()
		{
			//输入之前,需要清空字符串
			clear();
			while (1)
			{
				char ch;
				//cin >> ch;
				ch = cin.get();
				if (ch == '\n')
				{
					break;
				}
				else
				{
					(*this) += ch;
				}
			}
			(*this) += '\0';
		}

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

	//重载cout<<
	ostream& operator<<(ostream& out, string& s)
	{
		out << s.c_str() << endl;
		return out;
	}

	//重载cin>>
	istream& operator>>(istream& in, string& s)
	{
		//输入之前,需要清空字符串
		s.clear();
		while (1)
		{
			char ch;
			//cin >> ch;
			ch = in.get();
			if (ch == ' ' || ch == '\n')
			{
				break;
			}
			else
			{
				s += ch;
			}
		}
		s += '\0';
		return in;
	}

	//测试构造函数 和 赋值=重载
	void Test1()
	{
		string s1;
		string s2("hello world");
		string s3(s2);
		s1 = s3;

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

	//测试迭代器
	void Test2()
	{
		string s1("hello world");
		string::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
	}

	//测试
	void Test3()
	{
		string s1("hello world");
		s1.clear();
		s1.resize(10, 'b');
		cout << s1.c_str() << endl;
		cout << s1.empty() << endl;
		s1.clear();
		cout << s1.c_str() << endl;
		cout << s1.empty() << endl;
	}
    
    //测试operator[]
	void Test4()
	{
		string s1("hello world");
		for (int i = 0; i < s1.size(); i++)
		{
			s1[i] -= 1;
		}
		cout << s1.c_str() << endl;
	}

	//测试insert
	void Test5()
	{
		string s1("hello world");
		s1.getline();
		cout << s1 << endl;
	}
}

string.c文件

#include"string.h"
using namespace string_blog;

int main()
{
	string_blog::Test1();
	string_blog::Test2();
	string_blog::Test3();
	string_blog::Test4();
	string_blog::Test5();

	return 0;
}

十.拷贝构造和赋值=重载的现代写法

在做完上面的工作后,string类的模拟实现也基本完成了,而现在在补充一下拷贝构造和赋值=重载的现代写法

什么是现代写法?

在上面的写法中,是比较常规的写法,但是,在现在,我们可以这样写。 

1.拷贝构造现代写法

		void Swap(string& s)//交换函数,交换两个string类对象
		{
			swap(_str, s._str);
			swap(_size, s._size);
			swap(_capacity, s._capacity);
		}

		//拷贝构造现代写法
		string(const string& s)
			:_str(nullptr)
		{
			string tmp(s._str);//用s的字符串去构造tmp
			Swap(tmp);//用tmp和this进行交换
		}

2.赋值=重载的现代写法

		//赋值=重载的现代写法
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				string tmp(s);//用s去构造一个临时对象
				Swap(tmp);//交换两个string对象
			}
			return *this;
		}

  • 32
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值