string的模拟实现

目录

一、string类

二、构造函数、拷贝构造函数及析构函数

1、构造函数

2、拷贝构造函数

3、析构函数

三、string类中对容量操作的成员函数

1、size

2、capacity

3、reserve

4、resize

5、clear

6、empty

四、string类中对象的增删查改操作

1、push_back

2、append

3、c_str

4、find

5、substr

6、insert

7、erase

五、string对于重要的运算符重载

1、赋值运算符的重载

2、流插入 <<

3、流提取 >>

4、下标访问 [ ]

5、加等一个字符 +=

6、加等字符串 +=

7、大于 >

8、等于 ==

9、小于 <

10、大于等于 >=

11、小于等于 <=

12、不等于 !=

六、迭代器与范围for

1、迭代器

2、范围for

3、结果对比

七、完整代码


一、string类

1、 string是表示字符串的字符串类。
2、 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
3、 string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string。
4、 不能操作多字节或者变长字符的序列。在使用string类时,必须包含#include<string>头文件以及using namespace std。

那么接下来我们就来模仿标准库里的string类来进行一个模拟实现,进而更深层次地理解string容器。 (为了与标准库区分,我们在自己的命名空间里来实现)

首先根据源码,我们可以知道string中的主义成员变量我们可以写成 char* _str(字符数组),size_t  _size(数据个数),size_t  _capacity(容量大小)。


二、构造函数、拷贝构造函数及析构函数

1、构造函数

string的构造函数分为无参构造和有参构造,通过无参构造的对象会被默认生成一个空字符串,因此我们可以带一个缺省值。没有参数时就直接构造一空串。

string(const char* str = "")
    :_str(new char[strlen(str) + 1])
	, _size(strlen(str))
	, _capacity(strlen(str))
{
    strcpy(_str, str);
}

注:在开空间时,我们需要多开一个空间,因为strlen算出的大小不包含 ‘\0’ ,因此我们需要给 ‘\0’留一个空间。

2、拷贝构造函数

拷贝构造函数是默认成员函数,如果不写编译器会自动生成,对于内置类型完成浅拷贝,对于自定义类型调用其拷贝构造函数完成拷贝。对于string类型来说,如果不自己写拷贝构造函数会导致浅拷贝问题。

 如果仅仅依靠编译器自己提供的拷贝构造函数,就会像上图一样两个对象指向同一块空间。这就是我们所说的浅拷贝,浅拷贝有两大缺点:1、两个对象在析构时都会调用自己的析构函数,这样同一块空间就会被析构两次;2、一个对象的数据改变,另一个对象的数据也改变了。

因此我们自己实现一个拷贝构造函数尤为重要。我们需要进行深拷贝。

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

上面的这种实现方法是比较常见的一种写法,但是我们还有一种更加简便的写法。我们可以通过已经实现了的构造函数传一个常量字符串,即下面的 str . _str,来创建一个l临时对象,然后将这个临时对象的成员与自己交换,这样也完成了拷贝构造。

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

string(const string& str)
	:_str(nullptr)
	,_size(0)
	,_capacity(0)
{
	string tmp(str._str);
    swap(tmp);
}

但是,为了写成这个拷贝构造函数我们还写了一个swap函数,这这么就简便了呢?

那是因为通过查阅标准库我们发现swap函数也是一个string类中提供了的函数,因此我们不仅简便了拷贝构造函数的写法,还又完成了一个函数的实现。并且,因为tmp是一个局部对象,因此在出作用域后就会自动调用析构函数,所以交换后还可以清理掉原来的空间,一举两得。

注:蓝色的swap函数是我们自己写的,而黄色的,前面写明了域的swap函数是C++库中的一个现成的函数。这两个一定要区别开来。

3、析构函数

我们可以使用delete直接释放掉 _str的空间。

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

三、string类中对容量操作的成员函数

1、size

它的功能是返回字符串有效字符的长度。我们可以直接返回其成员变量中的 _size。

