【C++】string类的模拟实现

在这里插入图片描述
个人主页
在这里插入图片描述

⭐一、默认成员函数

1.构造函数

构造函数的概念是完成对象的初始化,在编写之一函数时,需要注意的是:初始化列表走的顺序并不是写入初始化中的顺序,而是成员变量中的声明顺序。
我们默认将构造函数设置为缺省参数,当我们不进行传入参数时,则默认为空字符串。将字符串的大小和空间设置为传入字符串中的大小及空间。(此处传并不包含’ \0’ )

//构造函数
string(const char* str = ‘ ’)
{
	_size = strlen(str);
	//_capcacity不包含\0
	_capacity = _size;
	//多开一个空间用于存放'\0'
	_str = new char[_capacity + 1];
	将字符串拷贝到已经开好的空间中
	strcpy(_str, str);
}

2.拷贝构造函数

创建一块同样大小的空间,将原有的数据进行拷贝。这样两个指针就指向自己相对应的空间,一个对象的改变就不会影响到另一个对象。这样的操作就称之为深拷贝。

//拷贝构造函数
string(const string& s)
{
	_str = new char[s._capacity + 1];
	strcpy(_str, s._str);
	_size = s._size;
	_capacity = s._capacity;
}

3.析构函数

析构函数是用来完成数据的销毁和释放空间的。

//析构函数
~string()
{
	//_str不为空时
	if (_str)
	{
		delete[] _str;
		_size = _capacity = 0;
	}
}

4.赋值函数

需要注意的是:左值的_str在开辟新空间之前需要先将原来的空间释放掉,并且需要判断一下是否会自己给自己赋值,如果有则无需操作。

//赋值(深)
string& operator =(const string& s)
{
	//防止自己赋值给自己
	if (this != &s)
	{
		//释放原来的空间
		delete[] _str;
		_str = new char[s._capacity + 1];
		strcpy(_str, s._str);
		_size = s._size;
		_capacity = s._capacity;
		return*this;
	}
}

🚀二、迭代器有关函数

1.begin和end

iterator begin()
{
	//返回字符串中的第一个字符的地址
	return _str;
}

iterator end()
{
	返回字符串中最后一个字符的地址
	return _str + _size;
}

2.const迭代器

//const迭代器
const_iterator begin() const
{
	//返回字符串中的第一个const字符的地址
	return _str;
}
const_iterator end() const
{
	//返回字符串中最后一个const字符的地址
	return _str + _size;
}

学到这里,我们就可以简单的用迭代器来遍历一下string

