当前位置: 首页 > article >正文

【C++】string模拟实现

各位读者老爷好,俺最近在学习string的一些知识。为了更好的了解string的结构,俺模拟实现了一个丐版string,有兴趣的老爷不妨垂阅!!!

目录

1.string类的定义

2.模拟实现成员函数接口 

2.1.constructor(构造函数)

2.2.destructor(析构函数)

2.3.c_str 

 2.4.size

 2.5.operator[]

2.6.begin、end

2.7.capacity

2.8.reserve 

2.9.push_back 

2.10.append 

2.11.operator+=

2.12.insert 

2.13.erase 

2.14.find

 2.15.substr

2.16.constructor

2.17.operator=

 2.18.clear

 2.19.swap

 3.模拟实现非成员函数接口

3.1.relational operators

3.2.operator<< 

3.3.operator>> 

3.4.getline

4.个别接口的不同写法

 4.1.constructor的不同写法

4.2.operator=的不同写法

4.3.operator>>的不同写法

5. string模拟实现完整代码

5.1.string.h

5.2.string.cpp 

5.3.test.cpp 


俺在上篇博客介绍过,string是一个由类模板实例化而来的类,是用来管理字符串的。其底层实现大致就是字符顺序表。

那么俺模拟实现string,是不是要从类模板开始搞起呢?当然不是,俺可没有这个本事。俺直接定义string类就好。并且string的接口众多,俺只模拟实现部分常用接口噢噢!!

1.string类的定义

namespace HD
{
	class string
	{
		char* _str = nullptr;
		size_t _capacity = 0;
		size_t _size = 0;
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		static const size_t npos;

		//string()
		//	:_str(new char[1]{'\0'})
		//	,_capacity(0)
		//	,_size(0)
		//{
		//}

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

		string(const char* s = "")//以上两构造合二为一
		{
			_size = strlen(s);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, s);
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 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];
		}

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		const_iterator end()const
		{
			return _str + _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

		string(const string& str)//拷贝构造函数
		{
			/写法一/
			/*_str = new char[str._capacity + 1];
			strcpy(_str, str._str);
			_size = str._size;
			_capacity = str._capacity;*/

			/写法二/
			string tmp(str.c_str());
			swap(tmp);
		}
		void reserve(size_t n = 0);
		void push_back(char c);
		string& append(const char* s);
		string& operator+= (const char* s);
		string& operator+=(char c);
		string& insert(size_t pos, size_t n, char c);
		string& insert(size_t pos, const char* s);
		string& erase(size_t pos = 0, size_t len = npos);
		size_t find(char c, size_t pos = 0) const;
		size_t find(const char* s, size_t pos = 0) const;
		string substr(size_t pos = 0, size_t len = npos) const;
		string& operator=(const string& str);
		//string& operator=(const string str);
		void clear();
		void swap(string& str);
	};
}

对于这个string类的定义如上。

1.俺们知道string类底层是字符顺序表,所以俺定义3个private成员变量_str、_capacity、_size来实现,并且给这3个成员变量缺省值,这3个缺省值是很有意义的,意义的体现会在下面的讲解可见。

2.俺将一些成员函数(接口)的声明和定义全部放到string类当中,则默认当成内联函数处理;还有一些成员函数(接口)的声明和定义分离,只将这些成员函数的声明放到string类当中。

3.为了防止与命名空间std的string(也就是STL的string)产生冲突,俺实现的string使用自己命名空间(如HD)封装起来。

PS:以下所说的string若没有特殊声明,都默认指俺模拟实现的string。若指STL的string,俺会用红色字体标注string,如string

2.模拟实现成员函数接口 

2.1.constructor(构造函数)

对于这个接口,string内重载了很多个函数。俺暂时就模拟实现两个,如下:


1.string()

这个函数的模拟实现如下,俺将其声明和定义都放到string类中:

		string()
			:_str(new char[1]{'\0'})
			,_capacity(0)
			,_size(0)
		{
		}

这里俺使用初始化列表来初始化。易知这是一个构造空的string对象的,那么当然将_capacity和_size初始化成0;但是对于_str,俺将其指向堆区动态开辟的一个字节的空间,这块空间存储字符'\0'。

为什么_str不用nullptr来初始化呢?其实大有意义,让子弹再飞一会,原因俺下面介绍。


3.string (const char* s)

这个函数的模拟实现如下,俺将其声明和定义都放到string类中:

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

这里俺在函数体内初始化。俺们知道这个构造函数的功能是用C-string来构造string类对象。那么我们开好空间(记得多开一个空间存放'\0'),将C_string(也就是s)拷贝到空间上,_size和_capacity 初始化为C_string的字节数(长度)就好。


2.2.destructor(析构函数)

这个接口就是析构函数,string类中申请了资源,俺们必须显示写析构函数,不然会造成资源的泄露。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

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

若不显示写析构函数,_str指向的空间就泄露了。

2.3.c_str 

 这个接口功能就是返回C形式的字符串,也就是返回底层的_str。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

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

这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数。


俺们来使用以下自己模拟实现的接口。事先要先知道的是本工程包含3个文件

……

string.h:存放string类的定义和非成员函数的声明;

……

string.cpp:存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义;

……

test.cpp:存放测试代码,来测试咱们模拟实现的string的功能;

……

俺们到现在介绍了几个接口的模拟实现了,咱们在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test1()
	{
		string S1;
		string S2("hello world");
		cout << S1.c_str() << endl << S2.c_str() << endl;
	}
}
int main()
{
	HD::test1();
	return 0;
}

运行结果:

 有几个点需要明确:

……

1.为什么test1函数需要用命名空间HD封装起来?

因为若是不用命名空间HD封装起来,string类实例化对象S1、S2会调用string的构造函数接口,且c_str接口的调用亦是调用string的接口,这样子就违背了我们测试模拟实现的接口的初衷。有关命名空间和编译器访问规则的介绍可以看[C++]C++入门1.0。

……

2.主函数中调用test1函数为什么要指定命名空间HD域?

因为若是不指定命名空间HD域,编译器会访问不到test1函数。编译器的访问规则介绍可以看[C++]C++入门1.0。

……

3.string()函数的定义中,_str不用nullptr来初始化呢?

若是用nullptr来初始化_str,当空string对象调用c_str接口时就返回nullptr,当我们对这个返回值进行操作时就有可能访问到空指针,程序就会崩溃。


 2.4.size

 这个接口这个接口返回字符串的长度。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		size_t size()const
		{
			return _size;
		}

 这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数。

在test.cpp中测试一下:

#include"string.h"
namespace HD
{
	void test2()
	{
		string S1;
	    string S2("hello world");
		cout << S1.size() << endl << S2.size() << endl;
	}
}
int main()
{
	HD::test2();
	return 0;
}