size_t size()const
{
	return _size;
}

2、capacity

它的功能是返回返回空间总大小 。我们可以直接返回其成员变量中的 _capacity

size_t capacity()const
{
	return _capacity;
}

3、reserve

它的功能是为字符串预留空间

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

4、resize

它的功能是将有效字符的个数改成n个,多出的空间用字符 ch 填充。
_size < n <_capacity   先预留 n 大小的空间。直接用字符 ch 填充 n - _size 位置,记住要在最后加上 ‘\0’
_size > n  直接将  n 位置的置成 ’\0‘
n > _capacity    先预留 n 大小的空间,剩下的空间用字符 ch 填充,记住要在最后加上 ‘\0’。
void resize(size_t n, char ch = '\0')
{
	if (n > _size)
	{	
		reserve(n);
		for (size_t i = _size; i < n; i++)
		{
			_str[i] = ch;
		}
		_size = n;
		_str[_size] = '\0';
				
	}
	else
	{
	    //删除数据
		_str[n] = '\0';
		_size = n;
	}
}

5、clear

它的作用是清空有效字符
void clear()
{
	_str[0] = '\0';
	_size = 0;
}

6、empty

它的作用是检测字符串释放为空串,是返回true,否则返回false

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

四、string类中对象的增删查改操作

1、push_back

在字符串后尾插字符c
void push_back(char ch)
{
	if (_size == _capacity)
	{
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
	_str[_size] = ch;
	_size++;
	_str[_size] = '\0';
}

2、append

在字符串后追加一个字符串

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

3、c_str

返回C格式字符串
const char* c_str()const
{
	return _str;
}

补充:C语言字符串和 string类字符串的区别(下面我们来通过一段代码来看看他们的区别)

void test_string8()
{
	string s1("hello");
	s1 += '\0';
	s1 += "De Bruyne";
	cout << s1 << endl;
	cout << s1.c_str() << endl;
}

代码运行结果如下:

通过上面的运行结果我们可以看出C语言的字符串一定是通过 ’\0‘ 结束的;而string 的字符串中 ’\0‘ 不一定就是字符串结束的标志。

4、find

npos是string类的静态成员变量,静态成员变量要在类外定义的。我们一般将它设为公共权限,并赋值为 -1。

在字符串中寻找一个字符

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

在字符串中寻找字符串

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

5、substr

它的作用是在str中从pos位置开始,截取n个字符,然后将其返回
string substr(size_t pos, size_t len = npos)const//取子串
{
	assert(pos < _size);
	size_t reallen = len;
	if (reallen == npos || reallen + pos > _size)
	{
		reallen = _size - pos;
	}
	string sub;
	for (size_t i = pos; i < reallen + pos; i++)
	{
		sub += _str[i];
	}
	return sub;
}

6、insert

插入一个字符

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] = _str[end - 1];
		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 (pos + len <= end)
	{
		_str[end] = _str[end - len];
		end--;
	}
	strncpy(_str + pos, str, len);
	_size += len;
	return *this;
}

7、erase

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


五、string对于重要的运算符重载

1、赋值运算符的重载

编译器默认生成的赋值重载也会导致浅拷贝,所以我们需要实现深拷贝。

string& operator=(const string& str)
{
	if (this != &str)
	{
		char* tmp = new char[str._capacity + 1];
		strcpy(tmp, str._str);
		delete[] _str;
		_str = tmp;
		_size = str._size;
		_capacity = str._capacity;
	}
	return *this;
}

和拷贝构造函数一样,我们也可以用简便写法来实现赋值运算符的重载。(注意不要自己给自己赋值)。

string& operator=(const string& str)
{
	if (this != &str)
	{
	    string tmp(str);
		swap(tmp);
	}
	return *this;
} 

2、流插入 <<

类外定义

ostream& operator<<(ostream& out, const string& s)
{
	for (size_t i = 0; i < s.size(); i++)
	{
		out << s[i];
	}
	return out;
}

