目录

Cstring类讲解含常用接口使用及原理模拟实现

【C++】string类讲解:含常用接口使用及原理模拟实现


string介绍

为什么要学习string类?

1、C语言中,字符串是以’\0’结尾的一些字符的集合,为了操作方便,C标准库中提供了一些str系列的库函数,但是这些库函数与字符串是分离开的,不太符合OOP的思想,而且底层空间需要用户自己管理,稍不留神可能还会越界访问。

2、在OJ中,有关字符串的题目基本以string类的形式出现,而且在常规工作中,为了简单、方便、快捷,基本都使用string类,很少有人去使用C库中的字符串操作函数。

学习方法:

一个容器的接口有很多,这种情况下,我们是不可能全部都记下来(我们程序员也不是干这活的),我们要在熟悉掌握常用接口的前提下,要学会查找文档

string类文档介绍:

当我们对某个接口的使用不太清楚的时候,就可以去查找文档

auto和范围for

在学习接口之前,我们来了解两个C++11中出现的十分好用的语法:auto和范围for

auto

概念及使用注意:

1、在早期C/C++中auto的含义是:使用auto修饰的变量,是具有自动存储器的局部变量,后来这个不重要了。C++11中,标准委员会变废为宝赋予了auto全新的含义即:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。

2、用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&,

3、当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

4、auto不能作为函数的参数,可以做返回值,但是建议谨慎使用

5、auto不能直接用来声明数组

void func2(auto a)
{}

auto func1()
{

	return 1;
}

auto func2()
{
    ...
	return func1();
}

auto func3()
{
	...
    //auto不能作为函数的参数,可以做返回值
    //谨慎使用
	return func2();
}

int main()
{
	int x = 10;

	auto y = &x;

	// 右边必须是指针
	//auto* z = x;
	auto* z = &x;

    //auto声明引用类型时则必须加&
	auto& m = x;
	auto n = x;

    //auto不能直接用来声明数组
	//auto array[] = { 4, 5, 6 };

	auto ret = func3();

	return 0;
}

auto的主要使用场景是在后面的迭代器的使用,我们在使用stl时,会用到很多各种各样的迭代器,它们的名字有的会很长,要想记住或写出来十分麻烦,这是便可以用auto来代替迭代器名

#include<iostream>
#include <string>
#include <map>
using namespace std;
int main()
{
	std::map<std::string, std::string> dict = { { "apple", "苹果" },{ "orange","橙子" }, {"pear","梨"}};
	// auto的用武之地
	//std::map<std::string, std::string>::iterator it = dict.begin();
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << it->first << ":" << it->second << endl;
		++it;
	}
	return 0;
}

范围for

1、对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。for循环后的括号由冒号“ :”分为两部分:第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围,自动迭代,自动取数据,自动判断结束。

2、范围for可以作用到数组和容器对象上进行遍历

3、范围for的底层很简单,容器遍历实际就是替换为迭代器,这个从汇编层也可以看到。

int main()
{
	int array[] = { 1, 2, 3, 4, 5 };
	// C++98的遍历
	for (int i = 0; i < sizeof(array) / sizeof(int); ++i)
	{
		array[i] *= 2;
	}

	for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
	{
		cout << array[i] << " ";
	}
	cout << endl;

	// 范围for
	for (auto& e : array)
	{
		e *= 2;
	}
	cout << endl;

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

	for (const auto& e : array)
	{
		cout << e << " ";
	}
	cout << endl;

	return 0;
}

string类常见接口说明

构造函数

这几个是常见的构造函数

//无参构造
string();
//拷贝构造
string (const string& str);

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

还有3个构造函数可以查看文档:

下面是构造函数的使用

