C++【STL】之string模拟实现

C++ string类模拟实现

上一篇讲解了string的使用,当然少不了string的模拟实现实现啦!这里依然是讲解常用接口的模拟实现,话不多说,下面正文直接开始!

文章目录:

  • C++ string类模拟实现
    • 1. 成员变量
    • 2. 默认成员函数
      • 2.1 构造和析构
      • 2.2 拷贝和赋值
    • 3. 容量操作
      • 3.1 reserve方法
      • 3.2 resize方法
      • 3.3 查看大小容量、判空
    • 4. 数据访问
      • 4.1 下标访问
      • 4.2 迭代器访问
    • 5. 数据修改
      • 5.1 push_back方法
      • 5.2 append方法
      • 5.3 insert方法
      • 5.4 erase方法
    • 6. 运算符重载
      • 6.1 +/+=运算符重载
      • 6.2 逻辑判断
    • 7. 其他
      • 7.1 find方法
      • 7.2 clean方法
      • 7.3 swap方法
      • 7.4 获取原生指针
    • 8. 流操作
      • 8.1 流插入
      • 8.2 流提取
    • 9. 完整代码

1. 成员变量

string本质就是一个存放字符的顺序表,是由指针大小容量组成的,并且加入了npos = -1,来当作size_t的上限,也起到当作查找是未找到的标准等功能。

namespace sakura	//命名空间
{
	class string
	{
	private:
		char* _str;	//数据指针
		size_t _size;	//大小
		size_t _capacity; //容量
		static const size_t npos;
	};
}		

注意:

这里npos 的类型为 static const size_tstatic 修饰后,npos 只能被初始化一次,而加了 const 后,允许在类中赋予缺省值进行初始化,如果不加 const,则必需到类外手动初始化静态成员,const修饰的静态变量,只允许整型家族在类中设置缺省值

2. 默认成员函数

2.1 构造和析构

构造函数

使用缺省参数,当用户没有传参时,将 string 对象初始化为空串,字符串长度可以利用初始化列表进行初始化

string(const char* str = "")
	:_size(strlen(str))
{
	_capacity = _size == 0 ? 3 : _size;
	_str = new char[_capacity + 1]; //预留位置给'/0'
	strcpy(_str, str);
}

析构函数

析构函数 在释放内存时,是以 delete[] 的形式,所以在申请内存时,即使只申请一个 char,也要写成 new char[1] 的形式,目的就是为了销毁对应

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

2.2 拷贝和赋值

拷贝构造

此处的拷贝构造为深拷贝,先开辟一块和s空间相同大小的空间,然后拷贝数据到新空间,完成拷贝

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

注意:申请空间后,要使用strcap进行数据拷贝,否则操作无效

赋值重载

赋值重载的实现首先需要判断是否为同一对象赋值,然后再进行赋值操作

注意:

这里不使用_str = new char[s._capacity + 1]的方式,而是先将数据拷贝到一块新开辟的空间,成功后改变_str指向,释放掉原空间,这样可以保证若申请空间失败,原空间数据不被破坏

string& operator=(const string& s)
{
	if (this != &s)
	{
		char* tmp = new char[s._capacity + 1]; //防止new失败
		strcpy(tmp, s._str);
		
		delete[] _str;
		_str = tmp; //改变指针指向
		_size = s._size;
		_capacity = s._capacity;
	}

	return *this; //返回避免 a = b = c 的连续赋值情况
}

3. 容量操作

3.1 reserve方法

若传入的capacity不大于_capacity,不做任何操作,若大于_capacity,则正常进行扩容

void reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1]; //防止new失败
		strcpy(tmp, _str);
        
		delete[] _str;
		_str = tmp; //改变指针指向
		_capacity = n;
	}
}

这里的_str = tmp 并不是使用的赋值重载(operator=),因为_strtmp都是类值类型(指针类型),这里只是改变了_str的指向

3.2 resize方法

大小调整分三种情况:

  • n < _size:缩容,直接调整'\0'的位置改变_size
  • n > capacity:扩容并初始化
  • n > _size && n < _capacity:不扩容只进行初始化
