初阶C++__STL__string类(使用方法+模拟实现+测试+思路分析)

发布于:2023-01-19 ⋅ 阅读:(397) ⋅ 点赞:(0)

目录

STL简介

string类简介

Ⅰ.  string类的常用接口

string库函数中的构造

补充:拷贝构造支持从pos开始,初始化npos个字符

Ⅱ. sting类对象的容量操作

1、size()与length()的区别

2、返回空间总大小的 capacity()

3、清空有效字符的 clear()

4、  调整字符串大小 resize()

5、请求更改容量reserve()

Ⅲ. string类对象的访问及遍历操作

1、访问字符串字符的 operator[] 和 at()

2、初识迭代器与begin() \ end() \ rbegin() \ rend()

3、范围for

Ⅳ.string 类对象的修改操作

1、string赋值与拼接push_back() append() operator+=的用法

2、返回C格式字符串的 c_str

3、追查字符位置的 find() 和生成子字符串的 substr()

反向追查字符串的 rfind()  

4、string字符串比较和替换 - replace() compare()

5、 string插入和删除 - insert() erase()

Ⅴ.string 类非成员函数的实现

1.operator 运算符重载

2.流插入和流提取

完整代码


STL简介


STL(standard template libaray-标准模板库):是C++标准库的重要组成部分,不仅是一个可复用的组件库,而且是一个包罗数据结构与算法的软件框架

其中包括六大组件:

  • 算法
  • 容器
  • 迭代器
  • 空间适配器
  • 仿函数
  • 配接器

而我们这个系列,实现的是各种各样的容器

网上有句话说:“不懂STL,不要说你会C++”。STL是C++中的优秀作品,有了它的陪伴,许多底层的数据结构以及算法都不需要自己重新造轮子,站在前人的肩膀上,健步如飞的快速开发。

string类简介

string是c++中表示字符串的容器,其底层的字符串表示方式仍然是以’\0’表示的字符串集合但是提供了比c语言更多的接口

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

#include <string>       // 使用string类时,需引入头文件 <string>
using namespace std;    // 展开std

string是C++风格的字符串,而string本质上是一个类

string和char * 区别:

  • char * 是一个指针
  • string是一个类,类内部封装了char*,管理这个字符串,是一个char*型的容器。

string成员中包含了以下几个成员:

  • 字符串数组
  • 字符串的有效字符大小
  • 字符串容量
  • 迭代器

对以上点做出以下解释:

迭代器提供了遍历整个字符串的方式,虽然string中的遍历没有迭代器会更加方便。但因为与STL中其它难以遍历的容器统一,所以string中并没有舍弃迭代器。

容量指的是类为我们的字符串数组开辟的空间,而有效字符个数指的是我们已经使用已经存储了元素的容量大小。

有效字符和容量都不包括’\0’

所以,我们模拟实现的字符串类型中,有以下成员:

char* _str;//字符串类型
size_t size;//字符串有效字符大小
size_t capacity;//字符串容量

size_t 是 unsigned long long,传-1的实际值是2^64-1 


 官方文本文档:

string类的文档介绍 - C++ Reference (cplusplus.com)

① string 是表示字符串的字符串类。

② 该类的接口与常规容器的接口基本相同,再添加了一些专门用来操作 string 的常规操作。

③ string在底层上实际是:basic_string 模板类的别名:

typedef basic_string<char, char_traits, allocator>string;

④ 不能操作多字节或者变长字符的序列。
 

Ⅰ.  string类的常用接口

(constructor) 函数名称 功能说明
string()                           (重点) 构造空的string类对象,即空字符
string(const char* s)      (重点) 用C-string来构造string类对象
string(size_t n, char c)     string类对象中包含n个字符c
string(const string& s)   (重点) 拷贝构造函数
string()                      // 构造空的string类对象,即非空字符串。
string(cosnt char* s)         // 用C-string来构造string类对象
string(size_t n, char c)      // string类对象中包含n个字符c
string(const string&s)        // 拷贝构造函数

string库函数中的构造

库函数构造主要有默认构造含参构造拷贝构造

默认构造是构造出一个空字符串

string s1;
//构造出一个空字符串

含参构造中我们可以传入c语言中的字符串指针或字符串数组类型

string s2("hello world!");
//构造出"hello world!字符串"
string(const char* s = "")
	:_size(strlen(s)), _capacity(strlen(s))
{
	_str = new char[_capacity + 1];
	strcpy(_str, s);
}

也可以在string中构造n个相同的字符c