//string类的构造函数
void test_string1()
{
	//1、string();
	string s1;
	//2、string (const char* s);
	string s2("1111");
	//3、string (const string& str);
	string s3(s2);
	//4、string (const string& str, size_t pos, size_t len = npos);
	string s4(s2, 2, 2);//从str的pos处,取len长度字符串
	//5、string (size_t n, char c);
	string s5(5, '0');//取n个字符
	//6、string (const char* s, size_t n);
	string s6("222333", 3);//取字符串的前三个字符
	//7、template <class InputIterator>
	//		string(InputIterator first, InputIterator last);
	string s7(s5.begin() + 1, s5.end() - 2);//取迭代器区间初始化

	cout << s2 << endl;
	cout << s3 << endl;
	cout << s4 << endl;
	cout << s5 << endl;
	cout << s6 << endl;
	cout << s7 << endl;
}

析构函数

文档:

这个函数没什么讲解的,用于释放资源和销毁空间的

size()和capacity()

size()接口是用于 返回字符串的有效长度 ,也就是不包括最后的结束符’\0'

capacity()接口是 返回空字符串最大容量 ,它和size()一样也是不包括’\0'

还有一个 length() 接口也是用于返回字符串长度的,和size()类似,但这个接口相对于size()在各个容器中各有不同,有的容器有,有的没有,所以为了使用方便,就最好使用size()

void test_string3()
{
	string s1("111222");
	//每个容器都有size(),但不一定都有length()
	//所以建议使用size()
	cout << s1.size() << endl;//字符串长度
	cout << s1.capacity() << endl;//字符串最大容量
	//size()和capacity计算长度的时候都不会考虑'\0'
}

reserve()和resize()

reserve()接口是用于改变字符串的空间容量:

void reserve (size_t n = 0);

1、当capacity>n和size<n时,不会扩容,也不会缩容,也不会改变字符串的内容

2、当capacity<n时,会发生扩容

void test()
{
    string s1("111222"); 
    //reserve():会在不改变原有字符串内容的前提下,进行扩容或缩容
	s1.reserve(5);//当小于原有size,不会扩容
	cout << s1.size() << endl;//字符串长度
	cout << s1.capacity() << endl;//字符串最大容量

	s1.reserve(15);//小于原有capacity,不会扩容
	cout << s1.size() << endl;//字符串长度
	cout << s1.capacity() << endl;//字符串最大容量

	s1.reserve(20);//大于原有capacity就会扩容
	cout << s1.size() << endl;//字符串长度
	cout << s1.capacity() << endl;//字符串最大容量
}

resize()是用于调整字符串大小:

void resize (size_t n);
void resize (size_t n, char c);

1、当n<size时,就会发生缩容并删除字符串中的内容

2、当n>size时,不会发生扩容,但多出来的部分会插入数据,在不指明插入字符时,默认是’\0’填充

3、当n>capacity时,会发生扩容并插入数据

void test_string3()
{
	string s2("111222");
    cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	s2.resize(5);//小于原有size,会发生缩容,删除数据
	cout << s2.size() << endl;
	cout << s2 << endl;
	cout << s2.capacity() << endl;

	s2.resize(15);//大于原有size,不会会发生扩容,插入数据
	//多出的空间,在不写明插入字符前,都是以'\0'填充
	cout << s2.size() << endl;
	cout << s2 << endl;
	cout << s2.capacity() << endl;

 	s2.resize(20,'3');//大于原有capacity,会发生扩容,插入数据
    //多出的空间,在写明插入字符前,都是以'\0'填充
	cout << s2.size() << endl;
	cout << s2 << endl;
	cout << s2.capacity() << endl;
}

empty()和clear()

empty()用于检测字符串是否为空,为空则返回true,反之返回false:

bool empty() const;

clear()用于清空字符串内容,它只会将字符串的size清空,不改变原有空间capacity:

void clear();

operator[]和at()

operator[]用于 返回

pos

位置的字符:

char& operator[] (size_t pos);
const char& operator[] (size_t pos) const;

at()功能和operator[]差不多:

char& at (size_t pos);
const char& at (size_t pos) const;

但是它们在判断越界时,所选择不一样

operator[]在遇到越界访问时,是直接断言assert()报错,退出程序

at[]在遇到越界访问时,可以进行抛异常,但仍然可以进行后面的程序

begin()、end()和rbegin()、rend()

begin()和end(),一个是取第一个字符,一个是取最后一个字符,它们都是用到迭代器,来取到字符: 、