运行结果:

 2.5.operator[]

本接口返回对字符串中位置pos处的字符的引用。且string中本接口重载了2个函数分别供普通对象和const对象使用。俺也模拟实现2个函数,并将其声明和定义都放到string类中:

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

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

在test.cpp中测试一下:

#include"string.h"
namespace HD
{
	void test3()
	{
		string S1("nbo");
		for (int i = 0; i < S1.size(); i++)
		{
			S1[i] -= 1;
			cout << S1[i];
		}
		cout << endl;
		const string S2("hello world");
		for (int i = 0; i < S2.size(); i++)
		{
			cout << S2[i];
		}
	}
}
int main()
{
	HD::test3();
	return 0;
}

对于char& operator[](size_t pos)这个函数,这个函数是一个对this指针指向对象的成员变量只进行读访问的函数,可是我们不能将其实现成const成员函数,为什么呢?

因为俺们实现了const char& operator[](size_t pos)const这个函数供const对象调用,若是还将char& operator[](size_t pos)实现成const成员函数的话,无法与const char& operator[](size_t pos)构成函数重载!

2.6.begin、end


有一个问题:基于范围的for循环的方式,对所有容器都适用。那么到现在为止俺们模拟实现的string适用于基于范围的for循环吗?

俺们在test.cpp试试:

#include"string.h"
namespace HD
{
	void test4()
	{
		string S1("hello world");
		for (auto ch : S1)
		{
			cout << ch;
		}
		cout << endl;
	}
}
int main()
{
	HD::test4();
	return 0;
}

发现行不通的,错误列表有一堆报错:

到现在为止模拟实现的string不适用基于范围的for循环的原因也很简单,因为范围for的底层很简单,容器遍历实际就是替换为迭代器。咱们模拟实现的string还没有模拟实现迭代器,也没有模拟实现迭代器相关接口,当然不适用了。

那么如何模拟实现迭代器呢?


 迭代器的实现很复杂,其实迭代器本质就是模拟指针的行为啊,不管迭代器是用什么方法实现的,都希望迭代器能像指针般使用达到访问容器的目的。

那么针对string的模拟实现来说,不用搞那么复杂,我们正好可以返璞归真,利用string类的底层是字符顺序表这个特点,俺们就可以用指针模拟实现迭代器,因为本身就可以用指针来访问顺序表啊!

正向迭代器的模拟实现如下,对应的迭代器属于对应容器的类域,所以俺将其声明放在string类中:

		typedef char* iterator;
		typedef const char* const_iterator;

 俺就不模拟实现反向迭代器了,反向迭代器就不是用指针可以模拟实现的了,复杂的很!


模拟实现了正向迭代器,俺们就可以模拟实现正向迭代器相关的接口了,以下接口的声明和定义都放到string类中:

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		const_iterator end()const
		{
			return _str + _size;
		}

这些接口的模拟实现好像不用解释什么,根据接口的作用很容易就能写出来啊。 

模拟实现了正向迭代器和正向迭代器相关的接口,俺们在test.cpp中测试一下:

#include"string.h"
namespace HD
{
	void test4()
	{
		string S1("hello world");
		for (auto ch : S1)
		{
			cout << ch;
		}
		cout << endl;
		const string S2("God is a girl");
		string::const_iterator cit = S2.begin();
		while (cit != S2.end())
		{
			cout << *cit;
			cit++;
		}
		cout << endl;
	}
}
int main()
{
	HD::test4();
	return 0;
}

运行结果没问题的,基于范围的for循环也能正常使用了:

2.7.capacity

 这个接口这个接口返回字符串的容量。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

		size_t capacity()const
		{
			return _capacity;
		}

 这是一个对this指针指向对象的成员变量只进行读访问的函数,所以我们将其实现成const成员函数,且这样子普通对象和const对象都可调用这个函数(这点以下不再赘述)。

在test.cpp中测试一下:

#include"string.h"
namespace HD
{
	void test5()
	{
		string S1;
		string S2("hello world");
		cout << S1.capacity() << endl << S2.capacity() << endl;
	}
}
int main()
{
	HD::test5();
	return 0;
}

运行结果:

2.8.reserve 

 本接口功能是为string预留空间,不改变有效元素个数。俺模拟实现这个接口不搞那么复杂,当扩容才使其生效。模拟实现本成员函数采用声明和定义分离的写法:

string类(再次说明string的定义放在了string.h中)中声明:

void reserve(size_t n = 0);

 string.cpp中定义:

#include"string.h"
namespace HD
{
	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* p = new char[n + 1];
			strcpy(p, _str);
			delete[] _str;
			_str = p;
			_capacity = n;
		}
	}
}

1.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数 。 

2.别忘记使用命名空间HD封装起来。

3.该函数声明和定义分离,所以定义成员函数时,成员函数名前需要加string::。

4.该接口模拟实现的思想:当需要扩容时,动态开辟n+1(多开一个空间存储'\0')个字节新空间,在将原来的数据拷贝到新空间,释放旧空间,再让_str指向新空间,更新_capacity即可。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test6()
	{
		string S1;
		string S2("hello world");
		cout << S1.capacity() << "   " << S2.capacity() << endl;
		S1.reserve(10); S2.reserve(1);
		cout << S1.capacity() << "   " << S2.capacity() << endl;
	}
}
int main()
{
	HD::test6();
	return 0;
}

运行结果符合预期:

2.9.push_back 

本接口需要实现将字符c附加到字符串末尾,使其长度增加1。俺的模拟实现采取本成员函数声明和定义分离的写法:

string类(再次说明string的定义放在了string.h中)中声明:

void push_back(char c);

string.cpp中定义:

#include"string.h"
namespace HD
{
	void string::push_back(char c)
	{
		if (_capacity == _size)//扩容
		{
			reserve(_capacity == 0 ? 4 : _capacity * 2);
		}
		_str[_size++] = c;
		_str[_size] = '\0';
	}
}

1.别忘记使用命名空间HD封装起来(这点以下不再赘述)。

2.该函数声明和定义分离,所以定义成员函数时,成员函数名前需要加string::(这点以下不再赘述)。

3.插入字符之前先检查字符串容量是否充足,若是不充足,就调用reserve接口扩容(当字符串旧容量为0时,给4个字节空间;当字符串旧容量不为0时,2倍扩容)。将字符c插入字符串末尾并使字符串长度加一,再在字符串末尾插入'\0'就好了。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test7()
	{
		string S1;
		string S2("hello world");
		S1.push_back('!');
		S2.push_back('!');
		cout << S1.capacity() << "    " << S2.capacity() << endl;
		cout << S1.c_str()<<"    " << S2.c_str() << endl;
	}
}
int main()
{
	HD::test7();
	return 0;
}