//string(size_t n,char c)
string s3(10,'a');
//第一个参数是个数n,第二个参数是字符c
//构造出的字符串:aaaaaaaaaa
string(size_t n, char c)
	:_size(n), _capacity(n)
{
	_str = new char[_capacity + 1];
	memset(_str, c, _size);
	_str[_size] = '\0';
}

拷贝构造使用某个其它stirng类的引用

string s4(s1);//拷贝一份s1到s4中

string 的拷贝构造: 

/* s2(s1) */
string(const string& s)
	: _str(new char[strlen(s._str) + 1]) {
	strcpy(_str, s._str);
}

演示:

#include<iostream>
#include<string> 
using namespace std;

//string构造
void test01()
{
	string s1; //创建空字符串,调用无参构造函数
	cout << "str1 = " << s1 << endl;

	const char* str = "hello world";
	string s2(str); //把c_string转换成了string

	cout << "str2 = " << s2 << endl;

	string s3(s2); //调用拷贝构造函数
	cout << "str3 = " << s3 << endl;

	string s4(10, 'a');
	cout << "str3 = " << s4 << endl;
}
int main() {
	test01();
	return 0;
}

 

补充:拷贝构造支持从pos开始,初始化npos个字符

 我们发现长度 len 缺省值给的是 npos:

string (
    const string& str, 
    size_t pos,         // 起始位置
    size_t len = npos   // 长度(缺省值给的npos)
    );

​  

这个 npos 你可以理解为是 string 里的一个静态变量,它是 -1。

当你没有指定 len 时,因为 npos 是 -1,而又是 size_t 无符号整型,他长度将会是整形的最大值。

也就是说,如果你不传,它就相当于取整型最大值 2147483647,所以也就相当于取所有字符串了

int main(void)
{
	string s1("abcdef123456");
	string s2(s1, 2);       // 从第2个位置开始,初始化npos个长度的字符
	cout << s2 << endl;
 
	return 0;
}

Ⅱ. sting类对象的容量操作

函数名称 功能说明
size     (重点) 返回字符串有效字符长度
length 返回字符串有效字符长度
capacity 返回空间总大小
empty  (重点) 检测字符串是否为空串,是返回true,否则返回 flase
clear    (重点)

清空有效字符

reserve(重点) 为字符串预留空间
resize   (重点) 将有效字符的个数改成n个,多出的空间用字符c填充

1、size()与length()的区别

①  size() 和 length() 的计算不包含 \0。

     解释:它不包含最后作为结尾标识符的 \0,告诉你的是有效的字符长度。

②  size() 和 length() 的功能都是返回字符串有效长度,功能上没有区别。

     解释: 这是一个 "历史包袱" 问题。

总结:size()与length()方法底层实现原理完全相同,引入size()的原因是为了与其他容器的接口保持一致,一般情况下基本都是用size()。

2、返回空间总大小的 capacity()

返回当前为字符串分配的存储空间的大小,以字节表示。

容量不一定等于字符串长度。它可以等于或更大,额外的空间允许对象在向字符串添加新字符时优化其操作。

请注意,此容量并不假定字符串的长度限制。当此容量耗尽并且需要更多容量时,它由对象自动扩展(重新分配其存储空间)。字符串长度的理论限制由成员max_size给出。

总结:在算法中没什么用处,因为每个编译器分配空间方式都不同,所以值都有所出入

3、清空有效字符的 clear()

clear:擦除字符串的内容,该字符串将成为空字符串长度为 0 个字符)

#include<iostream>
#include<string> 
using namespace std;

int main()
{
	string s1("abcdef");
    cout << "清空前: " << s1 << endl;
	cout << s1.size() << endl;
 	cout << s1.capacity() << endl;
	s1.clear();
	cout << "清空后: " << s1 << endl;
	cout << s1.size() << endl;
	cout << s1.capacity() << endl;
	return 0;
}

 总结:clear()只是将string中有效字符清空不改变底层空间大小

4、  调整字符串大小 resize()

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

将字符串大小调整为 n 个字符的长度

如果 n 小于当前字符串长度,则当前值将缩短为其前 n 个字符,并删除超出 n 的字符。

#include<iostream>
#include<string> 
using namespace std;

int main()
{
	string s1("abcdef");
    s1.resize(3);
    cout<<s1<<endl;
    s1.resize(30,'1');
	cout<<s1<<endl;
	return 0;
}