iterator begin();
const_iterator begin() const;

iterator end();
const_iterator end() const;

这里用到两个迭代器: 普通迭代器(iterator)const迭代器(const_iterator)

iterator:表示指向资源可以修改

const_iterator:表示指向资源不可修改

rbegin()是从后往前取第一个字符,也就是字符串的最后一个字符:

reverse_iterator rbegin();
const_reverse_iterator rbegin() const;

rend()是从后往前取最后一个字符,也就 是字符串的第一个字符:

reverse_iterator rend();
const_reverse_iterator rend() const;

这又有两个迭代器: 反向迭代器(reverse_iterator)反向const迭代器(const_reverse_iterator)

push_back()

void push_back (char c);

这个接口的作用是 在字符串尾插一个字符

void test_string4()
{
	string s1;
	//void push_back(char c);
	//尾插
	s1.push_back('a');
	s1.push_back('b');
	s1.push_back('c');
	cout << s1 << endl;
}

append()

string& append (const string& str);

这个接口是在字符串后追加一个字符串:

void test_string()
{
    string s2("111222");
    s2.append("333");
	cout << s2 << endl; 
}

insert()

这个接口可以实现在字符串任意位置插入字符及字符串:

它也有很多重载函数,下面两个是用到最多的

string& insert (size_t pos, const string& str);
string& insert (size_t pos, const string& str, size_t subpos, size_t sublen);
void test_string()
{
    string s2("111222");
	//string& insert (size_t pos, const string& str);
	s2.insert(2, s1);
	cout << s2 << endl;

	//string& insert(size_t pos, const string & str, size_t subpos, size_t sublen);
	s2.insert(3, s1, 1, 2);
	cout << s2 << endl;
}

operator+=

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

string& operator+= (char c);

这个接口可以进行在字符串后追加字符和字符串:

c_str()

这个接口是用于返回字符串,在后面我们模拟实现string类时,在还没有实现operator«时,就可以用这个接口来是实现string类对象的输出:

const char* c_str() const;

find()和npos

这个接口用于实现返回字符串中某个字符或子字符串的下标位置:

size_t find (const string& str, size_t pos = 0) const;
	
size_t find (const char* s, size_t pos = 0) const;
	
size_t find (const char* s, size_t pos, size_t n) const;

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

npos是一个静态成员常量值,是对于 类型的元素具有最大可能的值,

在类里声明,类外定义,一般给缺省值-1

static const size_t npos = -1;

operator»和operator«

这两个函数是不属于类成员函数,它们一定是全局函数但不一定是友元函数

istream& operator>> (istream& is, string& str);
ostream& operator<< (ostream& os, const string& str);

getline()

这个函数也不属于类成员函数,它可以获取一行字符串,而且连’ ‘和’\n’都可以取到,也可以指定字符来作为结束符

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

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

string类的模拟实现

成员变量

我们在声明的时候就给缺省值

private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		const static size_t npos;

构造函数

构造函数的实现并不难,我们可以实现多个构造函数

无参构造函数

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

有参构造函数

在这里实现两个有参构造,来应对多种对象初始情况,要注意的是给_str初始化new空间时,要多开一个字节,用来存放’\0’

对于存放多个字符的情况下,最后一个位置_size处,要记得给’\0'

对于给一个字符串的情况,我们用C语言库里的strcpy来进行_str的赋值,以此来提高效率

string::string(size_t n, char c)
		:_str(new char[n + 1])
		, _size(n)
		, _capacity(n)
	{
		for (size_t i = 0; i < n; ++i)
		{
			_str[i] = c;
		}
		_str[_size] = '\0';
	}

string::string(const char* str)
	:_size(strlen(str))
{
	_str = new char[_size + 1];
	_capacity = _size;
	strcpy(_str, str);
	//不用strcat的原因是因为strcat每次调用需要先找到目标字符串的末尾,再追加内容
}

拷贝构造函数传统写法及现代写法

传统写法:

这里进行的是深拷贝,给s1重开了一块和s2一样大的空间,后用strcpy将s2中的资源拷贝到s1新开辟的空间中

