【C++】string类详解

时间:2021-02-15 01:19:16


???? string类

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

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

????1.标准库中的string类

这里给大家推荐一个查库函数的网站,我们学习C++一般以这个作为参照:
string类详解

  1. 字符串是表示字符序列的类
  2. 标准的字符串类提供了对此类对象的支持,其接口类似于标准字符容器的接口,但添加了专门用于操作单字节字符字符串的设计特性。
  3. string类是使用char(即作为它的字符类型,使用它的默认char_traits和分配器类型(关于模板的更多信息,请参阅basic_string)。
  4. string类是basic_string模板类的一个实例,它使用char来实例化basic_string模板类,并用char_traits和allocator作为basic_string的默认参数(根于更多的模板信息请参考basic_string)。
  5. 注意,这个类独立于所使用的编码来处理字节:如果用来处理多字节或变长字符(如UTF-8)的序列,这个类的所有成员(如长度或大小)以及它的迭代器,将仍然按照字节(而不是实际编码的字符)来操作

总结:

  1. string是表示字符串的字符串类
  2. 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作string的常规操作。
  3. string在底层实际是:basic_string模板类的别名,typedef basic_string<char, char_traits, allocator>string;
  4. 不能操作多字节或者变长字符的序列

在使用string类时,必须包含#include头文件以及using namespace std

⭐1.1 string构造函数

【C++】string类详解
常用接口如下:

constructor函数名称 功能说明 函数原型
string() 构造空的string类对象,即空字符串 string();
string(const char * s) 用C-string来构造string类对象 string (const char* s, size_t n);
string(size_t n, char c) string类对象中包含n个字符c string (size_t n, char c);
string(const string&s) 拷贝构造函数 string (const string& str, size_t pos, size_t len = npos);
int main()
{
	string s1;
	string s2("hello wold");

	//隐式类型转换
	string s3 = "hello world";

	//从s3的第六个字符开始取三个字符
	string s4(s3, 6, 3);
	cout << s4 << endl;

	//超过了范围就取到末尾
	string s5(s3, 6, 12);
	cout << s5 << endl;

	//如果不给第三个参数呢? 官方文档给了一个缺省值 -- npos -- 42亿多
	//一个字符串不可能有那么长,所有默认取到结尾
	string s6(s3, 6);
	cout << s6 << endl;

	string s7("hello world", 6);
	cout << s7 << endl;

	string s8(10, 'x');
	cout << s8 << endl;

	//size_t是一种无符号整数类型就是unsigned int
	for (size_t i = 0; i < s2.size(); i++)
	{
		s2[i]++;
	}
	cout << s2 << endl;

	for (size_t i = 0; i < s2.size(); i++)
	{
		cout << s2[i] << " ";
	}

	//析构函数了解即可
	return 0;
}

对于拷贝构造函数,如果我们没有给第三个参数,会默认是缺省值npos,我们从官方文档可见,它是一个极大的数,我们所能用到的字符串长度不可能有这么大的,所以不给值的就默认是这个缺省值——即取到字符串末尾

【C++】string类详解
对于析构函数自行看一下官方文档即可.

⭐1.2 string容量操作

【C++】string类详解
常用接口如下:

函数名称 功能说明 函数原型
size 返回字符串有效字符长度 size_t size() const;
length 返回字符串有效字符长度 size_t length() const;
capacity 返回空间总大小 size_t capacity() const;
empty 检测字符串释放为空串,是返回true,否则返回false bool empty() const;
clear 清空有效字符 void clear();
reserve 为字符串预留空间 void reserve (size_t n = 0);
resize 将有效字符的个数改成n个,多出的空间给出的字符c填充 void resize (size_t n);void resize (size_t n, char c);

string的容量

int main()
{
	string s1("hello world");
	
	//字符串有效数据位数
	//size方法跟length是没有区别的
	//size是为了跟stl保持一致后面再加入的,建议用size
	cout << s1.size() << endl;//11
	cout << s1.length() << endl;//11

	//字符串容量
	cout << s1.capacity() << endl;//15

	//字符串所能达到的最大长度
	cout << s1.max_size() << endl;//字符串的最大长度,不同编译器的值可能不同

	return 0;
}

【C++】string类详解

string的扩容

我们可以用push_back方法向string对象里面插入一个字符

//string扩容
int main()
{
	//观察扩容的情况,不同对象可能不同
	//对于库里面的string第一次是2倍扩容,其余次数是1.5倍扩容
	string s("hello world");

	size_t sz = s.capacity();
	cout << "capacity: " << sz << '\n';
	cout << "making s grow:\n";

	for (int i = 0; i < 100; ++i)
	{
		s.push_back('c');
		if (sz != s.capacity())
		{
			sz = s.capacity();
			cout << "capacity changed: " << sz << '\n';
		}
	} 
	return 0;
}

【C++】string类详解
实际上,string类的简单成员如下:、

class string
{
private:
	char* _ptr;
	char _buf[16];
	size_t _size;
	size_t _capacity;
};

当我们调试观察string内部成员,capacity超过16的话就不会存在buf数组里面了,而是存在于ptr申请的堆空间上了,如果没超过16那么就会存在buf数组上

【C++】string类详解

但是我们知道,凡是关于扩容,我们的系统就会加大开销,所以无论我们是在做oj题还是在工作中,都尽量的事先扩好需要的容量,会大大的减小开销,以下是两个常用string扩容函数

reserve && resize

int main()
{
	string s1("hello world");

	//扩容但不初始化 111
	s1.reserve(100);
	cout << s1.size() << endl;//11
	cout << s1.capacity() << endl;//111

	string s2("hello world");

	//扩容初始化,未给第二个参数的话初始化成缺省值0
	s2.resize(100);
	cout << s2.size() << endl;//100
	cout << s2.capacity() << endl;//111

	//扩容初始化,将一百个位置全部初始化为x
	//但是字符串的100个位置全有值,所以字符串无变化
	s2.resize(100, 'x');
	cout << s2.size() << endl;
	cout << s2.capacity() << endl;

	//如果比size小,还可以删除数据,保留前五个数据不变,但容量不会缩容
	s2.resize(5);
	cout << s2.size() << endl;//5
	cout << s2.capacity() << endl;//111

	return 0;
}

resize初始化不会动原始数据的值,空间不足则开辟的空间,那段开辟的空间里面才会存放初始化的值

无论是reserve还是resize均不会缩容(即变小_capacity的值),因为底层的OS并不允许这么做,并且这么做会造成其它的一些问题

注意:

  1. size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。
  2. clear()只是将string中有效字符清空,不改变底层空间大小。
  3. resize(size_t n)resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间resize(size_t n, char c)用字符c来填充多出的元素空间。注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。
  4. reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小

⭐1.3 string类对象的访问遍历操作

【C++】string类详解

常用接口如下:

函数名称 功能说明 函数原型
operator[] 返回pos位置的字符,const string类对象调用 char& operator[] (size_t pos); const char& operator[] (size_t pos) const;
at 返回pos位置的字符,const string类对象调用 char& at (size_t pos);const char& at (size_t pos) const;
beginend begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 iterator begin();const_iterator begin() const;iterator end();const_iterator end() const;
rbeginrend begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器 reverse_iterator rbegin();const_reverse_iterator rbegin() const;reverse_iterator rend();const_reverse_iterator rend() const;
范围for C++11支持更简洁的范围for的新遍历方式 null

operator[] && at()

//string访问操作
int main()
{
	string s("hello world");

	cout << s[0] << endl;
	cout << s.at(0) << endl;

	//操作符越界:内部是直接assert断言的
	s[100];//断言终止

	//at方法越界的话是抛异常,可以被捕获到
	try
	{
		s.at(100);
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;//打印异常信息
	}

	return 0;
}

C++有一套异常机制,一些数组越界之类的问题可以靠抛异常来解决,这个我们后面再谈.

begin | end && rbegin | rend

//string遍历操作
void Func(const string& str)
{
	//如果继续用正常迭代器编译不通过 - const对象不允许写权限放大
	//可以使用const迭代器,遍历和读容器的数据,不能写

	//string::const_iterator it = str.begin();
	auto it = str.begin();
	while (it != str.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//string::const_reverse_iterator rit = str.rbegin();
	auto rit = str.rbegin();
	while (rit != str.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
}
//string内迭代器
int main()
{
	string s("hello world");

	//正向迭代器
	//string::iterator it = s.begin();

	auto it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	//反向迭代器
	//string::reverse_iterator rit = s.rbegin();

	auto rit = s.rbegin();
	while (rit != s.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;

	Func(s);

	return 0;
}

对于迭代器这种类型比较复杂,建议用auto自动识别类型,因为const迭代器需要特别区分,所以比较容易出错.我们再用范围for试试遍历string对象(底层也是迭代器)

//范围for
int main()
{
	string str("hello world");
	for(auto c : str)
	{
		cout << c << endl;
	}
	return 0;
}

在string这里使用迭代器明显没有下标访问有用,那为啥还会存在迭代器呢??我们想想二叉树的遍历还能用下标吗?不能,而迭代器是通用的,并不拘泥于顺序表类型

C++11提供了cbegincend以及crbegincrend来区分const对象,了解即可

⭐1.4 string类对象的修改操作

【C++】string类详解

函数名称 功能说明 函数原型
operator+= 在字符串尾部追加一个字符串或字符 【C++】string类详解
append 在字符串后追加一个字符串 【C++】string类详解
push_back 在字符串后尾插字符c 【C++】string类详解
assign 把字符串赋给当前对象 【C++】string类详解
insert 在字符串任意位置插入一个字符串 【C++】string类详解
erase 删除任意位置的任意字符 【C++】string类详解
replace 替换掉字符串任意位置的字符串 【C++】string类详解
swap 交换两个字符串 【C++】string类详解
pop_back 将字符串尾部字符删除 【C++】string类详解

注意:

  1. 在string尾部追加字符时,s.push_back(c) / s.append(1, c) / s += 'c'三种的实现方式差不多,一般情况下string类的+=操作用的比较多,+=操作不仅可以连接单个字符,还可以连接字符串。
  2. 对string操作时,如果能够大概预估到放多少字符,可以先通过reserve把空间预留好

字符串追加

int main(){
	string s1("hello");


	//追加一个字符
	s1.push_back(' *');
	cout << s1 << endl;

	//追加一个字符串
	s1.append("world");
	cout << s1 << endl;

	s1.clear();

	//通用 -- 推荐使用
	s1 += "hello ";
	s1 += 'w';
	s1 += "orld";
	cout << s1 << endl;

	return 0;
}

字符串插入删除替换

//insert  erase replace
int main()
{
	//insert:插入数据,不推荐经常使用 - 时间复杂度高,效率低
	string s1("world");

	//任意位置插入字符串
	s1.insert(0, "hello");
	cout << s1 << endl;

	//插入多个字符
	s1.insert(5, 3,  'a');
	cout << s1 << endl;
	
	//迭代器大法
	s1.insert(s1.begin()        + 5, ' ');
	cout << s1 << endl;
	
	//迭代器插入区间后面我们再讲

	//erase:删除数据,也存在挪动数据,效率低下
	string s2("hello world");

	//第五个位置开始删除1个字符
	s2.erase(5, 1);
	cout << s2 << endl;

	//传入迭代器,删除该位置字符
	s2.erase(s2.begin() + 5);
	cout << s2 << endl;

	//超过长度的时候,直接删到字符串尾部,因为给了缺省值npos
	s2.erase(5, 30);
	cout << s2 << endl;

	//replace:替换字符,效率也比较低下
	string s3("hello world");

	s3.replace(5, 2, "%20");
	cout << s3 << endl;

	//swap
	s1.swap(s2);
	cout << s1 << endl;
	cout << s2 << endl;

	swap(s1, s2);
	cout << s1 << endl;
	cout << s2 << endl;

	//有什么区别?
	//实际上我们直接库里面的swap函数会比用模板类生成的函数高效许多


	return 0;
}

⭐1.5 string类对象的常用功能

【C++】string类详解

常用接口如下:

函数名称 功能说明 函数原型
c_str 打印字符串吗,以\0结尾标志结束 【C++】string类详解
find 从字符串pos位置开始往后找字符串,完全匹配则返回第一个字符出现的位置 【C++】string类详解
refind 从字符串pos位置开始往前找字符串,完全匹配则返回最后一个字符出现的位置 【C++】string类详解
find_first_of 从字符串pos位置开始从前往后找,找到输入的字符串中任意一个字符的位置并返回 【C++】string类详解
find_last_of 从字符串pos位置开始从后往前找,找到输入的字符串中任意一个字符的位置并返回 【C++】string类详解
substr 在str中从pos位置开始,截取n个字符,然后将其返回 【C++】string类详解
void Test1()
{
	//c_str:将字符串识别成const char*,以\0为准认定为末尾
	string s1("hello world");

	//区别:流插入按照size()打印,c_str按照\0终止位置进行打印
	s1 += '\0';
	s1 += "xxxxx";
	cout << s1 << endl;
	cout << s1.c_str() << endl;

	//我们可以用c_str来将string对象转换为const char*
	string filename("Work.cpp");
	FILE* fout = fopen(filename.c_str(), "r");
	if (fout == nullptr)
	{
		perror("fopen fail");
		exit(-1);
	}
	cout << "Work.cpp:" << endl;
	char ch = fgetc(fout);
	while (ch != EOF)
	{  
		cout << ch;
		ch = fgetc(fout);
	}
	fclose(fout);

}
void Test2()
{
	string file("string.cpp.tar.zip");

	//从后往前找rfind
	size_t pos = file.rfind('.');
	if (pos != string::npos)
	{
		//string suffix = file.substr(pos, file.size() - pos);
		string suffix = file.substr(pos);
		cout << suffix << endl;
	}

	// 取出url中的域名
	string url("http://www.cplusplus.com/reference/string/string/find/");
	cout << url << endl;
	size_t start = url.find("://");
	if (start == string::npos)
	{
		cout << "invalid url" << endl;
		return 0;
	}
	start += 3;
	size_t finish = url.find('/', start);
	string address = url.substr(start, finish - start);
	cout << address << endl;

	// 删除url的协议前缀
	pos = url.find("://");
	url.erase(0, pos + 3);
	cout << url << endl;
}
void Test3()
{
	//find_first_of 顺着找,一旦输入字符串中任意一个字符匹配成功则返回其位置
	//find_last_of 倒着找,一旦输入字符串中任意一个字符匹配成功则返回其位置
	string str("Please, replace the vowels in this sentence by asterisks.");
	size_t found = str.find_first_of("aeiou");
	while (found != string::npos)
	{
		str[found] = '*';
		found = str.find_first_of("aeiou", found + 1);
	}

	cout << str << '\n';

	string s1("hello world");
	string s2("hello world");

	//字符串比较
	cout << (s1 == s2) << endl;
	cout << (s1 == "hello world") << endl;
	cout << ("hello world" == s1) << endl;

}

⭐1.6 string类非成员函数

【C++】string类详解

函数名称 功能说明 函数原型
operator+ 尽量少用,因为传值返回,导致深拷贝效率低 【C++】string类详解
relational operators (string) 运算符重载大小比较 【C++】string类详解
operator>> 输入运算符重载 【C++】string类详解
operator<< 输出运算符重载 【C++】string类详解
getline 获取一行字符串,以换行为结束符 【C++】string类详解
int main() {
	string s1;

	//因为有流插入和流提取运算符重载,所以可以直接打印string
	//输入hello world
	//cin >> s1;
	打印hello 
	//cout << s1 << endl;

	//这是为什么呢?
	//这是因为流插入符会识别空格跟换行符作为输入结束的标志
	//怎么解决呢?getline可以输入空格,并以\n作为结束标志

	string s2;

	
	getline(cin, s2);
	cout << s2 << endl;//hello world

	return 0;
}

????2.string经典OJ题

以下奉上几道string经典OJ题.

⭐2.1 字符串相加

来源:Leetcode:415.字符串相加

【C++】string类详解
题解:

class Solution {
public:
    string addStrings(string num1, string num2) { 
	int i = num1.size() - 1, j = num2.size() - 1, ret = 0;

	string str;
	//两个整数相加位数最多为大的那个加一位
	str.reserve(num1.size() > num2.size() ? num1.size() + 1 : num2.size() + 1);
	//ret != 0这个条件是判定是否最后还有进位
	while (i >= 0 || j >= 0 || ret != 0){
		if (i >= 0) ret += num1[i--] - '0';
		if (j >= 0) ret += num2[j--] - '0';
		str += (ret % 10 + '0');
		ret /= 10;
	}
	//最后反转即可得到答案
	reverse(str.begin(), str.end());

	return str;
    }
};

⭐2.2 替换空格

来源:Leetcode:JZ05.替换空格
【C++】string类详解
题解:

class Solution {
public:
    string replaceSpace(string s) {
    string newStr;
	size_t num = 0;
	for (auto ch : s)
	{
		if (ch == ' ')
		{
			num++;
		}
	}

	//提前开空间,避免replace时扩容
	newStr.reserve(s.size() + 2 * num);
	for (auto ch : s)
	{
		if (ch != ' ')
		{
			newStr += ch;
		}
		else
		{
			newStr += "%20";
		}
	}
	return newStr;
    }
};

⭐2.3 字符串转换为整数

来源:Leetcode:8.字符串转换整数(atoi)

【C++】string类详解

题解:

class Solution {
public:
    int myAtoi(string s) {
        int n = s.size();
        int idx = 0;
        while (idx < n && s[idx] == ' ') {
            // 去掉前导空格
            idx++;
        }
        if (idx == n) {
            //去掉前导空格以后到了末尾了
            return 0;
        }
        bool negative = false;
        if (s[idx] == '-') {
            //遇到负号
            negative = true;
            idx++;
        } else if (s[idx] == '+') {
            // 遇到正号
            idx++;
        } else if (!isdigit(s[idx])) {
            // 其他符号
            return 0;
        }
        int ans = 0;
        while (idx < n && isdigit(s[idx])) {
            int digit = s[idx] - '0';
            if (ans > (INT_MAX - digit) / 10) {
                // 本来应该是 ans * 10 + digit > INT_MAX
                // 但是 *10 和 + digit 都有可能越界,所有都移动到右边去就可以了。
                return negative? INT_MIN : INT_MAX;
            }
            ans = ans * 10 + digit;
            idx++;
        }
        return negative? -ans : ans;
    }
};

⭐2.4 字符串相乘

来源:Leetcode:43.字符串相乘

【C++】string类详解
题解:

class Solution {
public:
    string multiply(string num1, string num2) {
        if(num1 == "0" || num2 == "0"){return "0";}
        int size1 = num1.length(), size2 = num2.length();
        vector<int> memo(size1+size2, 0);
        string res = "";
        res.reserve(size1 + size2);
        for(int i = size1-1; i>=0; i--){
            int n1 = num1[i]-'0';
            for(int j = size2-1; j>=0; j--){
                int n2 = num2[j]-'0';
                int tempSum = memo[i+j+1]+n1*n2;
                memo[i+j+1] = tempSum % 10;
                memo[i+j] += tempSum / 10;
            }
        }
        //去除前置0
        bool flag = true;
        for(int i = 0; i<memo.size(); i++){
            if(memo[i] != 0 || flag == false){
                flag = false;
                res.push_back(memo[i]+'0');
            }
        }
        return res;
    }
};

????3.string类模拟实现

以下是string类常用接口模拟实现

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

//用new初始化
namespace ljk
{

	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;
		}
		/*string()
			:_str(new char[1])
			,_size(0)
			,_capacity(0)
		{
			_str[0] = '\0';
		}*/
		//不能传空指针,会崩溃
		//string(const char* str = nullptr)
		//传个空字符串就可以
		string(const char* str = "")
			:_size(strlen(str))
		{
			_capacity = _size == 0 ? 3 : _size;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		};
		string(const string& s)
			:_size(s._size)
			,_capacity(s._capacity)
		{
			_str = new char[s._capacity + 1];
			strcpy(_str, s._str);
		}
		const char* c_str()
		{
			return _str;
		}
		const char& operator[](size_t pos) const
		{
			assert(pos < _size);
			return _str[pos];
		}
		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}
		size_t size() const 
		{
			return _size;
		}
		size_t capacity() const
		{
			return _capacity;
		}
		//*
		string& operator=(const string& s)
		{
			if (this != &s)
			{
				//先用tmp开空间,就算new失败了原先_str的空间也还在
				char* tmp = new char[s._capacity + 1];
				strcpy(tmp, s._str);
				delete[] _str;
				_str = tmp;
				_size = s._size;
				_capacity = s._capacity;
			}

			return *this;
		}
		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 || *this == s;
		}
		string& operator+=(const char* ch)
		{
			append(ch);
			return *this;
		}
		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}
		//*
		void reserve(size_t n)
		{
			//如果n比capacity小会导致缩容
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				strcpy(tmp, _str);
				delete[] _str;
				_str = tmp;

				_capacity = n;
			}
		}
		//*
		//pong,分三种情况
		void resize(size_t n, char ch = '\0')
		{
			if (n > _size)
			{
				if (n > _capacity)
				{
					reserve(n);
				}
				memset(_str + _size, ch, n - _size);
			}

			_str[n] = '\0';
			_size = n;
		}
		void push_back(char ch)
		{
			/*if (_size + 1 > _capacity)
			{
				reserve(_capacity * 2);
			}
			_str[_size++] = ch;

			_str[_size] = '\0';*/
			insert(_size, ch);
		}
		void append(const char* str)
		{
			/*size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}
			strcpy(_str + _size, str);
			_size += len;*/
			insert(_size, str);
		}
		string& insert(size_t pos, char ch)
		{
			assert(pos <= _size);
			if (_size + 1 > _capacity)
			{
				reserve(2 * _capacity);
			}

			size_t end = _size + 1;
			//不太合适
			//while (end >= pos && end != npos)
			//合理
			while (end > pos)
			{
				_str[end] = _str[end - 1];
				--end;
			}

			//调用库函数
			//memmove(_str + pos + 1, _str + pos, _size - pos + 1);
			_str[pos] = ch;
			++_size;

			return *this;
		}
		//不调用库函数也OK
		string& insert(size_t pos, const char* str)
		{
			assert(pos <= _size);
			//string s(ch);
			size_t len = strlen(str);
			if (_size + len > _capacity)
			{
				reserve(_size + len);
			}

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

			//调用库函数
			/*memmove(_str + pos + len, _str + pos, _size - pos + 1);
			memmove(_str + pos, str, len);*/
			_size += len;

			return *this;
		}
		string& erase(size_t pos, size_t len = npos)
		{
			assert(pos < _size);
			if (len != npos && pos + len < _size)
			{
				memmove(_str + pos, _str + pos + len, _size - pos - len);
				_str[_size - len] = '\0';
				_size -= len;
			}
			else 
			{
				_str[pos] = '\0';
				_size = pos;
			}

			return *this;
		}
		size_t find(char ch, size_t pos = 0)
		{
			assert(pos < _size);

			for (size_t i = pos; i < _size; i++)
			{
				if (_str[i] == ch)
				{
					return i;
				}
			}

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

			//KMP算法 :: BM算法 -- 有兴趣看看
			char* p = strstr(_str + pos, ch);
			if (p == nullptr)
			{
				return npos;
			}
			else
			{
				return p - _str;
			}
		}
		void swap(string& s)
		{
			std::swap(_str, s._str);
			std::swap(_capacity, s._capacity);
			std::swap(_size, s._size);
		}
		void clear()
		{
			_str[0] = '\0';
			_size = 0;
		}
		~string()
		{
			delete[] _str;
			_str = nullptr;
			_size = _capacity = 0;
		}
	private:
		char* _str;
		size_t _size;
		size_t _capacity;

		//只针对整型
		static const size_t npos;
		//其它类型报错
		//static const double npos = -1;

		/*static const size_t N = 10;
		int _a[N];*/
	};

	const size_t string::npos = -1;

	istream& operator>>(istream& in, string& s)
	{
		//之前对象中如果有数据就清空
		s.clear();
		char ch = in.get();
		char buff[128];
		//get && getline
		//遇到空格或者换行停止 && 遇到换行停止
		//while (ch != '\n') //getline
		size_t i = 0;
		while (ch != ' ' && ch != '\n')
		{
			//s += ch;//输入的字符串够长,频繁扩容
			//编译器以为是多个字符之间的间隔

			//部分扩容
			buff[i++] = ch;
			if (i == 127)
			{
				buff[i] = '\0';
				s += buff;
				i = 0;
			}
			ch = in.get();
		}
		if (i != 0)
		{
			buff[i] = '\0';
			s += buff;
		}
		return in;
	}
	ostream& operator<<(ostream& out, const string& s)
	{
		//有区别的
		//out << s.c_str() << endl;

		for (auto ch : s)
		{
			out << ch;
		}
		return out;
	}
	void Print(const string& s)
	{
		for (size_t i = 0; i < s.size(); i++)
		{
			cout << s[i] << " ";
		}
		cout << endl;
		for (auto ch : s)
		{
			cout << ch << " ";
		}
		cout << endl;
	}

	void test_string1()
	{
		string s1;
		string s2("hello world");

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

		s2[0]++;

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

	void test_string2()
	{
		string s1;
		string s2("hello world");

		//浅拷贝
		//1.一个修改会影响另外一个
		//2.析构两次
		string s3(s2);

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

		s2[0]++;

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

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


		//必须处理,不然会首先释放掉空间
		s1 = s1;
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
		cout << s3.c_str() << endl;
	}

	void test_string3()
	{
		string s1("hello world");
		for (size_t i = 0; i < s1.size(); i++)
		{
			s1[i]++;
		}

		Print(s1);

		//迭代器遍历string
		/*string::iterator it = s1.begin();
		while (it != s1.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;*/

		//范围for基于迭代器,如果迭代器不能使用,那么范围for也失效了
		/*for (auto ch : s1)
		{
			cout << ch << " ";
		}*/
		cout << endl;
	}

	void test_string4()
	{
		string s1("hello world");
		string s2("hello world");
		string s3("xx");

		cout << (s1 < s2) << endl;
		cout << (s1 == s2) << endl;
		cout << (s1 >= s2) << endl;
	}
	void test_string5()
	{
		string s1("hello world");
		string s2("hello world");
		string s3("xx");
		string s5;
		//s1.push_back(']');
		//s1.append("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
		/*s1 += ' ';
		s1 += " xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";

		cout << s1.c_str() << endl;*/

		string s4;
		s4 += 'a';
		s4 += 'c';
		//cout << s4.c_str() << endl;

		s1.insert(5, 'x');
		//cout << s1.c_str() << endl;

		s1.insert(0, 'x');
		s1.insert(0, 'x');
		s1.insert(0, 'x');
		s1.insert(0, 'x');

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

		/*std::string s6("hello");
		s6.insert(9, "");
		cout << s6 << endl;*/

		/*s2.insert(11, "zzzzzzzz");
		cout << s2.c_str() << endl;*/

		/*std::string s7("hello world");
		s7.erase(5, 2);*/


		//cout << s7 << endl;


		s2.erase(3, 5);
		cout << s2.c_str() << endl;
	}
	void test_string6()
	{
		string s1("hello world1111111111111111");
		cout << s1.c_str() << endl;
		s1.reserve(10);
		cout << s1.c_str() << endl;
	}
	void test_string7()
	{
		//坚决不缩容,只会保留前10个数据但是不缩_capacity
		std::string s1;
		s1.resize(20, 'x');
		cout << s1.c_str() << endl;
		s1.resize(10, 'y');
		cout << s1.c_str() << endl;
	}
	void test_string8()
	{
		std::string s("hello world");

		s.insert(11, "xxxxxxxx");

		s.insert(5, "xxxxxxxxxxxxx");
		s.insert(0, "sasa");

		cout << s.c_str() << endl;
	}
	void test_string9()
	{
		std::string s("0123456789");


		s.erase(4, 3);
		cout << s.c_str() << endl;

		s.erase(4, 30);
		cout << s.c_str() << endl;

		s.erase(2);
		cout << s.c_str() << endl;
	}
	//流插入重载必须实现成友元函数? 不对
	void test_string10()
	{
		string s1("0123456789");
		s1 += '\0';
		s1 += "xxxxxxxx";

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

		string s2;
		cin >> s2;
		cout << s2 << endl;
	}
}

注意:s1.c_str == s2.c_str比较的是变成const char*类型的字符串地址,就算字符串一样,比较出来的值也是不一样的,还有两个对象在未设置接口的情况无法用==比较