总结: resize(size_t n) 与 resize(size_t n, char c)都是将字符串中有效字符个数改变到n个,不同的是当字符个数增多时:resize(n)用0来填充多出的元素空间,resize(size_t n, char c)用字符c来填充多出的元素空间

注意:resize在改变元素个数时,如果是将元素个数增多,可能会改变底层容量的大小,如果是将元素个数减少,底层空间总大小不变。


resize() 的实现

/* resize */
void resize(size_t new_capacity, char init_ch = '\0') {
	// 如果欲增容量比_size小
	if (new_capacity <= _size) {
		_str[new_capacity] = '\0';      // 拿斜杠零去截断
		_size = new_capacity;           // 更新大小
	}
	// 欲增容量比_size大
	else {
		if (new_capacity > _capacity) { 
			reserve(new_capacity);
		}
		// 起始位置,初始化字符,初始化个数
		memset(_str + _size, init_ch, new_capacity - _size);
		_size = _capacity;
		_str[_size] = '\0';
	}
}

5、请求更改容量reserve()

void reserve (size_t n = 0);

 请求将字符串容量适应计划的大小更改为最多 n 个字符的长度

如果 n 大于当前字符串容量,则该函数会导致容器将其容量增加到 n 个字符(或更大)。

在所有其他情况下,它被视为收缩字符串容量的非约束性请求:容器实现可以自由地进行优化,并使字符串容量大于n

此函数对字符串长度没有影响,并且无法更改其内容。

与resize()的对比: 

可见reserve()仅仅改变容量,不改变长度 

总结: reserve(size_t res_arg=0):为string预留空间,不改变有效元素个数,当reserve的参数小于string的底层空间总大小时,reserver不会改变容量大小。

reserve() 的实现

/* 增容:reverse */
void reserve(size_t new_capacity) {
	if (new_capacity > _capacity) {
		char* tmp = new char[new_capacity + 1];  // 开新空间
		strcpy(tmp, _str);                       // 搬运
		delete[] _str;                           // 释放原空间
 
		_str = tmp;                              // 没问题,递交给_str
		_capacity = new_capacity;                // 更新容量
	}
}

Ⅲ. string类对象的访问及遍历操作

函数名称 功能说明
operator[] 返回pos位置的字符,const string类对象调用
begin + end begin获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器
rbegin + rend rbegin获取一个字符的反向迭代器 + rend获取最后一个字符下一个位置的迭代器
范围 for C++11支持更简洁的范围 for 新遍历方式

1、访问字符串字符的 operator[] 和 at()

如果访问这个字符串的每一个字符,会有: 

for (size_t i = 0; i < s1.size(); i++) {
		cout << s1[i] << " ";
	}
cout << s1[i] << " ";        
cout << s1.operator[](i) << " ";   // 取字符串第i个位置的字符的引用

string 这里它作为函数调用,这就是 operator[]  ,并且等价于:cout << s1.operator[](i) << " ";

operator[] 的底层是这样设计的:

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

 所以


at() 

除了 operator[] 还有一个 at() ,at() 也是早期支持的一个接口

它和 operator[] 用处是一样的,at() 是像函数一样去使用的:

int main(void)
{
	string s1("hello world");
 
	for (size_t i = 0; i < s1.size(); i++) {
		s1.at(i) += 1;
	}
	cout << s1 << endl;
 
	return 0;
}

at() 和 operator[] 的区别 —— 它们检查越界的方式不一样。

 operator[] 是使用断言处理的,断言是一种激进的处理手段。

而 at() 是比较温柔的处理方式,如果 pos >= size 就 throw 一个异常对象出去。

2、初识迭代器与begin() \ end() \ rbegin() \ rend()

迭代器是 STL 六大组件之一,是用来访问和修改容器的。 

 同理:

#include<iostream>
#include<string> 
using namespace std;

int main(void)
{
	string s1("hello");
	
    // 迭代器
	
  	for (string::reverse_iterator rit=s1.rbegin(); rit!=s1.rend(); ++rit)
    	cout << *rit;
 
	return 0;
}

输出:olleh

迭代器遍历的意义:

  • 对于 string,你得会用迭代器,但是一般我们还是喜欢用 下标 + [] 遍历;
  • 比如 list、map / set  不支持 下标 + [] 遍历,迭代器就排上用场了,这就是迭代器存在的意义。
  • 迭代器是通用的遍历方式。

3、范围for

C++11 支持更简洁的范围 for 的新遍历方式
#include<iostream>
#include<string> 
using namespace std;