运行结果:

2.10.append 

 这个接口string中重载了很多个函数,俺就模拟实现string& append (const char* s)这个函数。这个函数功能就是在字符串后追加字符串s。

 俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string& append(const char* s);

 string.cpp中定义:

#include"string.h"
namespace HD
{
	string& string::append(const char* s)
	{
		if (_capacity < strlen(s) + _size)//扩容
		{
			size_t n = strlen(s);
			reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);
		}
		/写法一/
		//for (int i = 0; i < strlen(s); ++i)
		//{
		//	_str[_size++] = s[i];
		//}
		//_str[_size] = '\0';

		/写法二/
		strcpy(_str + _size, s);
		_size += strlen(s);
		return *this;
	}
}

插入字符串s之前先检查字符串容量是否充足,若是不充足,就使用reverse接口扩容(这里的扩容个数不能使用push_back接口的逻辑了,因为有可能4个字节空间或者字符串旧容量的2倍仍然不够字符串s插入的需要)。再将字符串s插入到字符串末尾并且使得字符串长度加strlen(s)并且返回*this就好了。这里插入字符串s到字符串末尾的写法有很多,俺写了两个,但是不管是那种写法都要保证插入之后字符串末尾有'\0'。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test8()
	{
		string S1;
		string S2("hello");
		S1.append("young man");
		S2.append(" Totoro!");
		cout << S1.capacity() << "            " << S2.capacity() << endl;
		cout << S1.c_str()<<"    " << S2.c_str() << endl;
	}
}
int main()
{
	HD::test8();
	return 0;
}

运行结果:

2.11.operator+=

这个接口string中重载了3个函数,功能就是通过在字符串的当前值末尾附加其他字符来扩展字符串。俺就模拟实现其中2个函数:string& operator+= (const char* s)和string& operator+= (char c)。其实模拟实现这2个函数分别直接调用俺模拟实现的append和push_back,然后返回*this就好了。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

		string& operator+= (const char* s);
		string& operator+=(char c);

string.cpp中定义:

#include"string.h"
namespace HD
{
	string& string::operator+= (const char* s)
	{
		append(s);
		return *this;
	}
	string& string::operator+=(char c)
	{
		push_back(c);
		return *this;
	}
}

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test9()
	{
		string S1;
		string S2("hello");
		S1 += '!'; S2 += " Totoro";
		cout << S1.c_str()<<"    " << S2.c_str() << endl;
	}
}
int main()
{
	HD::test9();
	return 0;
}

运行结果:

2.12.insert 

 这个接口string中重载了7个函数,都是在字符串中pos(或p)指示的字符之前插入其他字符。俺就模拟实现其中两个:string& insert(size_t pos, size_t n, char c)和string& insert(size_t pos, const char* s)。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

		string& insert(size_t pos, size_t n, char c);
		string& insert(size_t pos, const char* s);

string.cpp中定义:

#include"string.h"
namespace HD
{
	string& string::insert(size_t pos, size_t n, char c)
	{
		assert(pos <= _size);
		if (_capacity < n + _size)//扩容
		{
			reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);
		}
		size_t end = _size + n;
		while (end > pos + n - 1)
		{
			_str[end] = _str[end - n];
			end--;
		}
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = c;
		}
		_size += n;
		return *this;
	}
	string& string::insert(size_t pos, const char* s)
	{
		assert(pos <= _size);
		size_t n = strlen(s);
		if (_capacity < n + _size)
		{
			reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);
		}
		size_t end = _size + n;
		while (end > pos + n - 1)
		{
			_str[end] = _str[end - n];
			end--;
		}
		strncpy(_str + pos, s, n);
		_size += n;
		return *this;
	}
}

这2个函数的模拟实现逻辑都不难。无非就是处理好字符串容量使之足够插入的需要。然后把从字符位置为pos的字符到字符'\0'在内的所有字符往后挪动指定个数个字节位置(指定个数就是所插入字符的个数或者字符串长度),再插入指定个数的字符或者字符串,最后返回*this就好了。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test10()
	{
		string S1("A  man");
		string S2("hello");
		S1.insert(2, "young");
		S2.insert(S2.size(), 5, 'A');
		cout << S1.c_str()<<"    " << S2.c_str() << endl;
	}
}
int main()
{
	HD::test10();
	return 0;
}

运行结果:

2.13.erase 

 string在这个接口重载了3个函数。作用都是擦除字符串的一部分,缩短其长度。这里俺只模拟实现1个函数:string& erase(size_t pos = 0, size_t len = npos)。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string& erase(size_t pos = 0, size_t len = npos);

 string.cpp中定义:

#include"string.h"
namespace HD
{
	string& string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (_size - pos <= len)
		{
			_str[pos] = '\0';
			_size -= (_size - pos);
		}
		size_t begin = pos + len;
		while (begin < _size + 1)
		{
			_str[begin - len] = _str[begin];
			begin++;
		}
		_size -= len;
		return *this;
	}
}

1.这里用到了缺省参数npos,所以我们必须处理npos:

npos在stringstring类的公共静态成员变量,那么俺模拟实现也在string类中声明为公共静态成员变量:

static const size_t npos;

根据static成员的知识:静态成员变量必须在类外定义,定义时不添加static关键字。俺在string.cpp中定义:

#include"string.h"
namespace HD
{
	const size_t string::npos = -1;
}

 2.缺省参数不能在函数声明和定义中同时出现,当函数声明和定义分离时在声明给缺省参数(以下不再赘述)。

 3.这个接口的模拟实现思路:若是len>=_size-pos,说明要擦除从pos位置开始的全部字符,那么处理好'\0',并使得字符串长度减去擦除字符个数就好了;否则,将需要擦除的字符(串)后面剩余的字符(串)往前挪动len个字节位置,并且字符串长度减去len即可。最后返回*this。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test11()
	{
		string S1("A  man");
		string S2("hello fat Totoro");
		S1.erase();
		S2.erase(5, 4);
		cout << S1.c_str() << endl << S2.c_str() << endl;
	}
}
int main()
{
	HD::test11();
	return 0;
}

运行结果:

2.14.find

 string中这个接口重载了四个函数。功能就是在字符串中搜索其参数指定的序列的第一个匹配项,找到了返回第一个匹配项的字符位置,找不到返回string::npos。俺就模拟实现其中2个函数:size_t find(char c, size_t pos = 0) const和size_t find(const char* s, size_t pos = 0) const。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

	size_t find(char c, size_t pos = 0) const;
    size_t find(const char* s, size_t pos = 0) const;

string.cpp中定义:

#include"string.h"
namespace HD
{
	size_t string::find(char c, size_t pos) const
	{
		for (size_t i = pos; i < _size; i++)
		{
			if (_str[i] == c)
			{
				return i;
			}
		}
		return npos;
	}
	size_t string::find(const char* s, size_t pos) const
	{
		if (const char* p = strstr(_str + pos, s))
		{
			return p - _str;
		}
		return npos;
	}
}

这个模拟实现逻辑很简单,不解释了。其中size_t find(const char* s, size_t pos = 0) const这个函数俺直接套用了函数strstr,对于这个函数有兴趣可以看【C语言】字符函数和字符串函数的介绍。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test12()
	{
		string S1("A  man");
		string S2("hello fat Totoro");
		cout << S1.find('m') << endl;
		cout << S2.find("fat") << endl;
	}
}
int main()
{
	HD::test12();
	return 0;
}

运行结果:

 2.15.substr

 功能:在str中从pos位置开始,截取len个字符,将这些字符构造一个新的字符串对象返回。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string substr(size_t pos = 0, size_t len = npos) const;

string.cpp中定义:

#include"string.h"
namespace HD
{
	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;
	}
}

这个模拟实现思路:主要就是构造一个局部空字符串S1,将需要截取的字符一个个摘取到S1当中,返回S1就好了。

俺们在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test13()
	{
		string S1("hello fat Totoro");
		string S2 = S1.substr(6, 3);
		cout << S2.c_str() << endl;
	}
}
int main()
{
	HD::test13();
	return 0;
}

运行结果:

其实模拟实现到此为止,这个代码不是在任何环境都能运行成功的!

因为substr接口是传值返回,理论上是将substr的返回值S1拷贝到一个临时对象当中,局部对象S1便销毁了,再用临时对象拷贝构造S2的。C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,但是俺还没有模拟实现拷贝构造呢,若未显式定义,编译器会生成默认的拷贝构造函数,默认的拷贝构造函数完成的是浅拷贝噢! 

结论就是若没有显示实现拷贝构造函数,substr的调用理论上是会出问题的,因为浅拷贝导致S2底层_str指向一块已经销毁的空间(substr的局部对象底层的_str)。

那么在俺的环境(VS2022)中为什么能运行成功呢?

因为俺这个VS2022优化了,但是不是任何编译器都会进行优化,所以为了保证substr的正常调用,必须显示实现拷贝构造函数。

2.16.constructor

 模拟实现其中的拷贝构造函数。这个接口的模拟实现如下,俺将其声明和定义都放到string类中:

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

这个模拟实现思路很简单,不解释了。一定记住,类中如果没有涉及资源申请时,拷贝构造函数是否写都可以;一旦涉及到资源申请时,则拷贝构造函数是一定要写的,否则就是浅拷贝,会出问题。 

2.17.operator=

 string中重载了3个函数。俺就模拟实现了1个函数:string& operator=(const string& str)。

注意模拟实现的时候需要完成深拷贝就好,思路跟拷贝构造函数大同小异了。若是实现浅拷贝,问题就很大,俺画图方便理解:

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

string& operator=(const string& str);

string.cpp中定义:

#include"string.h"
namespace HD
{
	string& string::operator=(const string& str)
	{
		if (this==&str)
		{
			return *this;
		}
		delete[]_str;
		_str = new char[str._capacity + 1];
		strcpy(_str, str._str);
		_size = str.size();
		_capacity = str.capacity();
		return *this;
	}
}

这种写法要注意的是,防止自己给自己赋值噢,若是自己给自己赋值没有检查的话,一上来就把_str给delete掉了,那就没得玩了!

 2.18.clear

 这个接口擦除字符串的内容,该字符串将变为空字符串(长度(size接口的返回值)为0个字符),一般不会清理字符串容量。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

	void clear();

string.cpp中定义:

#include"string.h"
namespace HD
{
	void string::clear()
	{
		_str[0] = '\0';
		_size = 0;
	}
}

这个接口的模拟实现在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test14()
	{
		string S1("hello fat Totoro");
		cout << S1.capacity() << endl;
		S1.clear();
		cout << S1.c_str() << "    " << S1.capacity() << endl;
	}
}
int main()
{
	HD::test14();
	return 0;
}

运行结果:

 2.19.swap

 这个接口的功能就是与str交换内容,就是两个字符串底层的内容。

俺的模拟实现采取本成员函数声明和定义分离的写法:

string类中声明:

void swap(string& str);

string.cpp中定义:

#include"string.h"
namespace HD
{
	void string::swap(string& str)
	{
		std::swap(str._str, _str);
		std::swap(str._size, _size);
		std::swap(str._capacity, _capacity);
	}
}

对于这个模拟实现,俺直接调用C++算法库的swap ,调用3次swap分别交换_str、_size、_capacity。

俺们在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test15()
	{
		string S1("hello world");
		string S2("I do love you,yes I do love you");
		cout << "交换前S1:" << S1 << "                        " << S1.size() << "    " << S1.capacity() << endl;
		cout << "交换前S2:" << S2 << "    " << S2.size() << "    " << S2.capacity() << endl;
		cout << endl;
		S1.swap(S2);
		cout << "交换后S1:" << S1 << "    " << S1.size() << "    " << S1.capacity() << endl;
		cout << "交换后S2:" << S2 << "                        " << S2.size() << "    " << S2.capacity() << endl;
	}
}
int main()
{
	HD::test15();
	return 0;
}

运行结果:


 C++算法库的swap是一个函数模板,任何两个同类型的变量都可以直接调用以达到交换的目的,俺们可以看看截图:

但是这个函数模板对于交换两个申请了资源的自定义类型变量就不是很友好,虽然可以完成交换,但是消耗比较大,为什么消耗大?

当2个申请了资源的自定义类型类型变量要交换而调用这个函数模板时,这个模板就生成对应自定义类型的函数,我们看这个生成的函数体内:

……

T c(a):用第1个形参a来拷贝构造1个同类型临时对象c,由于本自定义类型申请了资源,那么调用拷贝构造时就完成1次深拷贝!

……

a=b:将第2个形参b赋值给a,调用operator=,涉及资源申请的自定义类型来说,operator=需要实现深拷贝,完成第2次深拷贝!

……

b=c:将临时对象c赋值给b,调用operator=,完成第3次深拷贝! 

交换两个申请了资源的自定义类型变量需要完成3次深拷贝,消耗当然大了!

这就是为什么string中为什么成员函数接口实现了1个swap了,非成员函数接口还要实现1个swap的意义:

俺们看string中的成员函数接口swap的底层实现跟俺模拟实现的成员函数接口swap是大同小异的,都是把自定义类型拆成若干个内置类型分别交换的,这样子交换就不会涉及深拷贝,1次深拷贝也没有。

