C++模拟实现string类

C++模拟实现string类_第1张图片

本章代码gitee仓库:string模拟实现

文章目录

  • 0. 搭个框子
  • 1. 成员变量
  • 2. 构造函数 & 析构函数 & 拷贝构造
  • 3. 字符串访问
    • c_str
    • operator[]
  • 4. 申请空间
  • 5. 增删查改
    • 增加字符/字符串
      • push_back
      • append
      • operator+=
      • insert
    • 删除
    • 查找 & 修改
  • 6. 字符串比较
  • 7. 深浅拷贝
  • 8. 流插入 & 流提取
  • 9. 写时拷贝(补充知识)

0. 搭个框子

要模拟实现string,我们就要定义出一个自己的类域,然后引用一些头文件(这个可以边写边加,我这边就直接全写上了)

#include
#include
#include
//我不是很喜欢全部展开,用到什么展开什么就行
//using namespace std;
using std::cout;
using std::cin;
using std::endl;
using std::string;
using std::ostream;
using std::istream;
namespace mystring
{
    class string
    {
    public:
        //	方法
    private:
    	//	成员变量
	public:
		const static size_t npos;
    }
    const size_t string::npos = -1;	//size_t类型最大值
}

1. 成员变量

string就像一个动态顺序表,记录当前字符的数量、空间的容量、字符串

size_t _size;	//	大小
size_t _capacity;	//容量
char* _str;	//	字符串

2. 构造函数 & 析构函数 & 拷贝构造

开口就的时候,多开一个为\0留一个空间,缺省参数""代表不传参的时候,默认\0

string(const char* str = "")	//构造函数
	:_size(strlen(str))
{
	_capacity = _size;
	_str = new char[_capacity + 1];	//为'/0'留一个空间
	memcpy(_str, str, _size + 1);
}

string(const string& s)		//拷贝构造
{
	_str = new char[s._capacity + 1];
	memcpy(_str, s._str, s._size + 1);
	_size = s.size();
	_capacity = s._capacity;
}
~string()	//析构函数
{
	delete[] _str;
	_str = nullptr;
	_size = _capacity = 0;
}

3. 字符串访问

c_str

代码边写边测,写一个输出函数,方便测试代码

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

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

4. 申请空间

预留空间的时候,每次都多开一个,为\0准备空间

防止出现xxxx\0xxx这样的字符,strcpy字符拷贝\0拷贝不过去;所以使用直接使用memcpy内存拷贝按要求拷贝完毕

void reserve(size_t n = 0)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];
		memcpy(tmp, _str, _size + 1);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

5. 增删查改

增加字符/字符串

增添字符串首先要先看空间还够不够,不够就扩容

push_back

string不提供头插,头插效率太低了

添加完毕之后,_size自增一下,结尾注意添加\0

void push_back(char c)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 12 : _capacity * 2);
	}
	_str[_size] = c;
	_str[++_size] = '\0';
}

append

尾部增添字符串

string& append(const char* s)
{
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		reserve(len + _size);
	}
	memcpy(_str + _size, s, len + 1);
	_size += len;
	return *this;
}

operator+=

用的最多的还是+=,所以重载+=,本质上还是push_backappend,这个看着更加舒服一点

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

insert

insert指定位置插入,某种意义上也可以说是实现了头插

string& insert(size_t pos, size_t n, char c)
{
	assert(pos <= _size);
	if (_size + n > _capacity)
	{
		reserve(n + _size);
	}
	//防止整形提升
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + n] = _str[end];
		end--;
	}
	for (size_t i = 0; i < n; i++)
	{
		_str[pos + i] = c;
	}
	_size += n;
	return *this;
}
string& insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		reserve(len + _size);
	}
	size_t end = _size;
	while (end >= pos && end != npos)
	{
		_str[end + len] = _str[end];
		end--;
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = str[i];
	}
	_size += len;
	return *this;
}

删除

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

查找 & 修改

能找到这个字符,就代表可以修改

string类也提供了resize,这个可以修改字符串

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

size_t find(const char* s, size_t pos = 0) const
{
	assert(pos < _size);
	const char* ptr = strstr(_str + pos, s);
	if (ptr)
	{
        //子字符串在原始字符串中的索引位置
		return ptr - _str;
	}
	return npos;
}

string substr(size_t pos = 0, size_t len = npos) const
{
	assert(pos < _size);	
	size_t n = len;
	if (len == npos || pos + len > _size)
	{
		n = _size - pos;
	}
	string tmp;
	tmp.reserve(n);
	for (size_t i = pos; i <pos+ n; i++)
	{
		tmp += _str[i];
	}
	return tmp;
}

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

6. 字符串比较

bool operator<(const string& s) const
{
	bool ret = memcmp(_str, s._str, _size < s._size ? _size : s._size);
	// "123"	"123"	f
	// "1234"	"123"	f
	//	"123"	"1234"	t
	return ret == 0 ? _size < s._size : ret;
}
bool operator==(const string& s) const
{
	return _size == s._size && memcmp(_str, s._str,_size) == 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 || *this == s;
}

7. 深浅拷贝

重载=赋值运算符,需要实现深拷贝,如果没有写,编译器默认实现浅拷贝,这不符合要求

深拷贝要做到,将赋值的数据给目标,然后再将原目标原本指向的空间释放

//正常思路 全部手动完成
/*string& operator= (const string& str)
{
	if (this != &str)
	{
		char* tmp = new char[str._capacity + 1];
		memcpy(tmp, str._str, str._size + 1);
		_size = str._size;
		_capacity = str._capacity;
	}
	return *this;
}*/
void swap(string& str)
{
	std::swap(_str, str._str);
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
}
/*string& operator= (const string& str)
{
	if (this != &str)
	{
		string tmp(str);
		swap(tmp);
	}
	return *this;
}*/
//自动化写法, 交互完毕之后,自动调用析构函数,释放原目标指向的空间
string& operator= (string tmp)
{
	swap(tmp);
	return *this;
}

8. 流插入 & 流提取

最开始写的c_str,是C语言样式的,遇见\0就停止,而string类是将对象字符全部输出

//清理对象
void clear()
{
	_str[0] = '\0';
	_size = 0;
}
ostream& operator<<(ostream& out, const string& s)
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}
istream& operator>>(istream& in, string& s)
{
    //清理原始对象
	s.clear();
	char ch=in.get();
	//清理缓冲区
	while (ch == ' ' || ch == '\n')
	{
		ch = in.get();
	}
	//避免频繁扩容
	char buff[128];
	int i = 0;
	while (ch!=' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == 127)
		{
			buff[i] = '\0';
			s += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i != 0)
	{
		buff[i] = '\0';
		s += buff;
	}
	return in;
}

9. 写时拷贝(补充知识)

写时拷贝是一种技术优化。用于在共享资源的情况下减少不必要的数据复制。它通常用于实现像字符串、向量等数据结构的副本,以避免在副本创建时立即复制整个数据。相反,只有在原始数据被修改时,才会执行实际的复制操作。
比如说在string类赋值的时候,Vs2022采用的就是深拷贝,每次都额外开空间
gcc/g++,则采用的是写时拷贝
C++模拟实现string类_第2张图片


那以上就将string类的基本功能就实现完毕了,完整的看我仓库代码吧

本期分享就到这里咯,我们下期再见,如果还有下期的话

你可能感兴趣的:(原创,C++,c++,开发语言)