void test()
{
	string s1("hello world!!!");
	string::iterator it = s1.begin();
	while (it != s1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

我们也可以使用范围for来进行遍历,其本质就是当代码在进行编译时,编译器会自动将范围for替换为迭代器的形式。

void test()
{
	string s1("hello world!!!");
	for (auto ch : s1)
	{
		cout << ch << " ";
	}
	cout << endl;
}

🏠三、有关容量及大小的函数

1.size和capacity

size函数是用来获取当前字符串中的有效长度 (注:并不包含’\0’)

size_t size() const
{
	return _size;
}

capacity函数是用来获取当前字符串的容量。

//容量
size_t capacity() const
{
	return _capacity; 
}

2.resize和reserve

resize:
当n大于当前的size时,则将size扩大到n。如果n小于当前的size时,则将size缩小到n。

reserve:
当n大于当前对象的空间时,需要对空间进行扩容操作;如果当n小于当前对象的空间时,当前对象的空间并不会进行缩小,而是什么也不做。

void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		cout << "reserve: " << n << endl;
		char* tmp = new char[n + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

🎡四、修改字符串相关函数

1.push_back函数

push_back函数是在当前字符串的后面插入一个字符。在尾插之前需要判断一下是否需要扩容,需要注意的是:尾插完字符时需要在该字符后面加上’\0’,否则打印时会出现非法访问的错误。

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

2.append函数

append函数是在当前字符串的后面加上一个字符串,在尾插之前也是需要判断一下当前的空间是否需要进行扩容,在尾插完字符串时并不需要在其后面加上’\0’,因为字符串本身后方自带’\0’.

void string::append(const char* str)
{
	size_t len = strlen(str);
	if (_size + len > _capacity)
	{
		// 大于2倍,需要多少给多少,小于2倍按2倍扩
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}
	strcpy(_str + _size, str);
	_size += len;
}

3.operator +=函数

用于完成字符的相加,我们可以直接调用push_back函数即可。

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

用于完成字符串的相加,我们也可以直接调用append函数即可。

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

4.insert函数

insert函数是在字符串中的任意位置插入字符或者字符串。
一、insert插入字符时
我们首先需要判断一下pos的位置是否合法,然后需要判断一下当前的空间大小是否需要扩容。
过程:先将pos位置及其后面的字符统一向后挪动一位,给待插入的字符留出位置,然后插入即可。

//插入字符
void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//挪动数据
	size_t end = _size + 1;
	while (end >= pos)
	{
		_str[end + 1] = _str[end];
		--end;
	}
	_str[pos] = ch;
	++_size;
}

二、insert插入字符串时
我们首先也是需要判断一下pos的位置是否合法,然后也是判断一下当前的空间大小是否需要进行扩容。
过程:先将pos位置及其后面的字符统一向后挪动len位,给待插入的字符串留出位置,然后插入即可。
(注:len为待插入字符串的长度)

//插入字符串
void string::insert(size_t pos, const char* s)
{
	assert(pos <= _size);
	size_t len = strlen(s);
	if (_size+len > _capacity)
	{
		reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
	}
	size_t end = _size + len;
	while (end > pos + len -1)
	{
		_str[end] = _str[end - len];
		--end;
	}
	for (size_t i = 0; i < len; i++)
	{
		_str[pos + i] = s[i];
	}
	_size += len;
}

5.erase函数

erase函数是用来删除字符串任意开始位置的n个字符。删除时又有两种情况:
一、pos位置及其之后的有效字符都需要被删除。
这一情况只需在pos位置上放入‘\0’即可,然后更新一下_size。
二、pos位置及其之后的有效字符只需删除一小部分。
在处理这一情况时,我们只需将后面需要保留的有效字符覆盖掉前面需要被删除的字符即可,然后更新一下_size。

void string::erase(size_t pos, size_t len)
{
	assert(pos < _size);
	if (len >= _size - pos)
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		for (size_t i = pos + len; i <= _size; i++)
		{
			_str[i - len] = _str[i];
		}
		_size -= len;
	}
}

6.swap函数

swap函数用于交换两个对象的数据,我们可以直接调用库里面的swap模板函数即可。在调用库中的函数时,需要在swap前面加上“::”的作用域限定符,否则编译器会优先调用你自己实现的swap函数。

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

7.c_str函数

c_str函数用于获取对象C类型的字符串。

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

8.clear函数

clear函数用于将对象中存储的字符串置空,实现时直接将对象的第一个位置放入’\0’,并将对象的_size置为空即可。

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

9.substr函数

substr函数是用于生成新的子串,其需注意的是开辟的空间大小以及拷贝的内容。

string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	// 如果len大于剩余字符长度,更新len
	if (len > _size - pos)
	{
		len = _size - pos;
	}
	string sub;
	sub.reserve(len);
	for (size_t i = 0; i < len; i++)
	{
		sub += _str[pos + i];
	}
	return sub;
}

⏱️五、find函数

find函数是用来在字符串中查找一个字符或者是字符串,其查找的顺序是从字符串开始向后来进行查找。
在查找字符操作时,若找到则返回其下标,否则返回npos(npos是string类的一个静态成员变量,表示为size_t的最大值)。

//查找字符
size_t string::find(char ch, size_t pos)
{
	assert(pos < _size);
	for (size_t i = pos; i < _size; i++)
	{
		if (_str[i] == ch)
		{
			return i;
		}
	}
	return npos;
}

在查找字符串操作时,如果查找的字符串没有找到,则返回npos;否则返回当前字符串中的起始位置和目标字符串中的起始位置的差值,从而返回目标字符串的下标。

//查找字符串
size_t string::find(const char* str, size_t pos)
{
	assert(pos < _size);
	const char* ptr = strstr(pos + _str, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		return ptr - _str;
	}
}

🏝️六、关系运算符重载函数

关系运算符有 >、>=、<、<=、==、!= 这六个,但对于C++中任意一个类的关系运算符重载,我们均只需重载其中的两个,而剩下的四个关系运算符可以通过复用已经重载好了的两个关系运算符来实现。
例如:我们选择重载< 和 == 这两个关系运算符。