……

string中非成员函数接口swap的行为跟成员函数接口swap是一模一样的,也是把自定义类型拆成若干个内置类型分别交换,也不涉及深拷贝。

……

若是string中没有实现非成员函数接口swap,那么当2个string对象想要交换且没有调用成员函数接口swap时,可以调用swap完成交换,会走函数模板,就需要3次深拷贝。

……

若是string中实现了非成员函数接口swap,那么那么当2个string对象想要交换且没有调用成员函数接口swap时,当去调用swap完成交换时,就会走非成员函数接口swap,而不是走函数模板,因为对于非模板函数和同名函数模板,如果其他条件都相同,在调动时会优先调用非模板函数而不会从该模板产生出一个实例,这样子不是避免深拷贝了吗。。。

……

 所以,string中的非成员函数接口swap和成员函数接口swap都是避免两个string类对象交换时陷入深拷贝的。

 3.模拟实现非成员函数接口

3.1.relational operators

string类重载了一堆运算符重载用于比较两个C++字符串的关系或者比较一个C++字符串和一个C字符串的关系。 俺在这里只是模拟实现比较2个C++字符串的关系的函数如下:

俺的模拟实现采取函数声明和定义分离的写法:

在string.h中声明:

namespace HD
{
	bool operator>(const string& lhs, const string& rhs);
	bool operator==(const string& lhs, const string& rhs);
	bool operator<(const string& lhs, const string& rhs);
	bool operator>=(const string& lhs, const string& rhs);
	bool operator<=(const string& lhs, const string& rhs);
	bool operator!=(const string& lhs, const string& rhs);
}

别忘了用命名空间HD封装起来以区分命名空间std内实现的非成员函数(这点以下不再赘述)。

在string.cpp中定义:

#include"string.h"
namespace HD
{
	bool operator>(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) > 0;
	}
	bool operator==(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}
	bool operator<(const string& lhs, const string& rhs)
	{
		return !(lhs >= rhs);
	}
	bool operator>=(const string& lhs, const string& rhs)
	{
		return lhs > rhs && lhs == rhs;
	}
	bool operator<=(const string& lhs, const string& rhs)
	{
		return !(lhs > rhs);
	}
	bool operator!=(const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}
}

 模拟实现直接调用strcmp比较2个C++字符串底层的_str就好了。实现了operator>和operator==,其他的函数实现可以直接复用这2个函数的逻辑。

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test16()
	{
		string S1("hello world");
		string S2("I do love you,yes I do love you");
		cout << (S1 == S1) << endl;
		cout << (S1 != S1) << endl;
		cout << (S1 > S2) << endl;
		cout << (S1 <= S2) << endl;
		cout << (S1 <= S1) << endl;
		cout << (S1 < S2) << endl;
	}
}
int main()
{
	HD::test16();
	return 0;
}

运行结果:


 俺虽然只是模拟实现了比较2个C++字符串的关系的函数,但是却也可以支持1个C++字符串与1个C字符串的比较。举例如下

#include"string.h"
namespace HD
{
	void test17()
	{
		const char* S1 = "hello world";
		string S2("I do love you,yes I do love you");
		cout << (S1 == S1) << endl;
		cout << (S1 != S1) << endl;
		cout << (S1 > S2) << endl;
		cout << (S1 <= S2) << endl;
		cout << (S1 <= S1) << endl;
		cout << (S1 < S2) << endl;
	}
}
int main()
{
	HD::test17();
	return 0;
}

运行结果:

为什么可以这样子呢?

因为构造函数不仅可以构造与初始化对象,对于单个参数构造函数,还具有类型转换的作用。具体介绍可以去看【C++】类和对象3.0 。


俺模拟实现了比较2个C++字符串的关系的函数,却不支持2个C字符串的比较噢,因为俺在【C++】类和对象2.0中介绍过重载操作符必须有一个类类型参数,不能通过运算符重载改变内置类型对象的含义。

例如:

#include"string.h"
namespace HD
{
	void test18()
	{
		const char* S1 = "hello world";
		const char* S2 = "I do love you,yes I do love you";
		cout << (S1 == S1) << endl;
		cout << (S1 != S1) << endl;
		cout << (S1 > S2) << endl;
		cout << (S1 <= S2) << endl;
		cout << (S1 <= S1) << endl;
		cout << (S1 < S2) << endl;
	}
}
int main()
{
	HD::test18();
	return 0;
}

要搞清楚,若是代码写成这样子,不会调用模拟实现的非成员函数接口relational operators的函数 ,也不是2个C字符串的比较,两个C字符串的比较要用函数strcmp。这里本质是比较2个地址呢。

运行结果:

3.2.operator<< 

 流插入运算符重载模拟实现还是很简单的,将底层_str的字符一个个输出就好了。

俺的模拟实现采取函数声明和定义分离的写法:

在string.h中声明:

namespace HD
{
	ostream& operator<<(ostream& os, const string& str);
}

在string.cpp中定义:

#include"string.h"
namespace HD
{
	ostream& operator<<(ostream& os, const string& str)
	{
		for (size_t i = 0; i < str.size(); i++)
		{
			os << str.c_str()[i];
		}
		return os;
	}
}

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test19()
	{
		string S1;
		string S2("hello world");
		const string S3("I love Totoro");
		cout << S1 << endl << S2 << endl << S3 << endl;
	}
}
int main()
{
	HD::test19();
	return 0;
}

运行结果:

3.3.operator>> 

 流提取运算符重载的实现好像也不难,俺们模拟实现采用声明和定义分离的写法:

在string.h中声明:

namespace HD
{
	istream& operator>>(istream& is, string& str);
}

在strng.cpp中定义:

#include"string.h"
namespace HD
{
	istream& operator>>(istream& is, string& str)
	{
		str.clear();
		char ch;
		is >> ch;
		while (ch == ' ' || ch == '\n')//确保第一次拿到的ch不是空格或者换行
		{
			is >> ch;
		}
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			is >> ch;
		}
		return is;
	}
}

这样子写其实是有问题的,俺们可以先在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test20()
	{
		string S1;
		cin >> S1;
		cout << S1;
	}
}
int main()
{
	HD::test20();
	return 0;
}

运行结果如下,会陷入死循环:

俺们看模拟实现函数体内的第2个while循环,导致运行结果陷入死循环的就是第2个while循环。原因就是上一篇博客介绍过的:使用C++的标准输入对象(键盘)cin和C语言的函数scanf获取字符串的时候获取不到空格(' ')或换行('\n'),它们均把空格(' ')和换行('\n')作为默认分隔符号,当它们在缓冲区提取到这两个默认分隔符号之一时,就停止在缓冲区拿数据了。也就是说第2个while循环的条件语句永远是真的,那不就陷入死循环了吗。