int main(void)
{
	string s1("hello");
	for (auto e : s1) {
		cout << e << " ";	//遍历
	}
	cout << endl;

	for (auto e : s1) {    // 无引用修改
		e += 1;
	}
	cout << "无引用 " << s1 << endl;

	for (auto& e : s1) {    // 引用修改
		e += 1;
	}
	cout << "引用修改 " << s1 << endl;

	return 0;
}

 详情可看下方连接的 Ⅴ. auto关键字(C++11)

  C++入门基础总结(最详细)

Ⅳ.string 类对象的修改操作

函数名称 功能说明
push_back 在字符串后尾插字符c
append 在字符后追加一个字符串
operator+=    (重点) 在字符串后追加字符串str
c_str              (重点) 返回c格式字符串
find  +  npos  (重点) 从字符串pos位置开始往后找字符c,返回该字符自字符串中的位置
rfind 从字符串pos位置开始往前找字符c,返回该字符在字符串中的位置
substr 在str中从pos位置开始,截取n个字符,然后将其返回

1、string赋值与拼接push_back() append() operator+=的用法

push_back:插入一个字符,但只能插入一个字符

append:可以插入字符串

 operator+=:可以添加字符、字符串、对象

  • string& operator=(const char* s); //char*类型字符串 赋值给当前的字符串
  • string& operator=(const string &s); //把字符串s赋给当前的字符串
  • string& operator=(char c); //字符赋值给当前的字符串

演示:

#include<iostream>
#include<string> 
using namespace std;

string s="C++";
string s1="";

int main()
{
	s1.push_back('I');//只能单字符 
	cout<<s1<<endl;
	
	s1.append(" Love ");//插入字符串 
	cout<<s1<<endl;
	
	s1+='C';
 	cout<<s1<<endl;
 	s1+=" and ";
 	cout<<s1<<endl;
 	s1+=s;
 	cout<<s1<<endl;
	//可以添加字符、字符串、对象 
 
	return 0;
}

push_back() 的实现

/* 字符串尾插:push_back */
void push_back(char append_ch) {
	if (_size == _capacity) {      // 检查是否需要增容
		reserve(_capacity == 0 ? 4 : _capacity * 2); 
	}
 
	_str[_size] = append_ch;       // 插入要追加的字符
	_size++;
	_str[_size] = '\0';            // 手动添加'\0'
}

append() 的实现

/* 字符串追加:append */
void append(const char* append_str) {
	size_t len = strlen(append_str);     // 计算要追加的字符串的长度
	if (_size + len > _capacity) {       // 检查是否需要增容
		reserve(_size + len);
	}
 
	strcpy(_str + _size, append_str);    // 首字符+大小就是\0位置
	_size += len;                        // 更新大小
}

 operator+= 的实现

/* operator+= */
string& operator+=(char append_ch) {
	push_back(append_ch);    // 复用push_back
	return *this;
}
string& operator+=(const char* append_str) {
	append(append_str);      // 复用append
	return *this;
}

2、返回C格式字符串的 c_str

当我们用printf输出string类型时,就必须用c_str

#include<iostream>
#include<string> 
using namespace std;

int main() {
	string s1("hello string");
	cout << s1 << endl;  // 调用重载的流插入运算符打印的
	printf("%s\n",s1.c_str());
	//printf("%s\n",s1);
	cout << s1.c_str() << endl;  
	// 这是调字符串打印的,c_str 是遇到 \0 结束的,
}

 补充:转成字符串才能取地址时

// 获取file后缀
void Test() {
	string file("test.txt"); 
	FILE* pf = fopen(file.c_str(), "w");  
}

比如这里需要打开文件,fopen 第一个参数要求是 const char*,

所以这里怎么能直接放 string 是不行的,这时候可以用 .c_str()  就可以把字符串的地址返回出来。

3、追查字符位置的 find() 和生成子字符串的 substr()

find:从字符串 pos 位置开始往后找字符 c ,返回该字符在字符串中的位置。

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

find('字符',开始位置); (不给开始位置默认从0开始)

substr:在 str 中从 pos 位置开始,截取 n 个字符,然后返回一个新构造的对象,其值初始化为此对象的子字符串的副本。

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

 substr(开始位置,截取长度);(不给截取长度默认缺省值无穷大npos)

演示:

#include<iostream>
#include<string> 
using namespace std;