void resize(size_t n, char ch = '\0')
{
	if (n < _size) //比_size小就是缩容,直接添加终止符号'\0'
	{
		//删除数据 保留前n个
		_size = n;
		_str[_size] = '\0';
	}
	else if (n > _size) //扩容
	{
		if (n > _capacity)
		{
			reserve(n);
		}
		size_t i = _size;
		while (i < n)
		{
			_str[i] = ch;
			++i;
		}
		_size = n;
		_str[_size] = '\0';
	}
}

3.3 查看大小容量、判空

size_t size() const
{
    return _size;
}

size_t capacity() const
{
    return _capacity;
}

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

类似获取数据这些,不修改成员变量数据的函数,最好都加上const

4. 数据访问

4.1 下标访问

类中的数据为私有,无法直接访问,但可以通过函数间接访问

char& operator[](size_t pos)
{
    assert(pos < _size);
    return _str[pos];
}
//const版本
const char& operator[](size_t pos) const
{
    assert(pos < _size);
    return _str[pos];
}

4.2 迭代器访问

获取原生指针来简单实现迭代器

typedef char* iterator;
typedef const char* const_iterator;

iterator begin()
{
    return _str;
}

iterator end()
{
    return _str + _size;
}
//const版本
const_iterator begin() const
{
    return _str;
}

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

5. 数据修改

5.1 push_back方法

这里的push_back方法对应于顺序表的尾插,相信友友们已经非常熟悉了,判断是否需要阔容后直接插入即可

void push_back(char ch)
{
    if (_size + 1 > _capacity) //检查扩容
    {
        
        reserve(_capacity * 2); //二倍扩
    }
	//尾插字符
    _str[_size] = ch;
    _str[_size] = '\0';
    ++_size;
}

5.2 append方法

首先是append尾插n个字符,这里直接复用push_back,来尾插n次即可实现

void append(size_t n, char ch)
{
    //提前开空间
    if (_size + n > _capacity)
    {
        reserve(_size + n);
    }
    while (n--)
    {
        push_back(ch);
    }
}

然后是append尾插字符串,这里判断是否需要扩容后,直接调用strcpy或者strcat即可实现

void append(const char* str)
{
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }

    strcpy(_str + _size, str);
    //strcat(_str, str);
    _size += len;
}

5.3 insert方法

操作步骤:

  1. 检查要插入位置的下标是否合法
  2. 检查是否需要扩容
  3. 挪动数据 + 插入数据

首先是任意位置插入字符的实现

string& insert(size_t pos, char ch)
{
    assert(pos <= _size); //判断下标是否合法
    if (_size + 1 > _capacity) //判断扩容
    {
        reserve(2 * _capacity);
    }
    
    //挪动数据
    size_t end = _size + 1;
    while (end > pos)
    {
        _str[end] = _str[end - 1];
        --end;
    }   
    //错误写法
    //size_t end = _size;
    //while (end >= pos)
    //{
    //	_str[end + 1] = _str[end];
    //	--end;
    //}
    
    //插入数据
    _str[pos] = ch;
    ++_size;
    return *this;
}

这里的size_t end = _size写法错误,是因为endsize_t类型,当end = -1时,会发生整型提升,end = INT_MAX产生错误

然后是任意位置插入字符串的实现

string& insert(size_t pos, const char* str)
{
    assert(pos <= _size);
    size_t len = strlen(str);
    if (_size + len > _capacity)
    {
        reserve(_size + len);
    }
    //挪动数据
    size_t end = _size + len;
    while (end > pos + len - 1)
    {
        
        _str[end] = _str[end - len];
        --end;
    }

    //写法二:
    //size_t end = _size;
    //for (size_t i = 0; i < _size + 1; ++i)
    //{
    //	_str[end + len] = _str[end];
    //	--end;
    //}

    //拷贝插入
    strncpy(_str + pos, str, len); //只拷贝len个,避免将'\0'拷贝
    _size += len;
    return *this;
}

有了insert方法,我们可以直接将其复用给push_backappend方法

void push_back(char c)
{
	insert(_size, c);
}

void append(const char* str)
{
	insert(_size, str);
}