3、流提取 >>

istream& operator>>(istream& in, string& s)
{
	s.clear();
	char ch;
	ch = in.get();
	const size_t N = 32;
	char buff[N];
	size_t i = 0;
	while (ch != ' ' && ch != '\n')
	{
		buff[i++] = ch;
		if (i == N - 1)
		{
			buff[i] = '\0';
			s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的
						  字符串过大,使s频繁扩容的现象。*/
			i = 0;
		}
		ch = in.get();
	}
	buff[i] = '\0';
	s += buff;

	return in;
}

4、下标访问 [ ]

普通对象:可读可写

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

​

const对象:可读不可写 

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

5、加等一个字符 +=

我们可以直接复用push_back来实现一个字符的加等

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

6、加等字符串 +=

我们可以直接复用append来实现字符串的加等

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

7、大于 >

bool operator>(const string& s)const
{
	return strcmp(_str, s._str) > 0;
}

8、等于 ==

bool operator==(const string& s)const
{
	return strcmp(_str, s._str) == 0;
}

9、小于 <

bool operator<(const string& s)const
{
	return !(_str > s._str) && !(_str == s._str);
}

10、大于等于 >=

bool operator>=(const string& s)const
{
	return !(_str < s._str);
}

11、小于等于 <=

bool operator<=(const string& s)const
{
	return !(_str > s._str);
}

12、不等于 !=

bool operator!=(const string& s)const
{
	return !(_str == s._str);
}

六、迭代器与范围for

1、迭代器

迭代器作为STL的六大组件之一,它的作用十分重要。而在string中迭代器的本质就是一个 char* 或 const char* 的指针。

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

2、范围for

范围for是C++11支持的更简洁的新遍历方式。但是其实它没有什么高大上的,它的本质还是去调用迭代器来实现遍历的。因此我们只要实现了迭代器后,就可以直接使用范围for,根本不用自己去实现。
我们从下面的汇编代码的红色标记处可以看出范围for和迭代器的底层实现都是去call的begin()和end(),因此范围for的底层就是迭代器。

3、结果对比

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

	for (auto e : s)
	{
		cout << e << " ";
	}
		cout << endl;
}

七、完整代码

string.h

#define _CRT_SECURE_NO_WARNINGS
#pragma once
#include<iostream>
using namespace std;
#include<cstring>
#include<cassert>

