一.STL简介
STL(standard template libaray- 标准模板库 ) : 是 C++ 标准库的重要组成部分 ,不仅是一个可复用的
组件库,而且 是一个包罗数据结构与算法的软件框架 。
STL包含六大组成部分,如下图所示:

其中最主要的部分为容器和算法。容器可以理解为之前C语言中的各种数据结构,算法就是对这些数据的处理方法,包含常见的例如交换、查找、排序等。
接下来的博客,将会按照:基本容器的使用方法+容器的基本底层实现来展开。
容器的使用将围绕 cplusplus.com - The C++ Resources Network这个网址中的容器函数来展开,会介绍其最基本的类的默认成员函数、容器的增删查改等
二.string的使用
string类,可以理解为是一个存放字符串的数组。
2.1string的构造函数(包括拷贝构造)

如上图所示,string的构造函数有7种。当前阶段重点应用到前6种。
cpp
//string的几种构造
string s1;//不含参数
string s2("Hello world");//常量字符串构造
string s3("Hello world", 5);//用常量字符串的前n个字符构造
const char* str = "Hello world";
string s4(str, 5);//与上面相同,常量字符串构造
string s5(10, 'c');//n个单字符构造
//拷贝构造
string s6(s2);
string s7(s2, 1, 5);//从下标1开始,到下标5结束的位置构造
string s8(s2, 1);//最后一个参数缺省默认为npos,默认取到最后
1.不含参数的默认构造
2.利用常量字符串的构造
3.利用常量字符串的前n个字符的构造
4.利用n个单字符构造
5.拷贝构造
6.利用某个下标段的位置拷贝构造,如果超过最后一个字符的下标,也是取到最后一个字符
7.如果最后一个参数缺省,那么默认取到最后一个字符的位置,缺省参数为npos(size_t -1)。
2.2赋值运算符重载
cpp
//赋值运算符重载
s8 = s3;
s8 = str;
s8 = 'x';
赋值运算符重载的三种类型:
1.两个string对象直接赋值
2.常量字符串的赋值
3.单字符的赋值
2.3string类的几种遍历方式
2.3.1 \[\]运算符重载
\[\]重载了两种,第二种主要目的是为了针对const string类型的对象实现取下标。
第一种返回类型为char&,表示其可支持\[\]取出元素的修改。
第二种返回类型为const char& ,仅可取出,不可修改。
cpp
string s1("Hello world");
cout << s1<<endl;//cout运算符重载,可以直接输出string类对象
//[]运算符重载,可以进行修改
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
++s1[i];
}
cout << endl;
for (size_t i = 0; i < s1.size(); i++)
{
cout << s1[i] << " ";
}
cout << endl;
const string s2("hello world!");
for (size_t i = 0; i < s1.size(); i++)
{
cout << s2[i] << " ";
//++s2[i];//s2为const的string类型对象,会调用const的[]运算符重载,返回类型为const char&,不能进行修改
}
cout << endl;
\[\]重载后的调用和基本使用方法与数组的\[\]下标类似。值得注意的是,由于非const对象的返回值类型为引用,所以可对\[\]取出的值进行修改。const string类型的返回值为const char&类型,不可进行修改。这一点需要格外注意。
2.3.2 迭代器遍历
迭代器在此处不做详细讲解,先说明其基本用法,后续具体实现的过程中再具体讲解。
基本用法如下:
cpp
//迭代器进行遍历
string s3("Hello world");
string::iterator it1 = s3.begin();
//可以看作类似于指针的用法
while (it1 != s3.end())
{
cout << *it1 << " ";
it1++;
}
cout << endl;
//迭代器也可对其进行修改
it1 = s3.begin();
while (it1 != s3.end())
{
cout << ++(*it1) << " ";
it1++;
}
cout << endl;
迭代器的使用:string::iterator it = s1.begin();需要指明迭代器所属的类,然后再调用string类中的begin函数返回一个迭代器对象并赋值给it。
在使用迭代器的时候,一版通过*it来实现取出类中的元素,所以可以将其理解为类似于指针一样的东西。不同的容器对于迭代器的调用方式相似,但其底层实现并不相同。从这点也能看出迭代器的几大优点:
迭代器的意义:
1:统一类似的方式遍历修改容器
2:算法脱离具体底层结构,和底层结构解耦,
3:算法独立模板实现针对多个容器处理
迭代器是类似于指针的用法,*迭代器表示取到该对象的内容。如果针对于const对象,则需要利用const_iterator,注意写法。
cpp
//普通对象用普通迭代器iterator
//const对象用const迭代器 const_iterator
string s1("hello world");
const string s2(s1);
string::iterator it1 = s1.begin();
string::const_iterator it2 = s2.begin();
//string::iterator it3 = s2.begin();//err
普通对象用普通迭代器,const对象用const迭代器。const_iterator本质是不能修改内容,而非不能修改迭代器本身。
反向遍历就需要用到反向迭代器,reverse_itetator
cpp
//如果想反向遍历,用反向迭代器
string::reverse_iterator rit1 = s1.rbegin();
while (rit1 != s1.rend())
{
cout << *rit1 << " ";
rit1++;
}
cout << endl;
2.3.3 范围for
在讲解范围for之前,先说明一个C++中引入的新语法,auto。
auto可以自动识别类型,如下所示:
cpp
//auto类型
//可自动识别类型
auto x = 1;
auto y = 1.1;
auto z = 'c';
//如果是地址,下面这两种写法均可
auto a = &x;
auto* pa = &x;
//如果是引用,需要是auto&
auto& rx = x;
auto自动识别类型的过程中,要注意对于指针和引用的方式。下面说明范围for的使用方式:
cpp
//范围for
//范围for本质是将其转换为*it。
string s4("hello world");
for (auto e : s4)
{
cout << e << " ";
}
cout << endl;
// 自动取容器数据赋值给e
// 自动判断结束
// 自动迭代
for (auto& e1 : s4)
{
e1++;
}
cout << endl;
使用方式:auto e(可以自由起的一个名字) : s4(类对象)
范围for的本质是将e 转换为迭代器的 *it 来实现。要注意其本质是一个拷贝出来的临时对象,对它进行修改并不能影响原string对象中的内容。
如果要通过范围for对其进行修改,需要引用类型。
总结范围for的优点:
自动取容器数据赋值给e
自动判断结束
自动迭代
2.4string类的一些常用函数
2.4.1查询容量相关函数,size,capacity等
cpp
string s1("hello world");
cout << s1.size()<<endl;
cout << s1.length() << endl;
cout << s1.max_size() << endl;//理论值,一般用不到
cout << s1.capacity() << endl;
string s2("123456");
int s2_capacity = s2.capacity();
cout << "s2_capacity = " << s2_capacity << endl;
//除了第一次是二倍扩容,往后几乎是1.5倍的扩容
for (size_t i = 0; i < 100; i++)
{
s2.push_back(i);
if (s2.capacity() != s2_capacity)
{
s2_capacity = s2.capacity();
cout << "New Capacity = " << s2_capacity << endl;
}
}
cout << endl;
s2.clear();//清空内容
cout << s2 << endl;
cout << s2.size() << endl;
cout << s2.capacity() << endl;
s2.shrink_to_fit();//缩小容量,不具有约束力,一般不用。
cout << s2.capacity() << endl;
size函数,返回当前string对象有多少字符。capacity函数返回当前string对象的容量有多大。
当string中的存储空间不够时,就会继续申请新的空间,申请方式为除了第一次为二倍扩容,其余均为1.5倍扩容。
clear函数用来清空当前string中的内容,但并不改变容量大小。
shirink_to_fit函数,主要目的时缩小容量。不过缩小容量的操作效率低下,且容易影响数组中的存储内容,一般不用。运行结果如下图所示:

2.4.2reserve函数
reserve函数主要目的是用来改变capacity的容量大小。主要用法为提前开辟空间,尽可能减少string对象由于增加字符而导致的频繁扩容
cpp
string s1;
s1.reserve(100);//直接开辟100个字符的空间
cout << s1.capacity() << endl;
for (size_t i = 0; i < 100; i++)
{
s1.push_back(i);
}
s1.reserve(50);//如果尝试缩小容量,那么也是不具有约束力的。况且如果真要缩小,用shrink_to_fit
cout << s1.capacity() << endl;
同样的,如果尝试用reserve来减少容量,其也是不具有约束力的,尽可能不用。
2.4.3resize函数
resize函数是用来改变string对象内部数据多少。
改变string对象的数据长度,会有三种情况:
cpp
string s1("123456");
cout << s1.size() << endl;
//resize函数,可以改变string类中存储数据的内容
//如果要变小,回去出一些元素直到resize中所写的内容。
//如果要变大,则会补充单字符
//第二个参数缺省,则会默认补充\0
s1.resize(5);
cout << s1 << endl;
s1.resize(10, 'x');//如果第二个字符缺省,那么会自动补充\0.
cout << s1 << endl;
1.当resize后的长度小于原长度时,就会强制去除一些数据,缩小到要resize的长度。
- 当resize后的长度大于原长度时,如果resize函数的第二个参数明确写了需要补充什么单字符,那么就会在不足的长度中补充该字符。
3.如果resize的第二个参数缺省,那么就会补充\0直到规定长度。