5.4 erase方法

操作步骤:

  1. 检查下标是否合法
  2. 判断删除长度,若len == npospos + len >= _size则直接终止到pos处,反之将pos + len处的字符挪动覆盖到pos
string& erase(size_t pos, size_t len = npos)
{
    assert(pos < _size);
    if (len == npos || pos + len >= _size)
    {
        _str[pos] = '\0';
        _size = pos;
    }
    else
    {
        strcpy(_str + pos, _str + pos + len);
        _size -= len;
    }
    return *this;
}

6. 运算符重载

6.1 +/+=运算符重载

这里的++=运算符重载,就是借助临时变量来进行字符或字符串的附加操作

string operator+(char ch) const
{
    string tmp(*this); //传值返回
    tmp.push_back(ch);

    return tmp;
}

string operator+(const string& s) const
{
    string tmp(*this);
    tmp.append(s._str);

    return tmp;
}

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

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

6.2 逻辑判断

string对象是借助ASCII值来进行大小的判断的,这里可以直接俄使用strcmp函数,只需要实现 >==,其他逻辑判断都可以复用

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 || s == *this;
}

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);
}

7. 其他

7.1 find方法

传入目标字符/字符串,遍历原字符串,找到返回下标,反之则返回npos,支持从传入的位置查找,默认从pos = 0的位置开始查找,查找字符串可以直接调用strstr库函数

//查找字符
size_t find(char ch, size_t pos = 0) //默认从0下标开始查找
{
    assert(pos < _size);
    for (size_t i = pos; i < _size; ++i)
    {
        if (_str[i] == ch)
        {
            return i; //找到返回下标
        }
    }
    return npos; //没找到返回npos
}
//查找字符串
size_t find(const char* str, size_t pos = 0)
{
    assert(pos < _size);
    char* p = strstr(_str + pos, str); //调用strstr库函数(查找子串)
    if (p == nullptr)
    {
        return npos;
    }
    else
    {
        return p - _str; //指针 - 指针
    }
}

指针 - 指针就是实际找到的字符串的下标

7.2 clean方法

clean方法用于清空字符串,实现只用将字符串结束标准设置到0下标位置,再将长度置零即可

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

7.3 swap方法

调用库中的swap函数,将string对象中的三个成员进行交换即可,此时是浅拷贝,效率很高

void swap(string& s)
{
    std::swap(_str, s._str); //交换指针
    std::swap(_capacity, s._capacity); /交换容量
    std::swap(_size, s._size); //交接大小
}

这里有个问题,为什么不直接交换对象,而是要交换三个成员变量呢?

这是因为直接交换对象的话,会发生多次拷贝构造的操作,而且还是深拷贝,效率很低

7.4 获取原生指针

_str为私有成员不能直接获取,只需要借助函数间接获取即可

const char* c_str()
{
    return _str;
}

8. 流操作

流操作是 string 中的类外成员函数,此时的左操作数为 ostreamistream

8.1 流插入

通过迭代器将string对象的内容输出到屏幕上

ostream& operator<<(ostream& out, const string& s)
{
    for (auto ch : s)
    {
        out << ch;
    }
    return out;
}

8.2 流提取

流提取的实现有两个问题需要解决:

  1. 在获取字符串前,不知道用户输入的字符串长度,无法提前开辟空间,开小了不够,开大了浪费
  2. 读取数据后,若对象中已有数据,会覆盖原数据

解决方案:

  1. 借助一个 数组存储数据,当数组装满时,将 数组内容拼接至字符串尾部,原数组则重新开始存储数据
  2. 调用 clear 函数先清理原对象数据,再进行输入
istream& operator>>(istream& in, string& s)
{
    s.clrar(); //清理原有数据
    char ch = in.get();
    char buff[128];
    size_t i = 0;
    while (ch != ' ' && ch != '\n')
    {
        buff[i++] = ch;
        if (i == 127)
        {
            buff[127] = '\0';
            s += buff;
            i = 0;
        }
        ch = in.get();
    }
    if (i != 0)
    {
        buff[i] = '\0';
        s += buff;
    }
    return in;
}