//s1(s2)
string::string(const string& str)
{
	_str = new char[str._capacity + 1];
	strcpy(_str, str._str);
	_size = str._size;
	_capacity = str._capacity;
}
现代写法:

现代写法和传统写法比起来并没有提高效率,只是简化了代码,提高了可读性

复用了std中的swap()

void string::swap(string& str)
{
	//这里调用std库里面的swap函数
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
	std::swap(_str, str._str);
}
string::string(const string& str)
{
	string tmp(str._str);//调用构造函数
	swap(tmp);//调用自己写的swap函数
}

析构函数

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

push_back()和reserve()

在进行数据的插入的时候,就要进行扩容,也就可以写reserve()函数

然后在使用reserve()时为了避免浪费空间,可以用一个三目操作符来控制扩容的大小

void string::reserve(size_t n)
{
	if (n > _capacity)
	{
		char* tmp = new char[n + 1];//这里要加一是因为_size不包含'\0'
		strcpy(tmp, _str);
		delete[] _str;
		_str = tmp;
		_capacity = n;
	}
}

void string::push_back(char ch)
{
	if (_size + 1 > _capacity)
	{
		reserve(_capacity == 0 ? 4 : 2 * _capacity);
	}

	_str[_size] = ch;
	++_size;
	_str[_size] = '\0';

}

insert()

在这里我们实现两个insert()函数,一个用于插入字符,一个用于插入字符串

在插入时我们要注意从后往前挪动数据

1、先将要插入的字符大小空间给挪出来

2、再从后往前挪动数据

3、最后将要插入字符放入到指定位置

这种方法在插入字符串也是一样的道理

void string::insert(size_t pos, size_t n, char c)
	{
		assert(pos <= _size);
		assert(n > 0);
		if (_size + n > _capacity)
		{
			size_t newcapacity = 2 * _capacity;
			if (2 * _capacity < _size + n)
			{
				newcapacity = _size + n;
			}
			reserve(newcapacity);
		}

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

在插入字符串时,我们有两种写法,一种和上面插入字符一样,一种是复用上面的插入字符的函数

1、先向要插入字符串插入指定大小空间个’\0’来占位置

2、然后用要插入字符串进行覆盖就可以了

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

		//1、
		/*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];
		}*/

		//2、
		size_t n = len;
		//复用上面写的insert
		//先将要插入的空间空出来,插入字符
		insert(pos, n, '0');
		//这里就直接用要插入字符进行覆盖就可以了
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = str[i];
		}

		_size += len;

	}

append()

尾部插入一个字符串和insert的原理差不多,唯一的差别就是在扩好容以后,是直接在_str+_size处进行strcpy插入字符串

void string::append(const char* s)
{
	size_t len = strlen(s);
	if (_size + len > _capacity)
	{
		size_t newcapacity = _capacity * 2;
		if (_size + len > newcapacity)
		{
				newcapacity = _size + len;
		}
			reserve(newcapacity);
	}
	strcpy(_str + _size, s);
	_size += len;
}

operator+=

+=这个重载运算符就可以直接复用上面写好的push_back()和append()

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

operator=的传统写法和现代写法

赋值这个重载运算符的原理和拷贝构造函数差不多

就是要先将原有空间资源释放,在进行开空间赋值

而且赋值还要考虑自己赋值给自己

传统写法:

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

现代写法:

在现代写法中,直接使用写好的swap()进行两个string类的交换,实现原理就是会调用拷贝构造,而且还需要对成员变量给缺省值(这就是为什么有初始化列表还要给缺省值)

void string::swap(string& str)
{
	//这里调用std库里面的swap函数
	std::swap(_size, str._size);
	std::swap(_capacity, str._capacity);
	std::swap(_str, str._str);
	}
string& string::operator=(string str)
{
	swap(str);
	return *this;
}

erase()

指定删除长度要给缺省值 = npos

输出过程中要考虑到两个情况:

1、要删除位置后字符串长度小于指定删除长度

2、不小于指定删除长度

删除过程也是从后往前进行字符覆盖

