C++必修:模拟实现STL之string

✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++学习
贝蒂的主页:Betty’s blog

为了让我们更加深入理解string,接下来我们将模拟实现一个·简易版的string。而为了和STL库中的string以示区分,我们将使用命名空间namespace对其封装。

1. string的成员变量

string简单来说就是一个被封装可动态增长的字符数组,这与我们在数据结构中学的串非常类似,所以我们可以借助实现串的思路来大致模拟string的结构。

下面是string的成员变量:

namespace betty
{
	class string 
    {
	public:
		//...
	private:
		size_t _size;//当前有效字符的个数
		size_t _capacity;//当前容量的大小,方便扩容
		char* _str;//存储的字符串
	};
}

值得注意的是\0既不占据有效长度的大小,也不占据容量的大小。

img

2. string的成员函数

再知道string的成员变量之后,接下来我们将探究string的成员函数,而常见成员函数的用法我们早在之前就已经介绍过了 ,下面我们将来具体实现一下:

2.1. string的迭代器

首先我们来模拟实现一下迭代器iterator,而在string中迭代器iterator就是一个指针。所以我们直接使用typedef实现

typedef char* iterator;//普通迭代器
typedef const char* const_iterator;//const迭代器

接下来我们来实现begin()end(),其中begin()指向的是字符串的起始位置即_str,而end()指向有效字符最后的下一位即\0的位置。

img

iterator begin()
{
	return _str;
}

iterator end()
{
	return _str + _size;
}

实现完普通迭代器之后,我们可以顺便重载一个const_iterator的版本。

const_iterator begin() const
{
	return _str;
}

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

我们知道在string中还有一个反向迭代器,这个我们在之后会统一实现。

2.2. string的初始化与销毁

2.2.1. 构造函数与拷贝构造

我们之前在学习string时知道其初始化方式有很多,可以通过默认构造函数给其初始化,也可以通过字符数组给其初始化。当然我们在实现时不用这么麻烦,直接给缺省值就可以了。

string(const char* str = "")//默认缺省为空串
		:_size(strlen(str))
		, _capacity(_size)
		, _str(new char[_capacity + 1])//也要存储'\0'
	{
		strcpy(_str, str);
	}

拷贝构造也十分简单,直接拷贝就行了

string(const string& s)
{
	_str = new char[s._capacity + 1];
	memcpy(_str, s._str, s._size);
	_size = s._size;
	_capacity = s._capacity;
}

当然我们也可以通过一个取巧的方式来实现拷贝构造。

string(const string& s)
	: _str(nullptr)
	, _size(0)
	, _capacity(0)
{
	string tmp(s._str);
	this->swap(tmp);
}

首先通过构造出一个与原字符串相同的字符串tmp,然后让this所指向的字符串与其交换,这样出了作用域之后销毁的就是原this所指向的字符串。当然我们必须先将this所指向的字符串先初始化,不然析构函数去释放未初始化的空间会出错。

2.2.2. 赋值重载与析构函数

赋值运算符重载与拷贝构造的实现就非常类似了,直接实现即可。只需要对自己赋值给自己的情况进行特判即可。

string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1];
		memcpy(tmp, s._str, s._size + 1);
		delete[] _str;
		_str = tmp;

		_size = s._size;
		_capacity = s._capacity;
	}
	return *this;
}

当然我们也能实现赋值重载的简易版本。

string& operator=(const string& s)
{
	if (this != &s)
	{
		string tmp(s);
		this->swap(tmp);
	}
	return *this;
}

最后我们实现析构函数,只需要清理资源即可

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

2.3. string的容量操作

2.3.1. 有效长度与容量大小

首先我们先实现返回字符串有效长度的size() 与容量大小的capacity()。并且为了适配const对象,最后用const修饰this指针。

size_t size() const
{
	return _size;
}
size_t capacity() const
{
	return _capacity;
}
2.3.2. 容量操作

首先我们实现判断字符串是否为空的empty()以及情况字符串的clear()。其中emty()不需要修改,可以加上const