int main() {
	//创建字符串记录网址
	string url("http://www.cplusplus.com/reference/string/string/find/");
 
	// 取出协议
	size_t pos1 = url.find(':'); //默认从头找
	string protocol = url.substr(0, pos1);
	cout << protocol << endl; 
 
	// 取出域名
	size_t pos2 = url.find('/', pos1 + 3);  
				// 冒号位置+3开始往后找(w位置) 
	string domain = url.substr(pos1 + 3, pos2 - (pos1 + 3));
	cout << domain << endl;
 
	// 取出路径
	string uri = url.substr(pos2 + 1);
	cout << uri << endl;

}

反向追查字符串的 rfind()  

#include<iostream>
#include<string> 
using namespace std;

void test01()
{
	//查找
	string str1 = "abcdefgde";

	int pos = str1.find("de");

	if (pos == -1)
	{
		cout << "未找到" << endl;
	}
	else
	{
		cout << "pos = " << pos << endl;
	}
	
	pos = str1.rfind("de");

	cout << "pos = " << pos << endl;

}


int main() {
	test01();
	return 0;
}

pos = 3
pos = 7

与find()同理,只是从右往左找而已

find() 的实现

find:查找字符

/* find */
size_t find(char aim_ch) {
	for (size_t i = 0; i < _size; i++) {
		if (aim_ch == _str[i]) { 
			// 找到了
			return i;    // 返回下标
		}
	}
	// 找不到
	return npos;
}

 find:查找字符串

size_t find(const char* aim_str, size_t pos = 0) {
	const char* ptr = strstr(_str + pos, aim_str);
	if (ptr == nullptr) {
		return npos;
	}
	else {
		return ptr - _str;  // 减开头
	}
}

4、string字符串比较和替换 - replace() compare()

  • string& replace(int pos, int n, const string& str); //替换从pos开始n个字符为字符串str
  • string& replace(int pos, int n,const char* s); //替换从pos开始的n个字符为字符串s

  • int compare(const string &s) const; //与字符串s比较
  • int compare(const char *s) const; //与字符串s比较

replace():

void test02()
{
	//替换
	string str1 = "abcdefgde";
	str1.replace(1, 3, "1111");

	cout << "str1 = " << str1 << endl;
}

int main() {
	test02();
	return 0;
}

str1 = a1111efgde

compare():

#include<iostream>
#include<string> 
using namespace std;
//字符串比较
void test01()
{

	string s1 = "hello";
	string s2 = "aello";

	int ret = s1.compare(s2);

	if (ret == 0) {
		cout << "s1 等于 s2" << endl;
	}
	else if (ret > 0)
	{
		cout << "s1 大于 s2" << endl;
	}
	else
	{
		cout << "s1 小于 s2" << endl;
	}

}

int main() {

	test01();

	return 0;
}

5、 string插入和删除 - insert() erase()

  • string& insert(int pos, const char* s); //插入字符串
  • string& insert(int pos, const string& str); //插入字符串
  • string& insert(int pos, int n, char c); //在指定位置插入n个字符c
  • string& erase(int pos, int n = npos); //删除从Pos开始的n个字符
#include<iostream>
#include<string> 
using namespace std;
//字符串插入和删除
void test01()
{
	string str = "hello";
	str.insert(1, "111");
	cout << str << endl;

	str.erase(1, 3);  //从1号位置开始3个字符
	cout << str << endl;
}

int main() {

	test01();

	return 0;
}

h111ello
hello

erase() 的实现

/* 删除:erase */
string& erase(size_t pos, size_t len = npos) {
	assert(pos < _size);
 
	if (len == pos || pos + len >= _size) {
		_str[pos] = '\0';    // 放置\0截断
		_size = pos;
	}
	else {
		strcpy(_str + pos, _str + pos + len);
		_size -= len;
	}
 
	return *this;
}

insert() 的实现

insert:字符

/* 插入:insert */
string& insert(size_t pos, char append_ch) {
	assert(pos <= _size);
 
	// 检查是否需要增容
	if (_size == _capacity) {     
		reserve(_capacity == 0 ? 4 : _capacity * 2);
	}
			
	// 向后挪动数据
	//size_t end = _size;
	//while (end >= (int)pos) {
	//	_str[end + 1] = _str[end];
	//	end--;
	//}
 
	size_t end = _size + 1;
	while (end > pos) {
		_str[end] = _str[end - 1];
		end--;
	}
 
	// 插入
	_str[pos] = append_ch;
	_size++;
 
	return *this;
}

insert:字符串