https://i-blog.csdnimg.cn/direct/c27c93fa5a42491aae30f8ed77a41f3a.png

string& string::erase(size_t pos, size_t len)
{
	assert(pos < _size);
	if (len >= _size)//将pos位置后所有字符删除
	{
		_str[pos] = '\0';
		_size = pos;
	}
	else
	{
		size_t end = pos + len;
		while (end <= _size)
		{
			_str[end - len] = _str[end];
			end++;
		}
		_size -= len;
	}
	return *this;

}

find()

进行字符的查找时很简单,就是一个个去对比

而进行字符串的查找的时候就需要用到C语言库中strstr函数,如果找到了就返回要寻找字符串第一个字符的下标,没有就返回空

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)
{
	//进行字符一一对比
	const char* s = strstr(_str + pos, str);
	if (s == nullptr)//为空则返回npos
	{
		return npos;
	}
	else
	{
		return s - _str;//不为空则返回找到该字符串的位置
	}	
}

operator[]

这个运算符重载的实现就很简单了

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

substr()

这个函数要考虑到的情况就是当要取得字符串长度大于要取位置处后的字符串长度

string string::substr(size_t pos, size_t len)
{
	assert(pos < _size);

	//当要取得字符串长度大于pos后字符串剩余长度,则就是把后面的字符串都取完
	if (len > (_size - len))
	{
		len = _size - len;
	}
	string str;
	str.reserve(len);
	for (size_t i = 0; i < len; ++i)
	{
		str += _str[pos + i];
	}
	return str;
}

relational operators

比较大小的运算符重载,只需要实现==、<(>)和<=(>=)就可以了,其他的就复用即可

    bool string::operator==(const string& s) const
	{
		return strstr(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}
	bool string::operator<(const string& s) const
	{
		return strstr(_str, s._str) < 0;
	}
	bool string::operator<=(const string& s) const
	{
		return (*this < s) || (*this == s);
	}
	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}
	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}

operator«和operator»

    ostream& operator<<(ostream& out, const string& s)
	{
		for (auto x : s)
		{
			out << x;
		}
		return out;
	}

在进行流提取的时候,我们建立一个临时字符数组要作为 “中间人”,用它来先接收字符,当字符数超过它的容量时,才将字符给要插入的字符串

当遇到’ ‘或’\n’时结束循环

要注意的是最后一个位置拿来插入’\0’

使用Buff这样一个临时数组来存储字符,起到一个控制作用

当输入字符少的时候,可以避免空间开得太大,导致空间浪费

当输入字符多的时候,可以避免频繁扩容

    istream& operator>>(istream& in, string& s)
	{
		s.clear();
		const size_t N = 1024;
		char Buff[N];//用于临时存储字符
		int i = 0;//做标记位
		char ch = in.get();
		while (ch != ' ' && ch != '\n')//当遇到' '或'\0'时结束
		{
			Buff[i] = ch;
			i++;
			if (i == N-1)//当等于1023时,说明Buff数组满了,最后一个位置用于存放'\0'
			{
				Buff[i] = '\0';
				s += Buff;//将Buff中字符转移到s中
				i = 0;//当还有字符输入时,就又从0开始,将之前Buff中的字符覆盖掉
			}
			ch = in.get();
		}
		if (i != 0)
		{
			Buff[i] = '\0';//将最后一位做'\0'字符串结束符
			s += Buff;
		}
		return in;
	}

getline()

gteline的实现原理和operator»差不多,就是gteline还可以实现指定字符作为结束符

所以在循环结束的条件就是当遇到结束符是结束循环

    istream& getline(istream& in, string& s, char delim)
	{
		s.clear();
		const size_t N = 1024;
		char Buff[N];//用于临时存储字符
		int i = 0;//做标记位
		char ch = in.get();
		while (ch != delim)//可以指定字符作为结束字符
		{
			Buff[i] = ch;
			i++;
			if (i == N - 1)//当等于1023时,说明Buff数组满了,最后一个位置用于存放'\0'
			{
				Buff[i] = '\0';
				s += Buff;//将Buff中字符转移到s中
				i = 0;//当还有字符输入时,就又从0开始,将之前Buff中的字符覆盖掉
			}
			ch = in.get();
		}
		if (i != 0)
		{
			Buff[i] = '\0';//将最后一位做'\0'字符串结束符
			s += Buff;
		}
		return in;
	}

