【ONE·C++||string类(二)】-创新互联

总言

  主要讲述string的模拟实现。

成都创新互联是一家专业提供海丰企业网站建设,专注与网站建设、成都做网站、H5建站、小程序制作等业务。10年已为海丰众多企业、政府机构等服务。创新互联专业网站制作公司优惠进行中。文章目录
  • 总言
  • 1、对构造函数、析构、string::c_str
  • 2、对 string::size、string::capacity、string::operator[ ]
  • 3、迭代器
  • 4、拷贝构造和赋值
    • 4.1、拷贝构造和赋值构造的写法1.0
    • 4.2、拷贝构造和赋值构造的写法2.0
  • 5、增删查改
    • 5.1、string::reserve、string::push_back、string::operator+=(char c)
    • 5.2、string::append、string::operator+=(const char* s)
    • 5.3、string:: insert
    • 5.4、string::erase
    • 5.5、流插入和流提取
    • 5.6、其它接口:比较、substr、resize、find
  • 6、其它相关知识

  
  
  1)、总言
  对string类,由于库函数中本身有一个,为了避免冲突,这里我们的处理方式有两种:其一是更改类名称,其二是为我们自己模拟实现的类添加命名空间。
  本文章中选择了第二种处理方法:

namespace mystring//命名空间
{class string//我们模拟实现的类
	{public:

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

  
  

1、对构造函数、析构、string::c_str

  1)、模拟实现一:

namespace mystring
{class string
	{public:
		//string类的构造:
		string(const char* str)//传入参数为字符串时
			:_str(new char[strlen(str) + 1])//此处+1是多开了一个空间
			,_size(strlen(str))
			,_capacity(strlen(str))
		{	strcpy(_str, str);
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

  注意事项:
  ①、此处初始化列表中不能直接_str(str),由于后续涉及string类的增删查改,我们最好采用动态开辟的方式。
  ②、我们之前学习string类的构造函数时有观察到,string类会为开辟出的对象保留一个位置以便放置’\0’,因此此处我们在动态开辟空间时也要将其考虑进去。
  
  2)、模拟实现二:无参构造
  思考上述模拟实现一,有没有发现什么缺陷?
  当我们使用是一个无参构造呢?因此此处对于它的实现,我们要么提供一个全缺省的构造,要么在此基础上提供一个无参构造。
  先来看一看无参构造的情况:

namespace mystring
{class string
	{public:
		//string类的构造:
		string(const char* str)//传入参数为字符串时
			:_str(new char[strlen(str) + 1])//此处+1是多开了一个空间
			,_size(strlen(str))
			,_capacity(strlen(str))
		{	strcpy(_str, str);
		}
		//上述显示写法,我们要么提供一个全缺省的构造,要么提供一个无参构造
		string()//假如是无参构造
			:_str(new char[1])//此处虽然只动态开辟一个空间,但为了方便后续释放,我们一并带上方括号
			, _size(0)
			,_capacity(0)
		{	_str[0] = '\0';//我们初始化定义空对象,其内部也有一个'\0'
		}

		//string类析构:
		~string()
		{	delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}


	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};
}

  注意事项:
  ①_str(new char[1]),此处我们使用new开辟空间时,尽管只申请一个空间,但我们仍然使用了方括号的形式,这是为了后续析构函数方便一次性释放(对string(const char* str)有效、对string()也有效)。
   ②关于无参构造函数,初始化列表处_str(new char[1]),是否能让_str(nullptr)?

string()//假如是无参构造
			:_str(nullptr)
			, _size(0)
			, _capacity(0)
		{}

   回答:不能。此处原因不在于后续delete释放空指针。(因为delete、free会检查空指针,如果所释放的对象是空指针,那么它们不会做任何处理)。
   此处真正错误的原因在于做一些调用返回时,会出错。比如下述情况:
   我们模拟实现c_str,根据之前所学,其会指向类中字符存储的指针。

//string函数实现:c_str
	const char* c_str()const
	{return _str;
	}
		
	void test_string1()
	{string s1;
		cout<< s1.c_str()<< endl;
	}

  
  
  3)、模拟实现三:全缺省构造
   在之前学习模拟实现日期类中,我们也表明,写一个带参+无参,不如写一个全缺省方便。那么如果是含有全缺省的string类,该如何实现呢?怎么给值?
  