string& insert(size_t pos, const char* append_str) {
	assert(pos <= _size);
	size_t len = strlen(append_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, append_str, len);
	_size += len;
 
	return *this;
}

Ⅴ.string 类非成员函数的实现

1.operator 运算符重载

大型造轮子现场,核心为复用与调用

 operator<

/* s1 < s2*/
bool operator<(const string& s1, const string& s2) {
	size_t i1 = 0, i2 = 0;
	while (i1 < s1.size() && i1 < s2.size()) {
		if (s1[i1] < s2[i2]) {
			return true;
		} 
		else if (s1[i1] > s2[i2]) {
			return false;
		}
		else {
			i1++;
			i2++;
		}
	}
	return i2 < s2.size() ? true : false;
}

当然,我们还可以实现的更简单些,直接用 strcmp 偷个懒:

/* s1 < s2*/
bool operator<(const string& s1, const string& s2) {
	return strcmp(s1.c_str(), s2.c_str()) < 0;
}

 operator==

/* s1 == s2 */
bool operator==(const string& s1, const string& s2) {
	return strcmp(s1.c_str(), s2.c_str()) == 0;
}

接下来的操作可以复用 == 和 <

高端的程序员,往往只需要最简单的编程方式~~

 operator<=

	/* s1 <= s2 */
	bool operator<=(const string& s1, const string& s2) {
		return s1 < s2 || s1 == s2;
	}

operator>

    /* s1 > s2 */
	bool operator>(const string& s1, const string& s2) {
		return !(s1 <= s2);
	}

operator>=

	/* s1 >= s2 */
	bool operator>=(const string& s1, const string& s2) {
		return !(s1 < s2);
	}

operator!=

	/* s1 != s2 */
	bool operator!=(const string& s1, const string& s2) {
		return !(s1 == s2);
	}

2.流插入和流提取

operator<< 的实现

// cout << s1  →  operator<<(cout, s1)
ostream& operator<<(ostream& out, const string& s) {
	//for (auto ch : s) {
	//	out << ch;
	//}
 
	for (size_t i = 0; i < s.size(); i++) {
		out << s[i];
	}
 
	return out;
}

operator>> 的实现

// cin >>
istream& operator<<(istream& in, string& s) {
	char ch = in.get();
	while (ch == '\n') {
		s += ch;
		ch = in.get();
	}
 
	return in;
}

完整代码

#include <iostream>
#include <assert.h>
using namespace std;
 
namespace chaos
{
	class string {
	public:
		/* 构造函数 */
		string(const char* str = "")
			: _size(strlen(str))        // 计算出字符串str的大小
			, _capacity(_size) {        // 初始容量等于字符串大小
 
			_str = new char[_capacity + 1];   // 开辟一块 "容量+1" 大小的空间 (_capacity存的是有效字符)
			strcpy(_str, str);                // 将传入的字符串str复制到 _str中
		}
 
		void Swap(string& tmp) {
			swap(_str, tmp._str);
			swap(_size, tmp._size);
			swap(_capacity, tmp._capacity);
		}
 
		/* 拷贝构造函数:s2(s1) 
		string(const string& src)
			: _size(src._size)                // 拷贝string大小
			, _capacity(src._capacity) {      // 拷贝string容量
			// 拷贝string内容
			_str = new char[src._capacity + 1];        // 开辟一块和src相同容量的空间
			strcpy(_str, src._str);					   // 将src中的_str内容拷贝到自己的_str中
		}
		*/
		string(const string& src)
			: _str(nullptr)
			, _size(0)
			, _capacity(0) {
 
			string tmp(src._str);   // 拷贝构造一个src
			Swap(tmp);              // 现代写法:交换
		}
	
 
		/* 赋值重载:s1 = s3
		string& operator=(const string& src) {
			// 防止自己跟自己赋值
			if (this != &src) {
				// 1. 暂时用tmp开辟一块相同的空间
				char* tmp = new char[src._capacity + 1];
				// 2. 把src的值复制给tmp
				strcpy(tmp, src._str);
				// 3. 释放this原空间
				delete[] _str;
				// 4. 没翻车,把tmp交付给_src
				_str = tmp;
				_size = src._size;
				_capacity = src._capacity;
			}
			return *this;
		}
		string& operator=(const string& src) {
			// 防止自己跟自己赋值
			if (this != &src) {
				string tmp(src);   // 复用拷贝构造
				Swap(tmp);
			}
			return *this;
		}
		*/
		string& operator=(string src) {
			Swap(src);    // 正好调用拷贝构造,不如让形参充当tmp
			return *this;
		}
 
 
		/* 返回C格式的字符串:c_str */
		const char* c_str() const {
			return _str;
		}
 
