string类简单模拟实现

 

目录

1. string成员变量

2. string的成员函数

(1). 构造函数和析构函数

(2). string的访问

① 范围for访问

②下标访问

(3). string的修改

① 函数重载+=

② push_back与append的实现

③ insert的实现

④ erase与clear的实现

⑤ 交换函数

(4). string的查找

(5).  string的流插入与流提取的重载

(6). 其他一些操作

① 比较

② substr

③ 赋值运算符重载

3. 源代码

(1). 头文件

(2). 实现功能的


我们实现string将其封装到一个namespace(命名空间中)

1. string成员变量

我们在使用string的时候发现它存储字符串,并且可以动态增加容量,于是我们用_str 来存储字符串,用_size来记录它存储的字符个数,用_capacity来记录容量

此外我们还实现了一个静态变量 npos

#pragma once
#include<iostream>
#include<assert.h>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
namespace Pc
{
	class string
	{
	public:
        //static const size_t npos = -1;
		static const size_t npos ;
        ........
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

静态变量是类内声明,类外定义。但这个在类内也可以直接赋值如上注释代码。此为个例,我在此处仍然在另一个文件(同名namespace)定义

2. string的成员函数
(1). 构造函数和析构函数

实现之前先在类里实现三个函数来获取取的成员变量的值

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

		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}

我们实现的三个构造函数和一个析构函数

#pragma once
#include<iostream>
#include<assert.h>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
namespace Pc
{
	class string
	{
	public:
		string()
			:_str(new char[5])//_caoacity不包含\0
			,_size(0)
			,_capacity(4)
		{
			_str[0] = '\0';
		}
		string(const char* str)
			:_str(new char [strlen(str) + 1])
			//_caoacity不包含\0
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			strcpy(_str, str);
		}
		string(const string& s1)
			:_str(new char [s1.capacity()+1])
			, _size(s1.size())
			, _capacity(s1.capacity())
		{
			strcpy(_str, s1.c_str());
		}
		~string()
		{
			delete[] _str;//str是nullptr也没问题
			_str = nullptr;
			_size = _capacity = 0;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

}

上面的默认构造函数我们开5个空间容量就为4,因为_capacity中不包括'\0' 所以实际开的要多一个。

通过字符串构造的string只需要用strlen将字符串的长记录下来赋值就可以了。

拷贝构造函数可以用过我们上面实现的成员函数来访问s1.的各个成员变量的值

析构函数直接将new的空间delete掉,将指针指向空即可。

(2). string的访问
① 范围for访问

范围for底层实际是通过迭代器实现的,所以将迭代器实现出来就可以使用范围for了

这里实现一个简易版的范围for

		typedef char* iterator;
		typedef 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中iterator就是一个指针所以我们这里将char* typedef一下用来模拟实现

②下标访问

即重载 [] 使其接收一个下标,返回对应位置的字符,引用返回减少消耗,与可读写,只读两种接口

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
(3). string的修改

我们先实现一个扩容的函数reserve,用来减少代码量

这些都从类外实现所以需要域作用限定符

	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[ n + 1];
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}
① 函数重载+=

由于会+=字符或+=字符串所以进行函数重载,实现时注意越界问题

	string& string::operator+=(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		int len = strlen(str);
		if (_size + len >= _capacity)
		{
			reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);

		}
		for (int i = 0; i <= len; i++)//假设字符串为"11\0"
		{
			_str[_size + i] = str[i];
		}
		_size = _size + len;
		return *this;
	}

② push_back与append的实现

由于实现了两个+=我们直接复用即可

	void string::push_back(char ch)
	{
		*this += ch;
	}
	void string::append(const char* str)
	{
		*this += str;
	}
③ insert的实现