   示例一:此处能直接给nullptr吗?为什么?

string(const char* str = nullptr)//缺省参数为空指针
			:_str(new char[strlen(str)+1])
			,_size(strlen(str))
			,_capacity(strlen(str))
		{	strcpy(_str, str);
		}

   回答:不能。因为strlen直接解引用空指针会崩溃。
  
   示例二:

全缺省下的构造:
		string(const char* str = "\0")//一种写法
			:_str(new char[strlen(str)+1])
			,_size(strlen(str))
			,_capacity(strlen(str))
		{	strcpy(_str, str);
		}

   但这种写法实际上不严谨,str里实际存储为\0\0。
  
   示例三:

全缺省下的构造:
		string(const char* str = "")//另一种写法"" ,常量字符串,以\0结尾。
			:_str(new char[strlen(str)+1])
			,_size(strlen(str))
			,_capacity(strlen(str))
		{	strcpy(_str, str);
		}

  
  
  4)、模拟实现四:优化说明
   针对上述的构造函数,我们发现初始化列表中我们使用了好多次strlen(),要知道strlen使用的效率相对较低,那么有没有什么优化的方式呢?
   以下举例了一种写法,问题:这样写能不能做到对strlen的优化?

class string
	{public:
		string(const char* str = "")
			: _size(strlen(str))//先用一次strlen
			, _capacity(_size)//对_capacity我们使用_size初始化
			, _str(new char[_capacity+1])//此处同理
		{	strcpy(_str, str);
		}

	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

   回答:这种写法是错误的。在类和对象·初始化列表中我们有提到,初始化列表的顺序与声明顺序一致,与初始化列表中定义的顺序无关。
   若按照上述方法进行初始化,_capacity为随机值,那么_str在动态开辟空间时会崩溃。
在这里插入图片描述
  此处需要引申一个话题:抛异常(详细介绍将在后续学习到,此处我们需要先学着使用它)。

int main()
{try {mystring::test_string1();
	}
	catch (const exception& e)
	{cout<< e.what()<< endl;
	}

	return 0;
}

  针对上述问题,一个提出的解决方案是将类中成员变量的顺序调换。但这样子将后续的维护问题与成员变量的顺序关联上了,相对来说不是优解。
  
  
  5)、模拟实现五:优化说明
   针对4)中存在的问题,我们的一个解决方案是,混合使用或者干脆不用初始化列表,直接在函数体内初始化。(string类的三个成员都是内置类型,没有必须在初始化列表完成初始化的要求)

string(const char* str)
		{	_size = strlen(str);
			_capacity = _size;
			_str = new char[_capacity + 1];

			strcpy(_str, str);
		}

  
  
  

2、对 string::size、string::capacity、string::operator[ ]

  1)、模拟实现一:

//string函数实现:size()
		size_t size()const
		{	return _size;
		}
		
		//string函数实现:capacity()
		size_t capacity()const
		{	return _capacity;
		}


		//string函数实现:operator[]
		char& operator[](size_t pos)
		{	assert(pos< _size);
			return _str[pos];
		}

   注意事项:
   ①operator[]我们之前学习时就讲过,下标访问符号其最终要达到读写目的,因此,我们在实现operator[]时不能加const,且必须有&。

  演示结果如下:
在这里插入图片描述

  库中的函数示意图:
在这里插入图片描述
在这里插入图片描述
   根据库函数我们这里最好为operator[]提供两种模式:可读可写&只读

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、迭代器

  1)、模拟实现一:
   需要注意的是,迭代器可能是指针,但不一定完全是指针。

//string函数实现:迭代器
		typedef char* iterator;

		iterator begin()
		{	return _str;
		}

		iterator end()
		{	return _str + _size;
		}

   演示结果如下:我们支持了迭代器,就支持了范围for。
在这里插入图片描述

void test_string2()
	{//验证迭代器的实现:
		string s1("hello string!");
		cout<< s1.c_str()<< endl;

		string::iterator it=s1.begin();
		while (it< s1.end())
		{	cout<< *it<< " ";
			++it;
		}
		cout<< endl;
		for (auto ch : s1)//持了迭代器,就支持了范围for
		{	cout<< ch<< " ";
		}
		cout<< endl;
	}

  
  
  
  

4、拷贝构造和赋值

  总言:如果一个类中涉及到资源的管理,其拷贝构造函数、赋值运算符重载以及析构函数必须要显式给出。一般情 况都是按照深拷贝方式提供。
  