2.4.4operator +=和append函数
+=运算符的重载本质和append函数的功能相同,都是在string对象后添加字符。

可以+=一个string类对象,也可以+=一个常量字符串,同时可以+=一个单字符。

append功能类似,仅仅多了可以规定长度的功能。
列表中的第二个表示为在string类对象中添加的类对象,指定添加的部分下标,包括该下标之后的sublen个字符。
列表中的第四个表示为,添加该常量字符串中的前n个字符
第五个表示为添加n个单字符到该对象中。
2.4.5 insert和erase函数

insert函数主要功能为在指定位置插入字符/字符串。

erase函数主要为删除指定下标之后多少长度的字符
主要用法示例如下:
cpp
string s1("123456");
s1.insert(1, "xxx");
cout << s1 << endl;
s1.insert(0, 1, 'a');
cout << s1 << endl;
string s2("hello world");
s1.insert(3, s2,5,10);//第三个参数为下标,最后一个参数为拷贝的长度
cout << s1 << endl;
s1.erase(0, 5);
cout << s1 << endl;

2.4.6assign和replace函数(以及find)

' assign函数是将当前string对象的内容指定为另一内容,其形式具体包括:1.指定为另一个string对象;2.指定为具体多少长度的另一个string对象;3.指定为某一常量字符串;4.指定为某一长度的常量字符串;5.指定为n个单字符
replace函数主要是将给定下标位置后的1个或几个字符替换为给定字符,主要搭配find使用

find用来寻找string类对象内某个string类对象,或者某个常量字符串/常量字符,具体用法如下例程序:
这个程序主要实现了将空格替换为两个百分号的功能。
cpp
string s1("hello world");
size_t pos = s1.find(' ');
while (pos != string::npos)
{
s1.replace(pos,1, "%%");
pos = s1.find(' ', pos + 2);
}
cout << s1 << endl;
string s2("hello world");//如果继续用上面这种算法,效率就会很低
string s3;
string::iterator it2 = s2.begin();
while (it2 != s2.end())
{
if (*it2 == ' ')
s3 += "%%";
else
s3 += *it2;
it2++;
}
cout << s3 << endl;
第一种思路为利用find函数寻找空格位置,并进行替换,但这种方法效率较低,因为涉及到数组数据前移问题。
第二种思路为创建一个新的string对象,,遍历原string对象,如果是空格,那就在新的string对象中添加两个百分号;如果是字符,那就直接添加。
2.4.7substr函数