解决的办法很多,其中一种办法就是调用istream类的成员函数接口get来获取字符:

get这个接口就是单单取一个字符,没有声明分隔符的概念。

在string.cpp种定义:

#include"string.h"
namespace HD
{
	istream& operator>>(istream& is, string& str)
	{
		str.clear();
		char ch;
		is.get(ch);
		while (ch == ' ' || ch == '\n')//确保第一次拿到的ch不是空格或者换行
		{
			is.get(ch);
		}
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			is.get(ch);
		}
		return is;
	}
}

 在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test20()
	{
		string S1;
		cin >> S1;
		cout << S1;
	}
}
int main()
{
	HD::test20();
	return 0;
}

运行结果如下,没问题:

3.4.getline

 这个接口string中重载了2个函数。俺也模拟实现2个函数,实现方法跟operator>>大同小异,如下:

俺们模拟实现采用声明和定义分离的写法:

在string.h中声明:

namespace HD
{
	istream& getline(istream& is, string& str);
	istream& getline(istream& is, string& str, char delim);
}

在string.cpp中定义:

#include"string.h"
namespace HD
{
	istream& getline(istream& is, string& str)
	{
		str.clear();
		char ch;
		is.get(ch);
		while (ch != '\n')
		{
			str += ch;
			is.get(ch);
		}
		return is;
	}
	istream& getline(istream& is, string& str, char delim)
	{
		str.clear();
		char ch;
		is.get(ch);
		while (ch != delim)
		{
			str += ch;
			is.get(ch);
		}
		return is;
	}
}

在test.cpp中试试:

#include"string.h"
namespace HD
{
	void test21()
	{
		string S1;
		getline(cin, S1);
		cout << S1 << endl<<endl;
		string S2("hello world");
		getline(cin, S2, '@');
		cout << S2 << endl;
	}
}
int main()
{
	HD::test21();
	return 0;
}

运行结果:

4.个别接口的不同写法

 4.1.constructor的不同写法

 俺一共模拟实现了3个构造函数,分别是string()、string(const char* s)、string(const string& str)。


对于string()和string(const char* s)俺们可以用一个带缺省参数的构造函数代替: 

		string(const char* s = "")//以上两构造合二为一
		{
			_size = strlen(s);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, s);
		}

1.string的构造函数接口内并没有重载带缺省参数的构造函数,俺实现这个函数可以用来替代以上两个函数罢了

2.当调用这个函数时,若我们给了s,那么就相当于调用string(const char* s);若我们没有给s,那么就相当于调用string()。

3.这里缺省值给空字符串(""),字符串默认结尾有'\0'的。若是缺省值给了"\0",其实这个字符串包含2个'\0',概念要分清捏。 


对于string(const string& str)俺们还可以这样写

		string(const string& str)//拷贝构造函数
		{
			string tmp(str.c_str());
			swap(tmp);
		}

 俺们希望用C++字符串str来构造一个新对象。俺们可以调用构造函数,用C字符串str.c_tr()来构造一个新对象tmp,这个新对象tmp不就是我们想要的吗,调用swap将tmp和*this的内容交换一下不就好了。

俺们知道string类底层是字符顺序表,所以俺定义3个private成员变量_str、_capacity、_size来实现,并且给这3个成员变量缺省值,这3个缺省值的意义就可以在这里体现:

当调用swap将tmp和*this的内容交换后,tmp得到的就是*this的旧内容且出了函数作用域就会销毁tmp。若是没有给缺省值,当调用构造函数走初始化列表的时,_str就得到一个随机值,也就是指向某块随机空间,当调用swap后并销毁tmp时,这块随机空间不就随着tmp的销毁而被delete掉了,这是不允许的;若是给缺省值,_str就得到nullptr,当tmp被销毁时是没问题的。

4.2.operator=的不同写法

 operator=还可以这样子写,跟构造函数的不同写法的思想是一样的:

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

operator=也可以如以下代码,只不过string中并没有重载以下operator=,但是下面这个operator=也能实现2个string类对象赋值的:

俺的模拟实现采取本成员函数声明和定义分离的写法:

string中声明:

string& operator=(const string str);

 在string.cpp中定义:

#include"string.h"
namespace HD
{
	string& string::operator=(string str)
    {
	swap(str);
	return *this;
    }
}

思路:调用swap时要先传参,由于C++规定⾃定义类型对象进⾏拷⻉⾏为必须调⽤拷⻉构造,那么理论上,operator=的形参str拷贝构造一个临时对象,这个临时对象再拷贝构造swap的参数str,哪怕编译器优化掉了临时对象,swap的形参str也是通过ooperator=形参str拷贝构造的一个新对象,将这个新对象str与*this的内容交换一下不就搞定了。

 这个operator=的模拟实现为什么参数部分设计成传值而不是传引用呢?

若是传引用,那么调用swap时,就不存在拷贝行为,就不会调用拷贝构造函数,就没有新对象的产生。在将operator=的形参str与*this交换内容,不就出大问题了吗。

以上2种写法若是自己给自己赋值也不会出现问题,仔细想想就能明白!

4.3.operator>>的不同写法

	istream& operator>>(istream& is, string& str)
	{
		str.clear();
		char ch;
		const size_t n = 256;
		char p[n];
		is.get(ch);
		size_t i = 0;
		while (ch == ' ' || ch == '\n')
		{
			is.get(ch);
		}
		while (ch != ' ' && ch != '\n')
		{
			p[i++] = ch;
			is.get(ch);
			if (i == n-1)
			{
				p[i] = '\0';
				str += p;
				i = 0;
			}
		}
		if (i > 0)
		{
			p[i] = '\0';
			str += p;
		}
		return is;
	}

这种写法与原本那种写法最大的不同就是,原本的写法是每取一个字符就插入str,容易造成频繁扩容;这种写法是每得到一串字符串再插入str,扩容次数就少了,一种优化吧!

5. string模拟实现完整代码

 关于这个string模拟实现完整代码如下,看完整代码也方便理解,有兴趣可以瞅瞅。本工程包括3个文件,分别是:string.h、string.cpp、test.cpp。

5.1.string.h

 存放string类的定义和非成员函数的声明。

#pragma once
#include<assert.h>
#include<iostream>
#include<string>
#include<string.h>
using namespace std;
namespace HD
{
	class string
	{
		char* _str = nullptr;
		size_t _capacity = 0;
		size_t _size = 0;
	public:
		typedef char* iterator;
		typedef const char* const_iterator;
		static const size_t npos;

		//string()
		//	:_str(new char[1]{'\0'})
		//	,_capacity(0)
		//	,_size(0)
		//{
		//}

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

