深度学习 string 类之模拟实现一个 string 类(C++)

在学习 string 类时我们认识了很多关于 string 类的接口,这些接口可以很好的帮助我们解决问题并简化代码,所以接下来我们要自行实现一个 string 类来加深对 string 类的理解 

关于 string 类的详细讲解参考博文:面向对象程序设计(C++)之 String 类

1. 构造与析构函数

string 类中的构造函数通常有无参与带参构造,这里我们都进行实现并且整合优化

using namespace std;
namespace bit
{
	class string
	{
	public:
		//无参构造函数
		string()
			:_str(new char[1] {'\0'})
			,_size(0)
			,_capacity(0)
		{}
		//带参构造
		string(const char* str)
		{
			//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
			_size = strlen(str);
			_capacity = _size;
			str = new char[_capacity + 1];
			strcpy(_str, str);//将str拷贝给_str 
		}
		//全缺省构造函数,上面二者合并
		string(const char* str = "")
		{
			//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
			_size = strlen(str);
			_capacity = _size;
			str = new char[_capacity + 1];
			strcpy(_str, str);//将str拷贝给_str 
		}
        //拷贝构造函数
        string(const string& s)//深拷贝
        {
	       _str = new char[s._capacity + 1];//多开一个空间存储'/0'
	       strcpy(_str, s._str);
	       _size = s._size;
	       _capacity = s._capacity;
        }
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
	
	private:
		char* _str;//字符串数组
		size_t _size;//长度
		size_t _capacity;//容量
	};
}

2. 遍历的实现

string 遍历有下标遍历、迭代器遍历与范围for遍历,这里我们一一实现

范围for的底层逻辑实际就是迭代器,所以模拟实现一个迭代器的操作就可以使用二者,下标遍历需要模拟实现 [] 符号的重载,然后使用 for 循环遍历即可  

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_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		无参构造函数
		//string()
		//	:_str(new char[1] {'\0'})
		//	,_size(0)
		//	,_capacity(0)
		//{}
		带参构造
		//string(const char* str)
		//{
		//	//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
		//	_size = strlen(str);
		//	_capacity = _size;
		//	str = new char[_capacity + 1];
		//	strcpy(_str, str);//将str拷贝给_str 
		//}
		//全缺省构造函数,上面二者合并
		string(const char* str = "")
		{
			//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
			_size = strlen(str);
			_capacity = _size;
			str = new char[_capacity + 1];
			strcpy(_str, str);//将str拷贝给_str 
		}
		//拷贝构造函数
		string(const string& s)//深拷贝
		{
			_str = new char[s._capacity + 1];//多开一个空间存储'/0'
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
		const char* c_str() const
		{
			return _str;
		}

		//返回长度
		size_t size()
		{
			return _size;
		}
		//返回容量
		size_t capacity()
		{
			return _capacity;
		}
		//下标实现[] 返回引用可读可写
		char& operator[](size_t pos)
		{
			assert(pos < _size);//防止越界
			return _str[pos];
		}
		//只读类型
		char& operator[](size_t pos) const
		{
			assert(pos < _size);//防止越界
			return _str[pos];
		}

	private:
		char* _str;//字符串数组
		size_t _size;//长度
		size_t _capacity;//容量
		static const size_t npos;
	};
}
void test_string_1()
{
	string s1;
	string s2("manbo");
	cout << s1.c_str() << endl;
	cout << s2.c_str() << endl;
	//模拟实现遍历
	for (auto e : s2)
	{
		cout << e << endl;
	}
	cout << endl;

	string::iterator it = s2.begin();
	while (it != s2.end())
	{
		cout << *it << " ";
	}
	cout << endl;
}

3. 基础函数的实现 

实现在字符串一些基础函数函数,如push_back、insert函数等

关于string类的所以函数详情见 string类 ,这里我们实现的有在指定位置插入字符/字符串,在指定位置删除字符/字符串,查找指定字符/字符串的位置,模拟实现比较两字符串(ASCII码),重载流插入流提取,在容量不足时扩容

#define _CRT_SECURE_NO_WARNINGS 1
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<iostream>
#include<string>
#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_iterator begin() const
		{
			return _str;
		}
		const_iterator end() const
		{
			return _str + _size;
		}
		无参构造函数
		//string()
		//	:_str(new char[1] {'\0'})
		//	,_size(0)
		//	,_capacity(0)
		//{}
		带参构造
		//string(const char* str)
		//{
		//	//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
		//	_size = strlen(str);
		//	_capacity = _size;
		//	str = new char[_capacity + 1];
		//	strcpy(_str, str);//将str拷贝给_str 
		//}
		//全缺省构造函数,上面二者合并
		string(const char* str = "")
		{
			//capacity 中不包含'/0',所以在开辟_str所需空间时才会多开一个存储'\0'
			_size = strlen(str);
			_capacity = _size;
			str = new char[_capacity + 1];
			strcpy(_str, str);//将str拷贝给_str 
		}
		//拷贝构造函数
		string(const string& s)//深拷贝
		{
			_str = new char[s._capacity + 1];//多开一个空间存储'/0'
			strcpy(_str, s._str);
			_size = s._size;
			_capacity = s._capacity;
		}
		//析构函数
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}

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

		//返回长度
		size_t size()
		{
			return _size;
		}
		//返回容量
		size_t capacity()
		{
			return _capacity;
		}
		//清除数据不缩小容量
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		//下标实现[] 返回引用可读可写
		char& operator[](size_t pos)
		{
			assert(pos < _size);//防止越界
			return _str[pos];
		}
		//只读类型
		char& operator[](size_t pos) const
		{
			assert(pos < _size);//防止越界
			return _str[pos];
		}

	private:
		char* _str;//字符串数组
		size_t _size;//长度
		size_t _capacity;//容量
		static const size_t npos;
	};
}