4.1、拷贝构造和赋值构造的写法1.0

  1)、拷贝构造之浅拷贝说明:
在这里插入图片描述

  
  
  2)、如何进行深拷贝的说明
   深拷贝即需要给对象分配自己单独的资源空间。
在这里插入图片描述
  演示代码如下:

//深拷贝:
		string(const string& s)
			:_str(new char[s._capacity + 1])
			, _capacity(s._capacity)
			, _size(s._size)
		{	strcpy(_str, s._str);
		}

  演示结果如下:
在这里插入图片描述

  
  3)、如何进行赋值的说明
   接上述演示,假设现在我们有一个s3,要将其赋值给s1,该如何实现?

void test_string3()
	{//验证拷贝构造
		string s1("hello world.");
		string s2(s1);

		cout<< s1.c_str()<< endl;
		cout<< s2.c_str()<< endl;


		//验证赋值
		string s3("1111111111 1111111111");
		s1 = s3;//如何实现?
	}

   直接用默认生成的赋值运算符重载可以吗?
   回答:不行。
   原因:默认生成的赋值运算符重载属于浅拷贝,会让s1指向与s3相同的空间。这样一来存在两大问题:①s1更改指向,原先动态开辟的空间没有被释放,则存在内存泄露的现象。②出了作用域调用析构函数时,同一块空间将释放两次。
  
   那么,如何解决这个问题呢?我们可以仿照拷贝构造一样,单独开辟一份资源空间。

赋值运算符重载·传统
		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;
		
		}

   写法分析:
   ①s1 = s3;虽然我们可以在s1空间足够充裕时不释放而是直接交换内部数据,但我们会面临s1空间不足,或s1空间远大于s3的情况,因此不如统一路径先释放s1后开辟新空间。
   ②如果直接使用_delete[ ] _str,new失败了会将原数据释放,所以先使用一个tmp临时变量完成拷贝,确定new成功再说。(当然此处不这样也行,new失败了本来就要抛异常)。但一种相对比较提倡的写法如下:先开辟空间,再交换数据:

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

   ③new char[s._capacity + 1]多开辟一个空间是留给\0,因为_size和_capacity不计预留的\0。
   ④if (this != &s)if语句:此处考虑到的是自己赋值给自己的情况。如果不加if判断语句,那相当于先把s1释放了,再用s1赋值给s1。
   ⑤return *this;this指针指向对象的地址,此处需要返回的是对象本身,故需要对this指针解引用。
  
  

4.2、拷贝构造和赋值构造的写法2.0

  1)、一个错误的写法说明

赋值运算符重载·自写·错误
		string& operator=(const string& s)
		{	if (this != &s)
			{		string tmps(s);
				swap(tmps._str, _str);
				_size = s._size;
				_capacity = s._capacity;
				delete[] tmps._str;//对随机空间进行了析构
			}
			
			return *this;
		}

   错误说明:
   ①string tmps(s);此处相当于在拷贝构造函数中使用了拷贝构造。正确写法应该是去调用构造函数:string tmps(s._str);
   ②delete[] tmps._str;此处存在两个问题,其一是tmp是临时对象,出了函数作用域会自动调用其析构函数。那这里相当于析构了两次。
   ③接②,就算我们把上面的delete[] tmps._str;删除,仍旧会存在报错。因为我们在做的是拷贝构造,假如我先定义string s1;再赋值s1=s3;由于s1未初始化,与tmp交换后,tmp得到该处的随机值。那么同样的,临时变量tmp出作用域调用对应的析构函数,会导致delete因释放随机空间而崩溃。(解决方法是为s1设置初始化列表)。

  
  
  2)、拷贝构造和赋值运算符现代写法说明