		string(const char* s = "")//以上两构造合二为一
		{
			_size = strlen(s);
			_capacity = _size;
			_str = new char[_capacity + 1];
			strcpy(_str, s);
		}

		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 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];
		}

		iterator begin()
		{
			return _str;
		}

		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin()const
		{
			return _str;
		}

		const_iterator end()const
		{
			return _str + _size;
		}

		size_t capacity()const
		{
			return _capacity;
		}

		string(const string& str)//拷贝构造函数
		{
			/写法一/
			/*_str = new char[str._capacity + 1];
			strcpy(_str, str._str);
			_size = str._size;
			_capacity = str._capacity;*/

			/写法二/
			string tmp(str.c_str());
			swap(tmp);
		}
		void reserve(size_t n = 0);
		void push_back(char c);
		string& append(const char* s);
		string& operator+= (const char* s);
		string& operator+=(char c);
		string& insert(size_t pos, size_t n, char c);
		string& insert(size_t pos, const char* s);
		string& erase(size_t pos = 0, size_t len = npos);
		size_t find(char c, size_t pos = 0) const;
		size_t find(const char* s, size_t pos = 0) const;
		string substr(size_t pos = 0, size_t len = npos) const;
		string& operator=(const string& str);
		//string& operator=(const string str);
		void clear();
		void swap(string& str);
	};
	bool operator>(const string& lhs, const string& rhs);
	bool operator==(const string& lhs, const string& rhs);
	bool operator<(const string& lhs, const string& rhs);
	bool operator>=(const string& lhs, const string& rhs);
	bool operator<=(const string& lhs, const string& rhs);
	bool operator!=(const string& lhs, const string& rhs);
	ostream& operator<<(ostream& os, const string& str);
	istream& operator>>(istream& is, string& str);
	istream& getline(istream& is, string& str);
	istream& getline(istream& is, string& str, char delim);
}

5.2.string.cpp 

 存放string类部分成员函数的定义、静态成员变量的定义和非成员函数的定义。

#include"string.h"
namespace HD
{
	const size_t string::npos = -1;

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

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

	string& string::append(const char* s)
	{
		if (_capacity < strlen(s) + _size)//扩容
		{
			size_t n = strlen(s);
			reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);
		}
		/写法一/
		//for (int i = 0; i < strlen(s); ++i)
		//{
		//	_str[_size++] = s[i];
		//}
		//_str[_size] = '\0';

		/写法二/
		strcpy(_str + _size, s);
		_size += strlen(s);
		return *this;
	}

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

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

	string& string::insert(size_t pos, size_t n, char c)
	{
		assert(pos <= _size);
		if (_capacity < n + _size)//扩容
		{
			reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);
		}
		size_t end = _size + n;
		while (end > pos+n-1)
		{
			_str[end] = _str[end - n];
			end--;
		}
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = c;
		}
		_size+=n;
		return *this;
	}

	string& string::insert(size_t pos, const char* s)
	{
		assert(pos <= _size);
		size_t n = strlen(s);
		if (_capacity < n + _size)
		{
			reserve(n + _size > 2 * _capacity ? n + _size : 2 * _capacity);
		}
		size_t end = _size + n;
		while (end > pos + n - 1)
		{
			_str[end] = _str[end - n];
			end--;
		}
		strncpy(_str + pos, s, n);
		_size += n;
		return *this;
	}

	string& string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (_size - pos <= len)
		{
			_str[pos] = '\0';
			_size -= (_size - pos);
		}
		size_t begin = pos + len;
		while (begin < _size + 1)
		{
			_str[begin - len] = _str[begin];
			begin++;
		}
		_size -= len;
		return *this;
	}

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

	size_t string::find(const char* s, size_t pos) const
	{
		if (const char* p = strstr(_str + pos, s))
		{
			return p - _str;
		}
		return npos;
	}

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

	string& string::operator=(const string& str)
	{
		/写法一/
		/*if (this==&str)//防止自己给自己赋值
		{
			return *this;
		}
		delete[]_str;
		_str = new char[str._capacity + 1];
		strcpy(_str, str._str);
		_size = str.size();
		_capacity = str.capacity();
		return *this;*/

		/写法二/
		string tmp(str);
		swap(tmp);
		return *this;
	}

	/*string& string::operator=(string str)
	{
		swap(str);
		return *this;
	}*/

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

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

	bool operator>(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) > 0;
	}

	bool operator==(const string& lhs, const string& rhs)
	{
		return strcmp(lhs.c_str(), rhs.c_str()) == 0;
	}

	bool operator<(const string& lhs, const string& rhs)
	{
		return !(lhs >= rhs);
	}

	bool operator>=(const string& lhs, const string& rhs)
	{
		return lhs > rhs && lhs == rhs;
	}

	bool operator<=(const string& lhs, const string& rhs)
	{
		return !(lhs > rhs);
	}

	bool operator!=(const string& lhs, const string& rhs)
	{
		return !(lhs == rhs);
	}

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

	istream& operator>>(istream& is, string& str)
	{
		/写法一/
		/*str.clear();
		char ch;
		is.get(ch);
		while (ch == ' ' || ch == '\n')
		{
			is.get(ch);
		}
		while (ch != ' ' && ch != '\n')
		{
			str += ch;
			is.get(ch);
		}
		return is;*/

		/写法二/
		str.clear();
		char ch;
		const size_t n = 256;
		char p[n];
		is.get(ch);
		size_t i = 0;
		while (ch == ' ' || ch == '\n')
		{
			is.get(ch);
		}
		while (ch != ' ' && ch != '\n')
		{
			p[i++] = ch;
			is.get(ch);
			if (i == n-1)
			{
				p[i] = '\0';
				str += p;
				i = 0;
			}
		}
		if (i > 0)
		{
			p[i] = '\0';
			str += p;
		}
		return is;
	}

	istream& getline(istream& is, string& str)
	{
		str.clear();
		char ch;
		is.get(ch);
		while (ch != '\n')
		{
			str += ch;
			is.get(ch);
		}
		return is;
	}

	istream& getline(istream& is, string& str, char delim)
	{
		str.clear();
		char ch;
		is.get(ch);
		while (ch != delim)
		{
			str += ch;
			is.get(ch);
		}
		return is;
	}
}

5.3.test.cpp 

 存放测试代码,来测试咱们模拟实现的string的功能。

#include"string.h"
namespace HD
{
	void test1()
	{
		string S1;
		string S2("hello world");
		cout << S1.c_str() << endl << S2.c_str() << endl;
	}
 