void clear()
{
	_str[0] = '\0';//直接将首字符改为'\0'
	_size = 0;
}
bool empty() const
{
	return _size == 0;
}

接下来我们来实现扩容函数reserve()与·resize(),其中reserve()最简单,只要新容量大于旧容量就发生扩容。

void reserve(size_t newCapacity = 0)
{
	// 当新容量大于旧容量的时才扩容
	if (newCapacity > _capacity)
	{
		char* tmp = new char[newCapacity + 1];
        //拷贝原有数据
		memcpy(tmp, _str, _size);
		delete[] _str;
		// 让_str指向新空间
		_str = tmp;
		_capacity = newCapacity;
	}
}

resize()的逻辑就比较复杂,需要分三种情况讨论。设字符串原来有效长度为size,容量为capacity,新容量为n

  1. n<size时,resize会删除有效字符到指定大小。
  2. size<n<capcity时,resize会补充有效字符(默认为’\0)到指定大小。
  3. n>capacity时,resize会补充有效字符(默认为’\0)到指定大小。
void resize(size_t n, char c = '\0')
{
	if (n > _size)
	{
		// n>capacity则扩容
		if (newSize > _capacity)
		{
			reserve(n);
		}
		//填充新数据c至有效长度为n
		memset(_str + _size, c, n - _size);
	}

	// 当n<=_size,删除多余的字符
	_size = n;
	_str[newSize] = '\0';
}

2.4. string的访问操作

为了符合我们C语言访问数组的习惯,我们可以先重载operator[]。当然我们也要提供两种不同的接口:可读可写与可读不可写。并且使用引用返回,减少不必要的拷贝。

// 可读可写
char& operator[](size_t pos)
{
	assert(pos < _size);
	return _str[pos];
}
// 可读不可写
const char& operator[](size_t pos) const
{
	assert(pos < _size);
	return _str[pos];
}

同理我们也可以实现front()back()函数。

// 可读可写
char& front()
{
	return _str[0];
}
char& back()
{
	return _str[_size - 1];
}
// 可读不可写
const char& front()const
{
	return _str[0];
}
const char& back()const
{
	return _str[_size - 1];
}

2.5. string的修改操作

2.5.1. 字符串的添加

首先我们将实现两个常用的修改函数:push_back()append()

void push_back(char ch)
{
	// 如果数据满了,则需要进行扩容
	if (_size == _capacity)
	{ 	
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size++] = ch;
	_str[_size] = '\0';
}
// 追加字符串
void append(const char* s)
{
	int len = strlen(s);// 获取字符串的长度
	// 如果大于原来容量,则就需要扩容
	if (_size + len > _capacity)
	{
		reserve(_size + len);
	}

	// 将字符串拷贝到末尾的_size位置
	memcpy(_str + _size, s, len + 1);
	_size += len;
}

而后我们可以复用前两个函数实现operator+=()

//追加一个字符
string& operator+=(char ch)
{
	push_back(ch);
	return *this;
}
//追加一个字符串
string& operator+=(const char* s)
{
	append(s);
	return *this;
}

最后我们来实现随机插入insert()函数。将pos位置后所有字符移动len个单位,如果为字符len=1,否则len=字符串长度

img

//添加一个字符
void insert(size_t pos, char ch)
{
    //防止越界访问
	assert(pos <= _size);
    //检查是否需要扩容
	if (_size == _capacity)
	{
		size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
		reserve(newCapacity);
	}
	size_t end = _size + 1;
	while (end > pos)
	{
		_str[end] = _str[end - 1];
		--end;
	}
	_str[pos] = ch;
	_size++;
}
//添加一个字符串
void insert(size_t pos, const char* s)
{
    //防止越界访问
	assert(pos <= _size);
    //检查是否需要扩容
	size_t len = strlen(s);
	if (_size+len > _capacity)
	{
		reserve(_size+len);
	}
	size_t end = _size + len;
	while (end > pos)
	{
		_str[end] = _str[end - len];
		--end;
	}
	memcpy(_str + pos, s, len);
}
2.5.2. 字符串的删除