string(const string& s)
			:_str(nullptr)
			,_capacity(0)
			,_size(0)
		{	string tmps(s._str);
			swap(tmps._str, _str);
			swap(tmps._size, _size);
			swap(tmps._capacity, _capacity);

   解释说明:1、此处为s2初始化,是为了后续与tmp做成员交换。在没有初始化时,s2为随机值,与tmp交换后,tmp得到该随机值,但由于tmp是临时变量,出了作用域要销毁,会调用对应的析构函数,会导致delete在释放随机空间时崩溃。(delete释放nullptr不会崩溃)

	:_str(nullptr)
			,_capacity(0)
			,_size(0)

  
   继续优化:
   1、string类中也有一个成员函数swap。
   2、此处使用了域作用限定符,前面为空白表示全局域。若不加该限定符,编译器会先在局部域中找,结果局部域中有swap,编译器会认为自己调用自己,而且此处局部域中参数不匹配。

string函数实现:swap
		void swap(string& tmps)
		{	::swap(tmps._str, _str);
			::swap(tmps._size, _size);
			::swap(tmps._capacity, _capacity);
		}
		拷贝构造:
		string(const string& s)
			:_str(nullptr)
			,_capacity(0)
			,_size(0)
		{	string tmps(s._str);
			swap(tmps);//解释:this->swap(tmps);
		}

  3、swap(tmps);拷贝构造中,此处先调用string类里的swap,string类中swap调用全局的swap。
   同理可得赋值运算符重载:

string& operator=(const string& s)
		{	if (this!=&s)
			{		//string tmps(s._str);//调用构造
				string tmps(s);//调用拷贝构造
				swap(tmps);
			}
			return *this;
		}

   1、此处两种写法都可以,前者使用的是构造函数,后者使用的是拷贝构造(使用后者的前提是我们自己实现了拷贝构造)
   2、此处做得很绝的一点是,出了作用域tmp销毁调用析构,就顺带把s1原先空间给释放掉了。不需要我们手动写delete.
   3、关于是否可以写swap(*this,tmps),即使用库里的全局swap?回答:不可以,库里的全局swap实际是个模板,假如我们在赋值运算符重载中使用它,库里的swap又在其内部使用了赋值运算符重载,那么此处会造成死循环。
在这里插入图片描述

   此外:调用string类里swap,根据我们的实现,此处只是简单的内置类型的交换,若使用全局库中的swap,则连同地址也会一并被换掉。

//string函数实现:swap
		void swap(string& tmps)
		{	::swap(tmps._str, _str);
			::swap(tmps._size, _size);
			::swap(tmps._capacity, _capacity);
		}
void test_string4()
	{string s1("hello world");
		string s2("XXXXXXX");

		s1.swap(s2);//调用string类里swap:直接交换内部成员变量
		swap(s1, s2);//调用全局库中的swap:会去掉拷贝构造。
	}

在这里插入图片描述
  
   折回上述问题,我们再来简化一下此处的赋值运算符重载:
   直接使用传值传参,省掉了tmp。相对更为精髓。

string& operator=(string s)
		{	swap(s);
			return *this;
		}

  
  
  

5、增删查改 5.1、string::reserve、string::push_back、string::operator+=(char c)

  1)、库函数中声明回顾:
在这里插入图片描述
  
  

  2)、模拟实现

void reserve(size_t n)
		{	if (n>_capacity)
			{		//开空间:
				char* tmp = new char[n + 1];//n个有效空间,一个\0空间
				//拷贝值:
				strcpy(tmp, _str);
				//释放旧空间:
				delete[]_str;
				//重新给定指向:
				_str = tmp;
				_capacity = n;//注意别忘记,另reserve里只是对_capacity做了修改,不会变动_size。
			}

		}
void push_back(char ch)
		{	//检查扩容:
			if (_size == _capacity)
			{		reserve(_capacity == 0 ? 4 : _capacity * 2);
				//此处三目运算符是为了预防构造时参数为空的情况。
			}
			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';//注意此处别忘了放置\0

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

  
  演示结果如下:
在这里插入图片描述

  
  

5.2、string::append、string::operator+=(const char* s)

  1)、库函数中声明回顾:
在这里插入图片描述
  
  2)、模拟实现append 1.0

void append(const char* str)
		{	//append仍旧需要扩容检查,只是其是在尾部追加字符串而非字符
			size_t len = strlen(str);
			if (_size + len >_capacity)
			{		//此处我们不能直接仿照push_back中的写法进行二倍扩容等。
				//所以我们干脆使用reserve重定义存储空间:
				reserve(_size + len);
			}
			//尾插字符串:
			strcpy(_str+_size, str);
			//修改属性:
			_size += len;
			//此处_capacity我们在使用reserve时做了处理。
		}

   此处strcpy(_str + _size, str);若换为strcat(_str, str)可以吗?
   回答是可以,但是strcat追加需要找\0,相率相对较低。

  演示结果如下:
在这里插入图片描述

   既然实现了上述append功能,我们自然而然可以利用它们实现以下接口:

string& operator+=(const char* str)
		{	append(str);
			return *this;
		}
void append(const string& s)
		{	append(s._str);
		}