		/* 求字符串大小:size() */
		size_t size() const {
			return _size;
		}
 
		/* 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];
		}
 
		/* 迭代器 */
		typedef char* iterator;
		iterator begin() { 
			return _str;            // 返回第一个字符的位置
		}
		iterator end() {
			return _str + _size;    // 返回最后一个字符的位置
		}
 
		/* const迭代器 */
		typedef const char* const_iterator;
		const_iterator begin() const {
			return _str;
		}
		const_iterator end() const {
			return _str + _size;
		}
 
		/* reserve() */
		void reserve(size_t new_capacity) {
			if (new_capacity > _capacity) {               // 检查是否真的需要扩容
				char* tmp = new char[new_capacity + 1];   // 开空间
				strcpy(tmp, _str);						  // 先搬运数据到tmp
 
				_str = tmp;								  // 没翻车,递交给_str
				_capacity = new_capacity;				  // 更新容量
			}
		}
 
		/* 字符尾插:push_back() */
		void push_back(char append_ch) {
			/*
			if (_size == _capacity) {                         // 检查是否需要扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);  // 首次给4,其他情况默认扩2倍
			}
			_str[_size] = append_ch;     // 插入要追加的字符
			_size++;						 
			_str[_size] = '\0';	         // 手动添加'\0'
			*/
 
			insert(_size, append_ch);
		}
 
		/* 字符串追加:append() */
		void append(const char* append_str) {
			/*
			size_t len = strlen(append_str);      // 计算出要追加的字符串的长度
			if (_size + len > _capacity) {		  // 检查是否需要扩容
				reserve(_size + len);
			}
			strcpy(_str + _size, append_str);      // 首字符+大小,就是'\0'位置
			_size += len;						   // 更新大小
			*/
			
			insert(_size, append_str);
		}
 
		/* operator+= */
		string& operator+=(char append_ch) {
			push_back(append_ch);
			return *this;
		}
		string& operator+=(const char* append_str) {
			append(append_str);
			return *this;
		}
 
		/* insert */
		string& insert(size_t pos, char append_ch) {
			assert(pos <= _size);
 
			if (_size == _capacity) {		// 检查是否需要扩容
				reserve(_capacity == 0 ? 4 : _capacity * 2);
			}
 
			// 向后挪动数据
			size_t end = _size + 1;
			while (end > pos) {
				_str[end] = _str[end - 1];
				end--;
			}
 
			// 插入
			_str[pos] = append_ch;
			_size++;
 
			return *this;
		}
		string& insert(size_t pos, const char* append_str) {
			assert(pos <= _size);
			size_t len = strlen(append_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, append_str, len);
			_size += len;
 
			return *this;
		}
 
		/* resize */
		void resize(size_t new_capacity, char init_ch = '\0') {
			// 如果欲增容量比_size小
			if (new_capacity <= _size) {
				_str[new_capacity] = '\0';      // 拿斜杠零去截断
				_size = new_capacity;           // 更新大小
			}
			// 欲增容量比_size大
			else {
				if (new_capacity > _capacity) {
					reserve(new_capacity);
				}
				// 起始位置,初始化字符,初始化个数
				memset(_str + _size, init_ch, new_capacity - _size);
				_size = _capacity;
				_str[_size] = '\0';
			}
		}
 
		/* find */
		size_t find(char aim_ch) {
			for (size_t i = 0; i < _size; i++) {
				if (aim_ch == _str[i]) {
					// 找到了
					return i;    // 返回下标
				}
			}
			// 找不到
			return npos;
		}
		size_t find(const char* aim_str, size_t pos = 0) {
			const char* ptr = strstr(_str + pos, aim_str);
			if (ptr == nullptr) {
				return npos;
			}
			else {
				return ptr - _str;  // 减开头
			}
		}
 
		/* 删除:erase */
		string& erase(size_t pos, size_t len = npos) {
			assert(pos < _size);
 
			if (len == pos || pos + len >= _size) {
				_str[pos] = '\0';    // 放置\0截断
				_size = pos;
			}
			else {
				strcpy(_str + pos, _str + pos + len);
				_size -= len;
			}
 
			return *this;
		}
 
		/* 析构函数 */
		~string() {
			if (_str != nullptr) {
				delete[] _str;
				_str = nullptr;
			}
			_size = _capacity = 0;
		}
 