字符串的删除我们需要实现pop_back()erase()两个函数。

void pop_back()
{
	_str[_size - 1] = '\0';
    --_size;
}

而随机删除erase()需要再定义一个类成员变量npos来实现,它为无符号数的-1,一般为整型的最大值

// 类内声明
static size_t npos;
// 类外初始化
size_t string::npos = -1;

pos位置后所有字符往前移动len个单位,如果为字符len=1,否则len=字符串长度

img

void erase(size_t pos, size_t len = npos)
{
	assert(pos < _size);
    //判断是否将后面字符之间删除完
	if (len == npos || pos + len >= _size)
	{
		_str[0] = '\0';
		_size = pos;
	}
	else
	{
        //往前移len个字符
		size_t begin = pos + len;
		while (begin <= _size)
		{
			_str[begin - len] = _str[begin];
			++begin;
		}
		_size -= len;
	}
}
2.5.3. 字符串的交换

最后我们来实现字符串的交换swap()函数,我们知道string的交换其实就是指针_str_sizecapacity的交换。

void swap(string& s)
{
	std::swap(_str, s._str);
	std::swap(_size, s._size);
	std::swap(_capacity, s._capacity);
}

img

2.6. string的其他操作

首先我们将常见字符串的比较函数实现,实现时相互之间还可以复用。

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 strcmp(_str, s._str) == 0;
}
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);
}

接下来让我们实现流插入operator<<()与流提取operator>>()。但是我们要注意普通istream对象无法提前空格与\n。这是我们就需要一个函数get()来提取

img

同时为了避免频繁扩容,我们可以借助一个辅助数组buf来辅助填充。

// 流提取
istream& operator>>(istream& in, string& s)
{
	s.clear();//先清空原字符串
	char ch = in.get();
	char buf[128];
	int i = 0;
	while (ch != '\n')//以换行为分隔符
	{
		buf[i++] = ch;
		// 为\0留空间
		if (i == 127)
		{
			buf[i] = '\0';
			s += buf;
			i = 0;
		}
		ch = in.get();
	}
	//将buf中剩余数据直接填入
	if (i != 0)
	{
		buf[i] = '\0';
		s += buf;
	}
	return in;
}

最后我们实现c_str()find()substr()函数。

const char* c_str()const		
{		
    return _str;        
}
//找字符
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 npos;
}
//找字符串
size_t find(const char* s, size_t pos = 0) const
{
	const char* p = strstr(_str + pos, s);
	if (p)
	{
		return p - _str;
	}
	return npos;
}
//截取一段字符串
string substr(size_t pos, size_t len = npos)
{
    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;
}

3. 源码