我们需要注意size_t类型是无符号整形,所以没有负值,我们可以通过下标加一,操作时整体减一,或将size_t都进行强制类型转换再操作等等

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		size_t n = _size+1;
		//while (n >= pos)//隐式类型转换
		//{
		//	_str[n + 1] = _str[n];
		//	n--;
		//}
		while (n > pos)
		{
			_str[n ] = _str[n-1];
			n--;
		}

		_str[pos] = ch;
		_size++;
	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		int len = strlen(str);
		if (_size + len >= _capacity)
		{
			reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);
		}

		//for (int i = _size; i >= pos; i--)//隐式类型转换
		//{
		//	_str[i + len] = _str[i];
		//}

		for (int i = _size+1; i >pos; i--)
		{
			_str[i + len-1] = _str[i-1];
		}

		for (int i = 0; i < len; i++)
			_str[pos + i] = str[i];
		_size += len;
	}
④ erase与clear的实现

erase实现如下

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

	}

clear由于代码量小直接在类内实现,直接在下标为0处给'\0'  即可

		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
⑤ 交换函数

我们来实现string里的swap

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

 各种信息直接交换很高效,C++98中全局的swap实现交换需要完成三次深拷贝,string里的swap优与全局的,但C++11中,全局swap检测到string会直接调用string里的swap因此效率差不多

拷贝构造函数也可以写为

		string(const string& s1)
		{
			string tmp(s1._str);
			swap(tmp);
		}
(4). string的查找

查找字符十分简单,查找字符串我们可已通过strstr来实现,他如果找到会返回要查找字符串的第一个字符地址若没有找到返回null指针

	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 -1;
	}
	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		const char* ps = strstr(_str + pos, str);

		if (ps == nullptr)
		{
			return npos;
		}
		else
		{
			return ps - _str;
		}
	}
(5).  string的流插入与流提取的重载

我们在实现流提取时需要注意cin遇见空格与换行是不会读进去的话这时候我们就要用到一个函数get()了

	ostream& operator<<(ostream& out, string& s)
	{
		out << s.c_str();
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		
		//错误示范
		//char ch;
		//in >> ch;
		//while (ch != ' ' && ch != '\n')
		//{
		//	s += ch;
		//	in >> ch;//遇到空格或换行符就不会读进去
		//}

		//正确
		//char ch;
		//ch = in.get();
		//while (ch != ' ' && ch != '\n')
		//{
		//	s += ch;
		//	ch = in.get();//不管什么类型一个字符一个字符的读
		//}

		//优化,减少存储字符串很长时扩容次数
		const int N = 256;//字符串特别长,N给大一点点还会减少扩容
		char charval[N];
		int i = 0;
		charval[i] = in.get();
		while (charval[i] != ' ' && charval[i] != '\n')
		{
			charval[++i] = in.get();
			if (i == N-1)
			{
				charval[i] = '\0';
				s += charval;
				i = 0;
			}
		}
		if(i>0)
		{
			charval[++i] = '\0';
			s += charval;
		}

		return in;
	}

那还会有一个问题,输入的字符串太长,需要频繁扩容,那么消耗会很大,我们可以建立一个数组等这个数组存满了再一次性给string对象,如果没满就结束了,那就退出循环将存储的值给对象(不要忘记给数组'\0',而且数组要留一个存储'\0')。这样就要有效的减少扩容次数

我们顺便实现getline(代码几乎一样)

	istream& getline(istream& in, string& str, char delim)
	{
		str.clear();
		//优化,减少存储字符串很长时扩容次数
		const int N = 256;//字符串特别长,N给大一点点还会减少扩容
		char charval[N];
		int i = 0;
		charval[i] = in.get();
		while (charval[i] != delim)
		{
			charval[++i] = in.get();
			if (i == N - 1)
			{
				charval[i] = '\0';
				str += charval;
				i = 0;
			}
		}
		if (i > 0)
		{
			charval[++i] = '\0';
			str += charval;
		}

		return in;
	}
	istream& getline(istream& in, string& str)
	{
		str.clear();
		const int N = 256;//字符串特别长,N给大一点点还会减少扩容
		char charval[N];
		int i = 0;
		charval[i] = in.get();
		while (charval[i] != '\n')
		{
			charval[++i] = in.get();
			if (i == N - 1)
			{
				charval[i] = '\0';
				str += charval;
				i = 0;
			}
		}
		if (i > 0)
		{
			charval[++i] = '\0';
			str += charval;
		}

		return in;
	}