9. 完整代码

#include 
#include 
#include 
using namespace std;

//模拟实现string
namespace sakura
{
	class string
	{
	public:
		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;
		}

		//string(const char* str = nullptr) //不可以
		//string(const char* str = "\0") //会多一个'\0'
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size == 0 ? 3 : _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

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

		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._size;
				_capacity = s._capacity;
			}

			return *this;
		}

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

		const char* c_str()
		{
			return _str;
		}

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

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		
		string operator+(char ch) const
		{
			string tmp(*this); //传值返回
			tmp.push_back(ch);

			return tmp;
		}

		string operator+(const string& s) const
		{
			string tmp(*this);
			tmp.append(s._str);

			return tmp;
		}

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

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

		size_t size() const
		{
			return _size;
		}

		size_t capacity() const
		{
			return _capacity;
		}

		bool empty() const
		{
			return _size == 0;
		}
		// 不修改成员变量数据的函数,最好都加上const
		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 || s == *this;
		}

		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 resize(size_t n, char ch = '\0')
		{
			if (n < _size) //比_size小就是缩容,直接添加终止符号'\0'
			{
				//删除数据 保留前n个
				_size = n;
				_str[_size] = '\0';
			}
			else if (n > _size) //扩容
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				size_t i = _size;
				while (i < n)
				{
					_str[i] = ch;
					++i;
				}
				_size = n;
				_str[_size] = '\0';
			}
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1]; //防止new失败
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp; //改变指针指向
				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size + 1 > _capacity) //检查扩容
			{
				_capacity == 0 ? _capacity = 1 : 0;
				reserve(_capacity * 2); //二倍扩
			}
			//尾插字符
			_str[_size] = ch;
			_str[_size] = '\0';
			++_size;
		}

		void append(size_t n, char ch)
		{
			//提前开空间
			if (_size + n > _capacity)
			{
				reserve(_size + n);
			}
			while (n--)
			{
				push_back(ch);
			}
		}


		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

			strcpy(_str + _size, str);
			//strcat(_str, str);
			_size += len;
		}

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

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

		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size); //判断下标是否合法
			if (_size + 1 > _capacity) //判断扩容
			{
				reserve(2 * _capacity);
			}

			//挪动数据
			size_t end = _size + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}
			//错误写法
			//size_t end = _size;
			//while (end >= pos)
			//{
			//	_str[end + 1] = _str[end];
			//	--end;
			//}

			//插入数据
			_str[pos] = ch;
			++_size;
			return *this;
		}

		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			//挪动数据
			size_t end = _size + len;
			while (end > pos + len - 1)
			{
				_str[end] = _str[end - len];
				--end;
			}

			//写法二:
			//size_t end = _size;
			//for (size_t i = 0; i < _size + 1; ++i)
			//{
			//	_str[end + len] = _str[end];
			//	--end;
			//}

			//拷贝插入
			strncpy(_str + pos, str, len); //只拷贝len个,避免将'\0'拷贝
			_size += len;
			return *this;
		}

		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len == npos || pos + len >= _size)
			{
				_str[pos] = '\0';
				_size = pos;
			}
			else
			{
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
			return *this;
		}

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

		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);
			for (size_t i = pos; i < _size; ++i)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}
			return npos;
		}

		size_t find(const char* str, size_t pos = 0)
		{
			assert(pos < _size);
			char* p = strstr(_str + pos, str);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}

		void clrar()
		{
			_str[0] = '\0';
			_size = 0;
		}
	private:
		char* _str;
		size_t _capacity;
		size_t _size;

		static const size_t npos;
	};
	const size_t string::npos = -1;

	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}

	istream& operator>>(istream& in, string& s)
	{
		s.clrar();
		char ch = in.get();
		char buff[128];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == 127)
			{
				buff[127] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

C++【STL】之string模拟实现,到这里就介绍结束了,本篇文章对你由帮助的话,期待大佬们的三连,你们的支持是我最大的动力!

文章有写的不足或是错误的地方,欢迎评论或私信指出,我会在第一时间改正!

你可能感兴趣的:(c++,开发语言,string,STL)