完整代码

//String.h
#pragma once
#include<iostream>
#include<string.h>
#include<assert.h>
using namespace std;

namespace yyc
{
	class string
	{
	public:
		//构造函数
		/*string()
			:_size(0)
		{
			_str = new char[_size]{ '\0' };
			_capacity = _size;
		}*/
		string(size_t n, char c);
		//默认构造
		string(const char* str = "");
		
		
		string(const string& str);

		//string& operator=(const string& str);
		//现代写法
		void swap(string& str);
		string& operator=(string str);

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

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

		//扩容
		void reserve(size_t n);

		//提取字符串
		const char* c_str() const
		{
			return _str;
		}

		//插入
		void push_back(char ch);
		void insert(size_t pos, size_t n, char c);
		void insert(size_t pos, const char* str);
		void append(const char* s);
		
		//opertor+=
		string& operator+= (char c);
		string& operator+= (const char* s);

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

		size_t size() const
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		
		size_t find(char ch, size_t pos = 0);
		size_t find(const char* str, size_t pos = 0);

		char& operator[] (size_t pos);
		const char& operator[] (size_t pos) const;

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

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

		bool operator==(const string& s) const;
		bool operator!=(const string& s) const;
		bool operator<(const string& s) const;
		bool operator<=(const string& s) const;
		bool operator>(const string& s) const;
		bool operator>=(const string& s) const;




	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;

		const static size_t npos;
	};

	// cout<<s1
	ostream& operator<<(ostream& out, const string& s);
	// cin>>s1
	istream& operator>>(istream& in, string& s);