void append(size_t n, char ch)
		{	reserve(_size + n);//提前开辟充裕空间
			for (int i = 0; i< n; i++)
			{		push_back(ch);
			}
		}

     

5.3、string:: insert

  1)、库函数中声明回顾:
   我们主要模拟实现下列这两个。
在这里插入图片描述
  
  2)、模拟实现insert 1.0

string& insert(size_t pos, char ch)
		{	//首先要断言一下插入的位置:pos需要合法
			//此处涉及的一个问题是边界问题:_size位置处能否插入?事实上只要我们处理好最后的收尾工作即可。
			assert(pos<= _size);

			//扩容检查:
			if (_size == _capacity)
			{		reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			//数据插入:向后挪动数据,从后往前遍历
			size_t end = _size;
			while (end >= pos)
			{		_str[end + 1] = _str[end];
				--end;
			}
			_str[pos] = ch;

			//收尾处理:
			++_size;
			_str[_size] = '\0';
			return *this;
		}


	private:
		char* _str;
		size_t _size;
		size_t _capacity;
	};

  演示结果如下:
在这里插入图片描述

  
  2)、模拟实现insert 2.0
   针对1.0版本,是否存在什么问题?

	//数据插入:向后挪动数据,从后往前遍历
			size_t end = _size;
			while (end >= pos)
			{		_str[end + 1] = _str[end];
				--end;
			}
			_str[pos] = ch;

   看看上述代码,如果pos位置在0处,那么头插会存在问题。若end是int类型数据,那么end=-1时退出循环。这不是正确的吗?但问题是上述代码中end值是size_t无符号整型。-1对应的无符号数是一个很大的值,因此才说此处会陷入死循环中。

   所以提出了如下解决方案:如图。

在这里插入图片描述

   接上图,又有提出那么我们把pos的类型修改了吧。
   这种做法行得通,但是事实上我们观察库函数里的pos,其提供的都是size_t类型。而且这里还会有一个问题:pos本意指代下标,使用int类型的pos,万一传入的是负数呢?
   PS:这里也解释了为什么assert(pos<= _size);不检查<0的情况,因为pos为无符号整型。

在这里插入图片描述
   综上所述,这里我们建议将end初始值置成_size+1,如下述演示:

/insert插入:版本2.0
		string& insert(size_t pos, char ch)
		{	//首先要断言一下插入的位置:pos需要合法
			//此处涉及的一个问题是边界问题:_size位置处能否插入?事实上只要我们处理好最后的收尾工作即可。
			assert(pos<= _size);

			//扩容检查:
			if (_size == _capacity)
			{		reserve(_capacity == 0 ? 4 : _capacity * 2);
			}

			//数据插入:
			size_t end = _size+1;
			while (end >pos) //理解:end-1 >= pos → end>=pos+1 → end >pos
			{		_str[end] = _str[end-1];//步骤一:向后挪动数据,从后往前遍历
				--end;
			}
			_str[pos] = ch;//步骤二:插入数据ch

			//收尾处理:
			++_size;
			_str[_size] = '\0';
			return *this;
		}

  演示结果如下:
在这里插入图片描述

  
  3)、模拟实现insert 3.0
   假如是插入字符串呢?代码如下:

string& insert(size_t pos, const char* str)
		{	assert(pos<= _size);
			size_t len = strlen(str);
			if (len + _size >_capacity)
			{		reserve(len + _size);
			}

			size_t end = _size + len;
			while (end >= pos + len)//等价于 end >pos + len -1;
			{		_str[end] = _str[end - len];
				--end;
			}
			strncpy(_str + pos, str,len);

			_size += len;
			_str[_size ] = '\0';
			return *this;
		}

  演示结果如下:
在这里插入图片描述

   同理可修改其它:

写法2.0
		void push_back(char ch)
		{	insert(_size, ch);
		}
写法2.0
		void append(const char* str)
		{	insert(_size, str);
		}

  
  

5.4、string::erase

  1)、库函数中声明回顾:
在这里插入图片描述
  
  2)、模拟实现:
  演示代码如下:

void erase(size_t pos, size_t len = npos)
		{	assert(pos<= _size);
			//不需要挪动数据的情况:
			if (len == npos || len + pos >_size)
			{		_str[pos] = '\0';
				_size = pos;
			}
			else
			{		//需要挪动数据的情况:
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}

		}

  演示结果如下:
在这里插入图片描述
  
  