3.1 增删查改

插入:insert、append、push_back、operator+=

删除:erase

扩容:reserve

查找:find

取出指定长度指定位置字符串:substr

插入:

1. insert 

insert 函数是在指定位置插入字符/字符串,模拟实现的思路是讲指定位置之后的字符均向后移动所需插入字符/字符串长度,使其预留出来的长度刚好满足插入指定字符/字符串,最终完成插入

需要注意的是:在容量不够时需要扩容,并且需要根据实际大小来扩容,还要注意移动数据时不能出现下标越界,例如代码中注释的情况,C语言中的隐式类型转换可能导致下标越界

void string::insert(size_t pos, char ch)
{
	assert(pos <= _size);
	//空间不够就会扩容
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	//依次挪动数据
	//size_t end = _size;
	//while ((int)pos <= end)//防止隐式类型转换将无符号转变为有符号,导致end为-1时仍然在比较
	//{
	//	_str[end + 1] = _str[end];
	//	end--;
	//}
	size_t end = _size + 1;
	while (pos < end)//解决方法二
	{
		_str[end] = _str[end - 1];
		end--;
	}
	_str[pos] = ch;
	_size++;
}
void string::insert(size_t pos, const char* str)
{
	assert(pos <= _size);
	size_t len = strlen(str);
	//扩容,小于两倍按二倍扩容,大于二倍需要多少扩容多少
	if (len + _size > _capacity)
	{
		reserve((len + _size) > 2 * _capacity ? (len + _size) : 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] = str[i];
	}
	_size += len;
}
2. append

append 函数功能是在字符串末尾添加字符串,所以直接扩容后拷贝所要添加的字符串即可

void string::append(const char* str)
{
	size_t len = strlen(str);
	if ((len + _size) > _capacity)
	{
		//如果所需空间比原空间容量两倍还大那么需要多少开多少,否则按照原空间容量的二倍扩容
		reserve((len + _size) > _capacity * 2 ? (len + _size) : _capacity * 2);
	}
	strcpy(_str + _size, str);//直接在结尾拷贝输入的字符串即可
	_size += len;
}
3. operator+=

operator+=就是直接在原有字符串后添加字符/字符串,可以复用push_back 函数实现

string& string::operator+=(char ch)
{
	push_back(ch);
	return *this;
}
string& string::operator+=(const char* str)
{
	append(str);
	return *this;
}
4. 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';
}

删除

erase 函数是在指定位置删除指定长度字符/字符串,需要注意的是,当给定长度超过原字符串长度,那么就直接删除剩余字符即可;如果不超出那么就直接覆盖即可

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

扩容

reserve 函数的目的是保证时刻的容量都是充足的,多开一个空间是为了存储'\0'

const size_t npos = -1;
void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		//创建新数组,拷贝原数组数据,释放原数组,将原数组指向新数组
		char* tmp = new char[_capacity + 1];
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

查找

find 函数的目的是查找指定字符/字符串,如果匹配则返回其下标/起始位置下标,不匹配返回npos(默认值-1)

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(_str + pos, str);
	if (ptr == nullptr)
	{
		return npos;
	}
	else
	{
		//指针相减返回下标
		return ptr - _str;
	}
}

取出指定长度指定位置字符串 

substr 函数是在函数内部创建一个名为 sub 的 string 类,然后拷贝需要取出的元素即可需要注意的是,不能使用默认构造函数,因为默认构造函数是浅拷贝,要自己写一个构造函数以达到深拷贝的目的

//注意此时为浅拷贝,需要显式定义拷贝构造函数使其成为深拷贝
string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);
	
	//如果超出字符串范围则更新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;
}

3.2 比较字符串

通过重载 >、<、>=、<=、==、!= 来实现比较两字符串

这里使用了 strcmp 函数首先实现两个基本函数,之后的函数复用即可

//先实现小于和等于,之后的函数可以不断复用即可
bool operator<(const string& s1, const string& s2)
{
	//strcmp 直接比较两字符串,根据ASCII码来比较
	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);
}
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);
}

3.3 流提取与流插入

通过重载 >> 与 << 来实现 string 类的流提取与流插入

流插入直接使用范围 for 来逐次插入即可,要注意的是流提取,cin 不能识别空格与换行,需要使用 get 函数逐个插入,并且要注意扩容次数不宜太多,所以这里模拟了一个缓冲区,创建了一个 buff 数组,直接先插入 buff 数组,最后将 buff 数组插入到原字符串即可

ostream& operator<<(ostream& out, const string& str)
{
	for (auto ch : str)
	{
		out << ch;
	}
	return out;
}
//istream& operator>>(istream& in, string& str)
//{
//	str.clear();

//	char ch;
//	//in>>ch;cin默认提取不到换行与空格,所以使用 get 函数逐个字符提取
//	ch = in.get();
//	while (ch != ' ' && ch != '\n')
//	{
//		str += ch;
//		ch = in.get();
//	}
//	return in;
//}
//减少扩容次数
istream& operator>>(istream& in, string& str)
{
	str.clear();
	const int N = 256;
	char buff[N];
	int i = 0;
	char ch;
	//in>>ch;cin默认提取不到换行与空格,所以使用 get 函数逐个字符提取
	ch = in.get();
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			str += buff;
			i = 0;
		}
		ch = in.get();
	}
	if (i > 0)//当buff没有放满时
	{
		buff[i] = '\0';
		str += buff;
	}
	return in;
}

 

  • 19
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值