	istream& getline(istream& is, string& s, char delim = '\n');

}
//String.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include"String.h"

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


	string::string(size_t n, char c)
		:_str(new char[n + 1])
		, _size(n)
		, _capacity(n)
	{
		for (size_t i = 0; i < n; ++i)
		{
			_str[i] = c;
		}
		_str[_size] = '\0';
	}
	string::string(const char* str)
		:_size(strlen(str))
	{
		_str = new char[_size + 1];
		_capacity = _size;
		strcpy(_str, str);
		//不用strcat的原因是因为strcat每次调用需要先找到目标字符串的末尾,再追加内容
	}
	

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


	//拷贝构造的现代写法
	string::string(const string& str)
	{
		string tmp(str._str);//调用构造函数
		swap(tmp);//调用自己写的swap函数
	}

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

	void string::swap(string& str)
	{
		//这里调用std库里面的swap函数
		std::swap(_size, str._size);
		std::swap(_capacity, str._capacity);
		std::swap(_str, str._str);

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



	void string::reserve(size_t n)
	{
		if (n > _capacity)
		{
			char* tmp = new char[n + 1];//这里要加一是因为_size不包含'\0'
			strcpy(tmp, _str);
			delete[] _str;
			_str = tmp;
			_capacity = n;
		}
	}

	void string::push_back(char ch)
	{
		if (_size + 1 > _capacity)
		{
			reserve(_capacity == 0 ? 4 : 2 * _capacity);
		}

		_str[_size] = ch;
		++_size;
		_str[_size] = '\0';

	}

	void string::insert(size_t pos, size_t n, char c)
	{
		assert(pos <= _size);
		assert(n > 0);
		if (_size + n > _capacity)
		{
			size_t newcapacity = 2 * _capacity;
			if (2 * _capacity < _size + n)
			{
				newcapacity = _size + n;
			}
			reserve(newcapacity);
		}
		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;
	}

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

		//1、
		/*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];
		}*/

		//2、
		size_t n = len;
		//复用上面写的insert
		//先将要插入的空间空出来,插入字符
		insert(pos, n, '0');
		//这里就直接用要插入字符进行覆盖就可以了
		for (size_t i = 0; i < n; i++)
		{
			_str[pos + i] = str[i];
		}

		_size += len;

	}


	void string::append(const char* s)
	{
		size_t len = strlen(s);
		if (_size + len > _capacity)
		{
			size_t newcapacity = _capacity * 2;
			if (_size + len > newcapacity)
			{
				newcapacity = _size + len;
			}

			reserve(newcapacity);
		}

		strcpy(_str + _size, s);
		_size += len;
	}


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

	string& string::erase(size_t pos, size_t len)
	{
		assert(pos < _size);
		if (len >= _size)//将pos位置后所有字符删除
		{
			_str[pos] = '\0';
			_size = pos;
		}
		else
		{
			size_t end = pos + len;
			while (end <= _size)
			{
				_str[end - len] = _str[end];
				end++;
			}
			_size -= len;
		}

		return *this;
	}

	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)
	{
		//进行字符一一对比
		const char* s = strstr(_str + pos, str);
		if (s == nullptr)//为空则返回npos
		{
			return npos;
		}
		else
		{
			return s - _str;//不为空则返回找到该字符串的位置
		}

	}

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

	string string::substr(size_t pos, size_t len)
	{
		assert(pos < _size);

		//当要取得字符串长度大于pos后字符串剩余长度,则就是把后面的字符串都取完
		if (len > (_size - len))
		{
			len = _size - len;
		}
		string str;
		str.reserve(len);
		for (size_t i = 0; i < len; ++i)
		{
			str += _str[pos + i];
		}
		return str;

	}

	bool string::operator==(const string& s) const
	{
		return strstr(_str, s._str) == 0;
	}
	bool string::operator!=(const string& s) const
	{
		return !(*this == s);
	}
	bool string::operator<(const string& s) const
	{
		return strstr(_str, s._str) < 0;
	}
	bool string::operator<=(const string& s) const
	{
		return (*this < s) || (*this == s);
	}
	bool string::operator>(const string& s) const
	{
		return !(*this <= s);
	}
	bool string::operator>=(const string& s) const
	{
		return !(*this < s);
	}

	// cout<<s1
	ostream& operator<<(ostream& out, const string& s)
	{
		for (auto x : s)
		{
			out << x;
		}
		return out;
	}
	// cin>>s1
	//使用Buff这样一个临时数组来存储字符,起到一个控制作用
	//当输入字符少的时候,可以避免空间开得太大,导致空间浪费
	//当输入字符多的时候,可以避免频繁扩容
	istream& operator>>(istream& in, string& s)
	{
		s.clear();
		const size_t N = 1024;
		char Buff[N];//用于临时存储字符
		int i = 0;//做标记位
		char ch = in.get();
		while (ch != ' ' && ch != '\n')//当遇到' '或'\0'时结束
		{
			Buff[i] = ch;
			i++;
			if (i == N-1)//当等于1023时,说明Buff数组满了,最后一个位置用于存放'\0'
			{
				Buff[i] = '\0';
				s += Buff;//将Buff中字符转移到s中
				i = 0;//当还有字符输入时,就又从0开始,将之前Buff中的字符覆盖掉
			}
			ch = in.get();
		}
		if (i != 0)
		{
			Buff[i] = '\0';//将最后一位做'\0'字符串结束符
			s += Buff;
		}
		return in;
	}

	istream& getline(istream& in, string& s, char delim)
	{
		s.clear();
		const size_t N = 1024;
		char Buff[N];//用于临时存储字符
		int i = 0;//做标记位
		char ch = in.get();
		while (ch != delim)//可以指定字符作为结束字符
		{
			Buff[i] = ch;
			i++;
			if (i == N - 1)//当等于1023时,说明Buff数组满了,最后一个位置用于存放'\0'
			{
				Buff[i] = '\0';
				s += Buff;//将Buff中字符转移到s中
				i = 0;//当还有字符输入时,就又从0开始,将之前Buff中的字符覆盖掉
			}
			ch = in.get();
		}
		if (i != 0)
		{
			Buff[i] = '\0';//将最后一位做'\0'字符串结束符
			s += Buff;
		}
		return in;
	}
}