#pragma once
#include <iostream>
#include<assert.h>
#include<stdbool.h>
using namespace std;
namespace betty
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		iterator begin()
		{
			return iterator(_str);
		}
		const_iterator begin()const
		{
			return const_iterator(_str);
		}
		iterator end()
		{
			return iterator(_str + _size);
		}
		const_iterator end()const
		{
			return const_iterator(_str + _size);
		}
		//string()
		//	:_size(0)
		//	, _capacity(0)
		//	, _str(new char[1])
		//{
		//	_str[0] = '\0';
		//}
		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
			, _str(new char[_capacity + 1])
		{
			strcpy(_str, str);
		}
		//string(const string& s)
		//{
		//	_str = new char[s._capacity + 1];
		//	strcpy(_str,s._str);
		//	_size = s._size;
		//	_capacity = s._capacity;
		//}
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_size, s._size);
			std::swap(_capacity, s._capacity);
		}
		string(const string& s)
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{
			string tmp(s._str);
			swap(tmp);
		}
		//string& operator=(const string& s)
		//{
		//	if (this != &s)
		//	{
		//		char* tmp = new char[s._capacity + 1];
		//		strcpy(tmp, s._str);
		//		delete[] _str;
		//		_str = tmp;
		//		_size = s._capacity;
		//		_capacity = s._capacity;
		//	}
		//	return *this;
		//}
		string& operator=(string tmp)
		{
			if (this != &tmp)
			{
				swap(tmp);
			}
			return *this;
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos)const
		{
			assert(pos < _size);
			return _str[pos];
		}
		size_t size()const
		{
			return _size;
		}
		size_t capacity()const
		{
			return _capacity;
		}
		const char* c_str()const
		{
			return _str;
		}
		void reserve(size_t newCapacity )
		{
			if (newCapacity > _capacity)
			{
				char* tmp = new char[newCapacity + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;
				_capacity = newCapacity;
			}
		}
		void resize(size_t newSize, char c = '\0')
		{
			if (newSize > _size)
			{
				if (newSize > _capacity)
				{
					reserve(newSize);
				}
				memset(_str + _size, c, newSize - _size);
			}
			_size = newSize;
			_str[newSize] = '\0';
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		bool empty() const
		{
			return _size == 0;
		}
		void push_back(char ch)
		{
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}
			_str[_size++] = ch;
			_str[_size] = '\0';
		}
		void append(const char* s)
		{
			size_t len = strlen(s);
			if (len + _size > _capacity)
			{
				reserve(len + _size);
			}
			memcpy(_str + _size, s, len + 1);
			_size += len;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		string& operator+=(const char* s)
		{
			append(s);
			return *this;
		}
		//找字符
		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 npos;
		}
		//找字符串
		size_t find(const char* s, size_t pos = 0) const
		{
			const char* p = strstr(_str + pos, s);
			if (p)
			{
				return p - _str;
			}
			return npos;
		}
		string substr(size_t pos, size_t len = npos)
		{
			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;
		}
		void insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size == _capacity)
			{
				size_t newCapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newCapacity);
			}
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			_str[pos] = ch;
			_size++;
		}
		void insert(size_t pos, const char* s)
		{
			assert(pos <= _size);
			size_t len = strlen(s);
			if (_size+len> _capacity)
			{
				reserve(_size+len);
			}
			size_t end = _size + len;
			while (end > pos)
			{
				_str[end] = _str[end - len];
				--end;
			}
			memcpy(_str + pos, s, len);
		}
		// 可读可写
		char& front()
		{
			return _str[0];
		}
		char& back()
		{
			return _str[_size - 1];
		}
		// 可读不可写
		const char& front()const
		{
			return _str[0];
		}
		const char& back()const
		{
			return _str[_size - 1];
		}
		void pop_back()
		{
			_str[_size - 1] = '\0';
			--_size;
		}
		void erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[0] = '\0';
				_size = pos;
			}
			else
			{
				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 strcmp(_str, s._str) == 0;
		}
		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);
		}
		~string()
		{
			delete[]_str;
			_str = nullptr;
			_size = _capacity = 0;
		}

	private:
		size_t _size;
		size_t _capacity;
		char* _str;
		static size_t npos;
	};
	 size_t string::npos = -1;
	 ostream& operator<<(ostream& out, const string& s)
	 {
		 for (auto& e : s)
		 {
			 out << e;
		 }
		 return out;
	 }
	 //istream& operator>>(istream& in,  string& s)
	 //{
		// s.clear();
		// char ch = in.get();
		// while (ch != '\n'&&ch!=' ')
		// {
		//	 s += ch;
		//	 ch = in.get();
		// }
		// return in;
	 //}
	 // 流提取
	 istream& operator>>(istream& in, string& s)
	 {
		 s.clear();
		 char ch = in.get();
		 char buf[128];
		 int i = 0;
		 while (ch != '\n')
		 {
			 buf[i++] = ch;
			 if (i == 127)
			 {
				 buf[127] = '\0';
				 i = 0;
				 s += buf;
			 }
			 ch = in.get();
		 }
		 if (i != 0)
		 {
			 buf[i] = '\0';
			 s += buf;
		 }
		 return in;
	 }

}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Betty’s Sweet

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值