5.5、流插入和流提取

   不是所有的流插入、流提取都需要设置为友元。我们在日期类中使用了友元是因为涉及访问私有成员。而string类中我们完全可以使用下标来实现这一功能。

  1)、模拟实现1.0:
  演示代码如下:

ostream& operator<<(ostream& cou, const string& s)
	{for (size_t i = 0; i< s.size(); ++i)
		{	cou<< s[i];
		}
		return cou;
	}
istream& operator>>(istream& ci, string& s)
	{char ch;
		ch = ci.get();
		while (ch != ' ' && ch != '\n')
		{	s += ch;
			ch = ci.get();
		}
		return ci;
	}

  
  

  2)、模拟实现2.0:
   上述流提取中,若输入字符串很长,不断+=会频繁扩容,效率很低,因此我们可以优化一下 :

istream& operator>>(istream& ci, string& s)
	{char ch;
		ch = ci.get();
		//用于优化流插入:
		const int N = 32;//常量数组
		char buff[N];

		size_t i = 0;

		while (ch != ' ' && ch != '\n')
		{	buff[i++] = ch;//先将插入字符放入数组中
			if (i == N - 1)//若字符放满,则一次性挪动到string类中
			{		buff[N] = '\0';//留一个位置给'\0'
				s += buff;
				i = 0;//注意需要将i置回0以便下一轮使用
			}
			ch = ci.get();
		}
		//这是为最后一轮作处理:若ch读到\n或'',则跳出while循环,那么残余部分没有被放入string类中
		buff[i] = '\0';
		s += buff;
		return ci;
	}

  

   同理,对于流插入,仍旧有一些细节:
在这里插入图片描述

  如上图所示:假如类原先就有字符,那么cin后在标准库中会被覆盖,针对这一问题,我们可在类中实现一个clear函数:

void clear()
		{	_str[0] = '\0';
			_size = 0;
		}
istream& operator>>(istream& ci, string& s)
	{s.clear();

		char ch;
		ch = ci.get();
		//用于优化流插入:
		const int N = 32;//常量数组
		char buff[N];

		size_t i = 0;

		while (ch != ' ' && ch != '\n')
		{	buff[i++] = ch;//先将插入字符放入数组中
			if (i == N - 1)//若字符放满,则一次性挪动到string类中
			{		buff[N] = '\0';//留一个位置给'\0'
				s += buff;
				i = 0;//注意需要将i置回0以便下一轮使用
			}
			ch = ci.get();
		}
		//这是为最后一轮作处理:若ch读到\n或'',则跳出while循环,那么残余部分没有被放入string类中
		buff[i] = '\0';
		s += buff;
		return ci;
	}

  
  
  

5.6、其它接口:比较、substr、resize、find
string substr(size_t pos, size_t len = npos)const
		{	assert(pos< _size);
			size_t reallen = npos;//是为了记录真实取得的字符长度
			if (len == npos || len + pos >_size)
			{		reallen = _size - pos;
			}
			string tmp;
			for (size_t i = 0; i< reallen; i++)
			{		tmp += _str;
			}

		}

  

size_t find(char ch, size_t pos = 0)const
		{	assert(pos< _size);

			for (size_t i = pos; i< _size; i++)
			{		if (_str[i] == ch)
					return i;//若找到对应字符,则返回下标
			}
			return npos;//若找不到,则返回npos
		}

		size_t find(const char* sub, size_t pos = 0)const
		{	const char*p =strstr(_str + pos, sub);//此处为暴力匹配,其它匹配方法:kmp/bm
			if (p != nullptr)
			{		return npos;
			}
			else {		return p - _str;
			}
		}

  

void resize(size_t n, char ch = '\0')
		{	if (n >_size)
			{		//开辟空间,插入数据
				reserve(n);
				for (size_t i = _size; i< n; ++i)
				{_str[i] = ch;
				}
				_str[n] = '\0';
				_size += n;
			}
			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;
		}

		bool operator >=(const string& s)const
		{	return (*this == s) || (*this >s);
		}


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

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

		bool operator !=(const string& s)const
		{	return !(*this == s);
		}

  
  
  
  

6、其它相关知识

1、vs下string类内存大小设置
2、其它string类实现方案
3、引用计数和写时拷贝。

  
  
  
  

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


网页题目:【ONE·C++||string类(二)】-创新互联
网页URL:http://azwzsj.com/article/ccidci.html