//<运算符重载
bool operator<(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

//=运算符重载
bool operator==(const string& s1, const string& s2)
{
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}

而剩余的四个运算符我们就可以通过复用来对其进行实现。

//<=运算符重载
bool operator<=(const string& s1, const string& s2)
{
	return s1 < s2 || s1 == s2;
}
//>运算符重载
bool operator>(const string& s1, const string& s2)
{
	return !(s1 <= s2);
}
//>=运算符重载
bool operator>=(const string& s1, const string& s2)
{
	return !(s1 < s2);
}
//!=运算符重载
bool operator!=(const string& s1, const string& s2)
{
	return !(s1 == s2);
}

🎄七、流插入和流提取运算符重载

一、流插入>>运算符重载

为了防止过多浪费空间,我们需要借助一个插入缓冲区。
过程:输入内容 -> 流入缓冲区 -> 从缓冲区中读取内容 -> 从缓冲区中提取内容 -> 清楚缓冲区。

	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		const int N = 256;
		char buff[N];
		int i = 0;
		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

二、流提取<<运算符重载

在我们之前写了迭代器,因此我们可以使用范围for来对对象进行遍历即可。

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

🚆总结

1.string.cpp

#define _CRT_SECURE_NO_WARNINGS 1

#include"string.h"

namespace bit
{
	const size_t string::npos = -1;

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			cout << "reserve: " << n << endl;
			char* tmp = new char[n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

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

	void string::append(const char* str)
	{
		size_t len = strlen(str);
		if (_size + len > _capacity)
		{
			// 大于2倍,需要多少给多少,小于2倍按2倍扩
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		strcpy(_str + _size, str);
		_size += len;
	}

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

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

	//插入字符
	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		//挪动数据
		size_t end = _size + 1;
		while (end >= pos)
		{
			_str[end + 1] = _str[end];
			--end;
		}
		_str[pos] = ch;
		++_size;
	}
	//插入字符串
	void string::insert(size_t pos, const char* s)
	{
		assert(pos <= _size);
		size_t len = strlen(s);
		if (_size+len > _capacity)
		{
			reserve(_size + len > 2 * _capacity ? _size + len : 2 * _capacity);
		}
		size_t end = _size + len;
		while (end > pos + len -1)
		{
			_str[end] = _str[end - len];
			--end;
		}
		for (size_t i = 0; i < len; i++)
		{
			_str[pos + i] = s[i];
		}
		_size += len;
	}

	void string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len >= _size - pos)
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			for (size_t i = pos + len; i <= _size; i++)
			{
				_str[i - len] = _str[i];
			}
			_size -= len;
		}
	}
	size_t string::find(char ch, size_t pos)
	{
		assert(pos < _size);
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == ch)
			{
				return i;
			}
		}
		return npos;
	}

	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		const char* ptr = strstr(pos + _str, str);
		if (ptr == nullptr)
		{
			return npos;
		}
		else
		{
			return ptr - _str;
		}
	}
	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);
		// 如果len大于剩余字符长度,更新len
		if (len > _size - pos)
		{
			len = _size - pos;
		}
		string sub;
		sub.reserve(len);
		for (size_t i = 0; i < len; i++)
		{
			sub += _str[pos + i];
		}
		return sub;
	}
	bool operator<(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
	bool operator<=(const string& s1, const string& s2)
	{
		return s1 < s2 || s1 == s2;
	}
	bool operator>(const string& s1, const string& s2)
	{
		return !(s1 <= s2);
	}
	bool operator>=(const string& s1, const string& s2)
	{
		return !(s1 < s2);
	}
	bool operator==(const string& s1, const string& s2)
	{
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
	bool operator!=(const string& s1, const string& s2)
	{
		return !(s1 == s2);
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		const int N = 256;
		char buff[N];
		int i = 0;
		char ch;
		ch = in.get();
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			//s += ch;
			ch = in.get();
		}
		if (i > 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
}

2.string.h

#include<iostream>
#include<assert.h>

using namespace std;

namespace bit
{
	class string
	{
	public:
		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;
		}
		// 短小频繁调用的函数放进类里面,默认是内联inline函数
		string()
			:_str(new char[1]{'\0'})
			,_size(0)
			,_capacity(0)
		{}

		string(const char* str)
		{
			_size = strlen(str);
			//_capcacity不包含\0
			_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;
		}

		//赋值(深)
		string& operator =(const string& s)
		{
			if (this != &s)
			{
				delete[] _str;
				_str = new char[s._capacity + 1];
				strcpy(_str, s._str);
				_size = s._size;
				_capacity = s._capacity;
				return*this;
			}
		}
		//析构函数
		~string()
		{
			//_str不为空时
			if (_str)
			{
				delete[] _str;
				_size = _capacity = 0;
			}
		}

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

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

		size_t size() const
		{
			return _size;
		}

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

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

		void reserve(size_t n);
		void push_back(char ch);
		void append(const char* str);
		string& operator +=(char ch);
		string& operator +=(const char* str);
		void insert(size_t pos, char ch);
		void insert(size_t pos, const char* str);
		void erase(size_t pos, size_t len = npos);

		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);
		string substr(size_t pos, size_t len = npos);
		
		
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		static const size_t npos;
	};

	bool operator<(const string& s1, const string& s2);
	bool operator<=(const string& s1, const string& s2);
	bool operator>(const string& s1, const string& s2);
	bool operator>=(const string& s1, const string& s2);
	bool operator==(const string& s1, const string& s2);
	bool operator!=(const string& s1, const string& s2);

	ostream& operator<<(ostream& out, const string& s);
	istream& operator>>(istream& in, string& s); 
}
评论 79
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值