namespace zdl
{
	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;
		}

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

		//构造函数
		string(const char* str = "")
			:_str(new char[strlen(str) + 1])
			, _size(strlen(str))
			, _capacity(strlen(str))
		{
			strcpy(_str, str);
		}
		/*string()
			:_str(new char[1])
			, _size(0)
			, _capacity(0)
		{
			_str[0] = '\0';
		}*/

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

		/*简便写法 string(const string& str)
					:_str(nullptr)
					,_size(0)
					,_capacity(0)
				{
					string tmp(str._str);
					swap(tmp);
				}*/

		//赋值
		string& operator=(const string& str)
		{
			if (this != &str)
			{
				char* tmp = new char[str._capacity + 1];
				strcpy(tmp, str._str);
				delete[] _str;
				_str = tmp;
				_size = str._size;
				_capacity = str._capacity;
			}
			return *this;
		}

			/*简便写法  string& operator=(const string& str)
			{
				if (this != &str)
				{
					string tmp(str);
					swap(tmp);
				}
				return *this;
			}  */

		//析构函数
		~string()
		{
			delete[] _str;
			_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) const
		{
			assert(pos < _size);
			return _str[pos];
		}

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

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

		void append(const char* str)
		{
			size_t len = strlen(str);
			if (_capacity < len + _size)
			{
				reserve(len + _size);
			}
			strcpy(_str + _size, 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 == _capacity)
			{
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
			
			size_t end = size() + 1;
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				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 (pos + len <= end)
			{
				_str[end] = _str[end - len];
				end--;
			}
			strncpy(_str + pos, str, len);
			_size += len;
			return *this;
		}


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

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

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

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

		string substr(size_t pos, size_t len = npos)const//取子串
		{
			assert(pos < _size);
			size_t reallen = len;
			if (reallen == npos || reallen + pos > _size)
			{
				reallen = _size - pos;
			}
			string sub;
			for (size_t i = pos; i < reallen + pos; i++)
			{
				sub += _str[i];
			}
			return sub;
		}

		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				reserve(n);
				for (size_t i = _size; i < n; i++)
				{
					_str[i] = ch;
				}
				_size = n;
				_str[_size] = '\0';
				
			}
			else
			{
				//删除数据
				_str[n] = '\0';
				_size = n;
			}
		}

		bool operator>(const string& s)const
		{
			return strcmp(_str, s._str) > 0;
		}

		bool operator==(const string& s)const
		{
			return strcmp(_str, s._str) == 0;
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	public:
		static size_t npos;
	};
	size_t string::npos = -1;

	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;
		ch = in.get();
		const size_t N = 32;
		char buff[N];
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			buff[i++] = ch;
			if (i == N - 1)
			{
				buff[i] = '\0';
				s += buff;/*先将输入的字符存在buff数组中,然后一次性+=到s中,这样就避免了输入的字符串过大,使s频繁扩容的现象。*/
				i = 0;
			}
			ch = in.get();
		}
		buff[i] = '\0';
		s += buff;

		return in;
	}

	void test_string1()
	{
		string s1("hello world");
		string s2(s1);
		string s3;
		cout << "s1: " << s1 << endl;
		cout << "s2: " << s2 << endl;
		cin >> s3;
		cout << "s3: " << s3 << endl;
		string s4("ZD");
		s4 += 'L';
		cout << "s4: " << s4 << endl;
		s4 += " ";
		s4 += "and";
		s4 += " ";
		s4 += "De Bruyne";
		cout << "s4: " << s4 << endl;
	}

	void test_string2()
	{
		string s5("hello");
		s5.append("ZDL 17");
		cout << "s5: " << s5 << endl;
	}

	void test_string3()
	{
		string s6("hello");
		s6.insert(0, '#');
		cout << "s6: " << s6 << endl;
	}

	void test_string4()
	{
		string s7("hello");
		s7.insert(2, "ZDL");
		cout << "s7: " << s7 << endl;
	}

	void test_string5()
	{
		string s8("helloeeeeeelllooo");
		s8.erase(2, 2);
		cout << "s8: " << s8 << endl;
	}

	void test_string6()
	{
		string s9("hello world ZDL waiting for love");
		string s10 = s9.substr(6, 5);
		cout << "s10: " << s10 << endl;
	}

	void test_string7()
	{
		string s11("hello world ZDL waiting for love");
		string s12("shfshf");
		cout << "比较: " << (s11 > s12) << endl;
		string s13("De Bruyne");
		string s14("De Bruyne");
		cout << "s13 与 s14: " << (s13 == s14) << endl;
	}

	void test_string8()
	{
		string s1("hello");
		s1 += '\0';
		s1 += "De Bruyne";
		cout << s1 << endl;
		cout << s1.c_str() << endl;
	}

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

		for (auto e : s)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

test.cpp

#include"string.h"

int main()
{
	zdl::test_string1();
	cout << "------------------------------" << endl;
	zdl::test_string2();
	cout << "------------------------------" << endl;
	zdl::test_string3();
	cout << "------------------------------" << endl;
	zdl::test_string4();
	cout << "------------------------------" << endl;
	zdl::test_string5();
	cout << "------------------------------" << endl;
	zdl::test_string6();
	cout << "------------------------------" << endl;
	zdl::test_string7();
	cout << "------------------------------" << endl;
	zdl::test_string8();
	cout << "------------------------------" << endl;
	zdl::test_string9();
	return 0;
}

  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值