	private:
		/* 成员变量 */
		char* _str;
		size_t _size;
		size_t _capacity;
 
	public:
		static const size_t npos;
	};
 
	/* 初始化npos */
	const size_t string::npos = -1;   // 无符号整型的-1,即整型最大值
 
	/* s1 < s2*/
	bool operator<(const string& s1, const string& s2) {
		/*
		size_t i1 = 0, i2 = 0;
		while (i1 < s1.size() && i1 < s2.size()) {
			if (s1[i1] < s2[i2]) {
				return true;
			}
			else if (s1[i1] > s2[i2]) {
				return false;
			}
			else {
				i1++;
				i2++;
			}
		}
		return i2 < s2.size() ? true : false;
		*/
 
		return strcmp(s1.c_str(), s2.c_str()) < 0;
	}
 
	/* s1 == s2 */
	bool operator==(const string& s1, const string& s2) {
		return strcmp(s1.c_str(), s2.c_str()) == 0;
	}
 
	/* s1 <= s2 */
	bool operator<=(const string& s1, const string& s2) {
		return s1 < s2 || s1 == s2;
	}
 
	/* s1 > s2 */
	bool operator>(const string& s1, const string& s2) {
		return !(s1 <= s2);
	}
 
	/* s1 >= s2 */
	bool operator>=(const string& s1, const string& s2) {
		return !(s1 < s2);
	}
 
 
	/* s1 != s2 */
	bool operator!=(const string& s1, const string& s2) {
		return !(s1 == s2);
	}
 
	// cout << s1  →  operator<<(cout, s1)
	ostream& operator<<(ostream& out, const string& s) {
		/*
		for (auto ch : s) {
			out << ch;
		}
		*/
 
		for (size_t i = 0; i < s.size(); i++) {
			out << s[i];
		}
 
		return out;
	}
 
	// cin >>
	istream& operator<<(istream& in, string& s) {
		char ch = in.get();
		while (ch == '\n') {
			s += ch;
			ch = in.get();
		}
 
		return in;
	}
 
 
 
	/* 测试用 */
	void test_string1() {
		string s1("hello world");
		string s2(s1);
		cout << s1.c_str() << endl;
		cout << s2.c_str() << endl;
 
		string s3("pig");
		cout << s3.c_str() << endl;
 
		s1 = s3;
		cout << s1.c_str() << endl;
	}
 
	void test_string2() {
		string s1("hello world");
		string s2;
 
		for (size_t i = 0; i < s1.size(); i++) {
			cout << s1[i] << " ";
		}
		cout << endl;
	}
 
	void test_string3() {
		string s1("hello world");
		string s2;
 
		s1[0] = 'F';
		for (size_t i = 0; i < s1.size(); i++) {
			cout << s1[i] << " ";
		}
		cout << endl;
	}
 
	void test_string4() {
		string s1("hello world");
 
		// 迭代器写
		string::iterator it = s1.begin();
		while (it != s1.end()) {
			*it += 1;
			it++;
		}
 
		// 迭代器读
		it = s1.begin();   // 重置起点
		while (it != s1.end()) {
			cout << *it << " ";
			it++;
		}
	}
 
	void test_string5() {
		string s1("hello world");
		cout << s1.c_str() << endl;
 
		s1.push_back('!');
		cout << s1.c_str() << endl;
 
		s1.push_back('A');
		cout << s1.c_str() << endl;
	}
 
	void test_string6() {
		string s1("hello world");
		cout << s1.c_str() << endl;
 
		s1 += '!';
		cout << s1.c_str() << endl;
 
		s1 += "this is new data";
		cout << s1.c_str() << endl;
	}
 
	void test_string7() {
		string s1("hello world");
		cout << s1.c_str() << endl;
 
		s1.insert(0, 'X');
		cout << s1.c_str() << endl;
 
		s1.insert(0, "hahahaha");
		cout << s1.c_str() << endl;
	}
 
	void test_string8() {
		string s1("hello world");
		cout << s1.c_str() << endl;
 
		s1.erase(5, 2);   // 从第五个位置开始,删两个字符
		cout << s1.c_str() << endl;
 
		s1.erase(5, 20);  // 从第五个位置开始,删完
		cout << s1.c_str() << endl;
 
	}
}
 

终于写完了!!

 不是,这还拿不下你?

Ⅲ、Ⅳ、Ⅴ、Ⅵ、Ⅶ、Ⅷ、

本文含有隐藏内容,请 开通VIP 后查看