	void test2()
	{
		string S1;
		string S2("hello world");
		cout << S1.size() << endl << S2.size() << endl;
	}
	void test3()
	{
		string S1("nbo");
		for (int i = 0; i < S1.size(); i++)
		{
			S1[i] -= 1;
			cout << S1[i];
		}
		cout << endl;
		const string S2("hello world");
		for (int i = 0; i < S2.size(); i++)
		{
			cout << S2[i];
		}
	}
	void test4()
	{
		string S1("hello world");
		for (auto ch : S1)
		{
			cout << ch;
		}
		cout << endl;
		const string S2("God is a girl");
		string::const_iterator cit = S2.begin();
		while (cit != S2.end())
		{
			cout << *cit;
			cit++;
		}
		cout << endl;
	}
	void test5()
	{
		string S1;
		string S2("hello world");
		cout << S1.capacity() << endl << S2.capacity() << endl;
	}
	void test6()
	{
		string S1;
		string S2("hello world");
		cout << S1.capacity() << "   " << S2.capacity() << endl;
		S1.reserve(10); S2.reserve(1);
		cout << S1.capacity() << "   " << S2.capacity() << endl;
	}
	void test7()
	{
		string S1;
		string S2("hello world");
		S1.push_back('!');
		S2.push_back('!');
		cout << S1.capacity() << "    " << S2.capacity() << endl;
		cout << S1.c_str() << "    " << S2.c_str() << endl;
	}
	void test8()
	{
		string S1;
		string S2("hello");
		S1.append("young man");
		S2.append(" Totoro!");
		cout << S1.capacity() << "            " << S2.capacity() << endl;
		cout << S1.c_str() << "    " << S2.c_str() << endl;
	}
	void test9()
	{
		string S1;
		string S2("hello");
		S1 += '!'; S2 += " Totoro";
		cout << S1.c_str() << "    " << S2.c_str() << endl;
	}
	void test10()
	{
		string S1("A  man");
		string S2("hello");
		S1.insert(2, "young");
		S2.insert(S2.size(), 5, 'A');
		cout << S1.c_str() << "    " << S2.c_str() << endl;
	}
	void test11()
	{
		string S1("A  man");
		string S2("hello fat Totoro");
		S1.erase();
		S2.erase(5, 4);
		cout << S1.c_str() << endl << S2.c_str() << endl;
	}
	void test12()
	{
		string S1("A  man");
		string S2("hello fat Totoro");
		cout << S1.find('m') << endl;
		cout << S2.find("fat") << endl;
	}
	void test13()
	{
		string S1("hello fat Totoro");
		string S2 = S1.substr(6, 3);
		cout << S2.c_str() << endl;
	}
	void test14()
	{
		string S1("hello fat Totoro");
		cout << S1.capacity() << endl;
		S1.clear();
		cout << S1.c_str() << "    " << S1.capacity() << endl;
	}
	void test15()
	{
		string S1("hello world");
		string S2("I do love you,yes I do love you");
		cout << "交换前S1:" << S1 << "                        " << S1.size() << "    " << S1.capacity() << endl;
		cout << "交换前S2:" << S2 << "    " << S2.size() << "    " << S2.capacity() << endl;
		cout << endl;
		S1.swap(S2);
		cout << "交换后S1:" << S1 << "    " << S1.size() << "    " << S1.capacity() << endl;
		cout << "交换后S2:" << S2 << "                        " << S2.size() << "    " << S2.capacity() << endl;
	}
	void test16()
	{
		string S1("hello world");
		string S2("I do love you,yes I do love you");
		cout << (S1 == S1) << endl;
		cout << (S1 != S1) << endl;
		cout << (S1 > S2) << endl;
		cout << (S1 <= S2) << endl;
		cout << (S1 <= S1) << endl;
		cout << (S1 < S2) << endl;
	}
	void test17()
	{
		const char* S1 = "hello world";
		string S2("I do love you,yes I do love you");
		cout << (S1 == S1) << endl;
		cout << (S1 != S1) << endl;
		cout << (S1 > S2) << endl;
		cout << (S1 <= S2) << endl;
		cout << (S1 <= S1) << endl;
		cout << (S1 < S2) << endl;
	}
	void test18()
	{
		const char* S1 = "hello world";
		const char* S2 = "I do love you,yes I do love you";
		cout << (S1 == S1) << endl;
		cout << (S1 != S1) << endl;
		cout << (S1 > S2) << endl;
		cout << (S1 <= S2) << endl;
		cout << (S1 <= S1) << endl;
		cout << (S1 < S2) << endl;
	}
	void test19()
	{
		string S1;
		string S2("hello world");
		const string S3("I love Totoro");
		cout << S1 << endl << S2 << endl << S3 << endl;
	}
	void test20()
	{
		string S1;
		cin >> S1;
		cout << S1;
	}
	void test21()
	{
		string S1;
		getline(cin, S1);
		cout << S1 << endl << endl;
		string S2("hello world");
		getline(cin, S2, '@');
		cout << S2 << endl;
	}
}
int main()
{
	HD::test21();
	return 0;
}


感谢阅读! 


http://www.kler.cn/a/396763.html

相关文章:

  • 计算机网络 (3)计算机网络的性能
  • HARCT 2025 分论坛4:智能系统传感、传感器开发和数据融合中的智能数据分析
  • LeetCode Hot 100 题解[java版本,冲大厂]
  • 【eNSP】路由基础与路由来源——静态路由实验
  • H.265流媒体播放器EasyPlayer.js H.264/H.265播放器chrome无法访问更私有的地址是什么原因
  • 力扣 LeetCode 239. 滑动窗口最大值(Day5:栈与队列)
  • SQL练习(2)
  • Linux篇(用户管理命令)
  • Python 桌面应用开发:使用 Tkinter 创建 GUI 应用程序
  • QT定时器
  • iOS swift开发--- 加载PDF文件并显示内容
  • 聊聊Flink:Flink的运行时架构
  • 【含开题报告+文档+PPT+源码】基于Spring Boot智能综合交通出行管理平台的设计与实现
  • 除了 TON, 哪些公链在争夺 Telegram 用户?数据表现如何?
  • 【IEEE出版 | 中国石油大学(华东)主办】第六届信息与计算机前沿术国际学术会议(ICFTIC 2024,12月13-15日)
  • 两部手机的IP地址:是否会相同?全面探讨
  • K8S 查看pod节点的磁盘和内存使用情况
  • 【364】基于springboot的高校科研信息管理系统
  • Python 如何通过 cron 或 schedule 实现爬虫的自动定时运行
  • Flink_DataStreamAPI_源算子Source
  • Java进阶 - 并发编程
  • 帽子矩阵--记录
  • SQL 中 BETWEEN AND 用于字符串的理解
  • Element UI如何实现按需导入--Vue3篇
  • Kotlin jetpack MVP
  • sql server 查看io资源使用