(6). 其他一些操作
① 比较

字符串之间的比较是比较ASCII码值,我们通过strcmp实现,再复用代码

	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);
	}
	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);
	}
② substr
	string string::substr(size_t pos , size_t len ) const
	{
		assert(pos < _size);
		if (len > _size - pos)
			len = _size - pos;

		string s1;
		s1.reserve(len);
		for (int i = 0; i < len; i++)
		{
			s1 += _str[pos + i];
		}
		return s1; 
	}
③ 赋值运算符重载

我们判断是不是给自己赋值,是就跳过,不是就创建一个新变量拷贝构造后进行交换

出了if就将tmp给销毁了,而this指针指向的就是tmp初始化的地址,销毁的为this之前的地址

		string& operator=(const string& s)
		{
			if (this != &s)//是自己就没必要赋值了
			{
				string tmp(s);
				swap(tmp);
			}
			return *this;
		}

而我们可以将其简化为,不是引用传参,那他就进行了一次拷贝构造了交换即可

		string& operator=(string tmp)
		{
			//不需要判断是否是给自己赋值,因为tmp变量已经创建出来了
			swap(tmp);
			return *this;
		}
3. 源代码
(1). 头文件
#pragma once
#include<iostream>
#include<assert.h>
#include<cstring>
using std::cout;
using std::cin;
using std::endl;
using std::istream;
using std::ostream;
namespace Pc
{
	class string
	{
	public:
		typedef char* iterator;
		typedef char* const_iterator;
		//static const size_t npos = -1;
		static const size_t npos ;
		//实现迭代器
		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[5])
			,_size(0)
			,_capacity(4)
		{
			_str[0] = '\0';
		}
		string(const char* str)
			:_str(new char [strlen(str) + 1])
			//_caoacity不包含\0
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			strcpy(_str, str);
		}

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

		string(const string& s1)
			//:_str(new char [s1.capacity()+1])
			//, _size(s1.size())
			//, _capacity(s1.capacity())
		{
			//strcpy(_str, s1.c_str());
			string tmp(s1._str);
			swap(tmp);
		}
		//string& operator=(const string& s)
		//{
		//	if (this != &s)//是自己就没必要赋值了
		//	{
		//		string tmp(s);
		//		swap(tmp);
		//	}
		//	return *this;
		//}
		string& operator=(string tmp)
		{
			//不需要判断是否是给自己赋值,因为tmp变量已经创建出来了
			swap(tmp);

			return *this;
		}
		~string()
		{
			delete[] _str;//str是nullptr也没问题
			_str = nullptr;
			_size = _capacity = 0;
		}

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

		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		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);
		string substr(size_t pos = 0, size_t len = npos) const;
		
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);


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


	};
	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);
	istream& operator>>(istream& in, string& s);
	ostream& operator<<(ostream& out, string& s);

	istream& getline(istream& is, string& str, char delim);
	istream& getline(istream& is, string& str);

}
(2). 实现功能的
#include"string.h"

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

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

	void string::push_back(char ch)
	{
		*this += ch;
	}
	void string::append(const char* str)
	{
		*this += str;
	}

	string& string::operator+=(char ch)
	{
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}

		_str[_size] = ch;
		_size++;
		_str[_size] = '\0';
		return *this;
	}
	string& string::operator+=(const char* str)
	{
		int len = strlen(str);
		if (_size + len >= _capacity)
		{
			reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);

		}
		for (int i = 0; i <= len; i++)//假设字符串为"11\0"
		{
			_str[_size + i] = str[i];
		}
		_size = _size + len;
		return *this;
	}

	void string::insert(size_t pos, char ch)
	{
		assert(pos <= _size);
		if (_size == _capacity)
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		size_t n = _size+1;
		//while (n >= pos)//隐式类型转换
		//{
		//	_str[n + 1] = _str[n];
		//	n--;
		//}
		while (n > pos)
		{
			_str[n ] = _str[n-1];
			n--;
		}

		_str[pos] = ch;
		_size++;
	}
	void string::insert(size_t pos, const char* str)
	{
		assert(pos <= _size);
		int len = strlen(str);
		if (_size + len >= _capacity)
		{
			reserve(_capacity * 2 > _size + len ? _capacity * 2 : _size + len);
		}

		//for (int i = _size; i >= pos; i--)//隐式类型转换
		//{
		//	_str[i + len] = _str[i];
		//}

		for (int i = _size+1; i >pos; i--)
		{
			_str[i + len-1] = _str[i-1];
		}

		for (int i = 0; i < len; i++)
			_str[pos + i] = str[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
		{
			size_t n = pos;
			for (size_t i = pos + len; i <= _size; i++)
			{
				_str[n] = _str[i];
				n++;
			}
			_size -= len;
		}

	}
	string string::substr(size_t pos , size_t len ) const
	{
		assert(pos < _size);
		if (len > _size - pos)
			len = _size - pos;

		string s1;
		s1.reserve(len);
		for (int i = 0; i < len; i++)
		{
			s1 += _str[pos + i];
		}
		return s1; 
	}
	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 -1;
	}
	size_t string::find(const char* str, size_t pos)
	{
		assert(pos < _size);
		const char* ps = strstr(_str + pos, str);

		if (ps == nullptr)
		{
			return npos;
		}
		else
		{
			return ps - _str;
		}
	}
	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);
	}
	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, string& s)
	{
		out << s.c_str();
		return out;
	}
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		
		//错误示范
		//char ch;
		//in >> ch;
		//while (ch != ' ' && ch != '\n')
		//{
		//	s += ch;
		//	in >> ch;//遇到空格或换行符就不会读进去
		//}

		//正确
		//char ch;
		//ch = in.get();
		//while (ch != ' ' && ch != '\n')
		//{
		//	s += ch;
		//	ch = in.get();//不管什么类型一个字符一个字符的读
		//}

		//优化,减少存储字符串很长时扩容次数
		const int N = 256;//字符串特别长,N给大一点点还会减少扩容
		char charval[N];
		int i = 0;
		charval[i] = in.get();
		while (charval[i] != ' ' && charval[i] != '\n')
		{
			charval[++i] = in.get();
			if (i == N-1)
			{
				charval[i] = '\0';
				s += charval;
				i = 0;
			}
		}
		if(i>0)
		{
			charval[++i] = '\0';
			s += charval;
		}

		return in;
	}
	istream& getline(istream& in, string& str, char delim)
	{
		str.clear();
		//优化,减少存储字符串很长时扩容次数
		const int N = 256;//字符串特别长,N给大一点点还会减少扩容
		char charval[N];
		int i = 0;
		charval[i] = in.get();
		while (charval[i] != delim)
		{
			charval[++i] = in.get();
			if (i == N - 1)
			{
				charval[i] = '\0';
				str += charval;
				i = 0;
			}
		}
		if (i > 0)
		{
			charval[++i] = '\0';
			str += charval;
		}

		return in;
	}
	istream& getline(istream& in, string& str)
	{
		str.clear();
		const int N = 256;//字符串特别长,N给大一点点还会减少扩容
		char charval[N];
		int i = 0;
		charval[i] = in.get();
		while (charval[i] != '\n')
		{
			charval[++i] = in.get();
			if (i == N - 1)
			{
				charval[i] = '\0';
				str += charval;
				i = 0;
			}
		}
		if (i > 0)
		{
			charval[++i] = '\0';
			str += charval;
		}

		return in;
	}

}

这篇文章就到这里啦~

希望能帮到你

(づ ̄3 ̄)づ╭❤~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值