substr函数为在该string对象内部,在规定的下标和字符串长度后,找出并返回该字串,并构建一个string对象。其搭配find和rfind函数能写出一些小程序:
cpp
void test10()
{
string s1("hello world");
//substr用来分割一个string对象的子串,并返回一个string对象
string s2 = s1.substr(0, 5);//第一个表示下标,第二个表示要多少长度
cout << s2 << endl;
//利用substr和find就可以写一个基础的分割网址的小程序。
}
void FindSubfix(const string& s)
{
size_t pos1 = s.find('/');
if (pos1 != string::npos)
{
cout << s.substr(0, pos1) << endl;;
size_t pos2 = s.find( '/',pos1+2);
cout << s.substr(pos1 + 2, pos2 - pos1 - 2) << endl;
cout << s.substr(pos2 + 1, s.size() - pos2 - 1) << endl;
}
else
{
cout << "The URL is wrong!" << endl;
exit(EXIT_FAILURE);
}
}
void test11()
{
string s1("https://legacy.cplusplus.com/reference/string/string/substr/");
string s2("https://www.bilibili.com/video/BV1Vm4y1r7jY/");
FindSubfix(s1);
FindSubfix(s2);
}
//也可以写一个识别后缀的小程序
void searchfix(const string& s)
{
size_t pos = s.rfind('.');//rfind为从后向前找
if (pos != string::npos)
{
cout << s.substr(pos) << endl;
}
else
{
cout<<"The file have not the fix.";
}
}
void test12()
{
string s1("test.cpp");
string s2("abcd.zip");
string s3("abcd,ziop.7z.rar");
searchfix(s1);
searchfix(s2);
searchfix(s3);
}
上面代码的test11()实现了一个基础的分割网址的功能,将其分为协议、域名以及之后的部分内容。实现思路为:先利用find函数找出第一个 '/',就可以用substr函数分割出协议。之后以该下标为基础,向后继续寻找 ' / ',就可以找出第二段内容。利用该下标和npos,即可分割出最后的内容。
test12()的分割后缀,也是类似的思路:通过rfind从后向前寻找 '.',以该下标为第一个参数,利用默认参数npos即可分割出后缀。
2.4.8返回 const char*的C语言接口

该函数的主要目的是返回一个指向字符串数组内容的const char* 类型的指针。利用该函数可以对接一些C语言的函数。
cpp
void test13()
{
//c_str函数用来返回针对于C语言应用的接口
string file ( "string的基本使用.cpp");
FILE* FILENAME = fopen(file.c_str(), "r");
char ch = getc(FILENAME);
while (ch!= EOF)
{
cout << ch;
ch = getc(FILENAME);
}
}
例如该段代码,fopen函数第一个参数需要传递一个char*的指针,就可以利用c_str()函数来实现该功能。这段代码实现了读取并打印出这个cpp文件内的字符内容。
2.4.9find_first_of等函数

以find_first_of为例

该函数主要实现了在类对象中寻找任何(所有)指定的内容。如下端代码所示:
cpp
void test14()
{
string s1("abcerfsasdwanjklwajdioanbkjwdnjkasda");
string s2("abcerfsasdwanjklwajdioanbkjwdnjkasda");
size_t pos1 = s1.find_first_of("aeiou");
//size_t pos = s1.find_last_of("aeiou");//从后向前找
size_t pos2 = s2.find_first_not_of("aeiou");
while (pos1 != string::npos)
{
s1[pos1] = ' ';
pos1 = s1.find_first_of("aeiou",pos1+1);
}
while (pos2 != string::npos)
{
s2[pos2] = ' ';
pos2 = s2.find_first_of("aeiou", pos2 + 1);
}
cout << s1 << endl;
cout << s2 << endl;
}
s1和s2为两个内容相同的对象。s1利用find_first_of函数找出其内容中所有的aeiou,并将其替换为空格。s2调用find_first_not_of函数,找出s2中所有不是aeiou的字符,并将其替换为空格。
这涵盖了string类一些常用的函数,下一篇博客将带来一个简单的string类的底层实现