前言:在刷 PAT 乙级题目的过程中,深切感受到了 C++ 标准模板库(STL) 在编写代码中的方便。因此,想先记录一下该应用。当然,若只要求刷题的话,没有必要去过分深究 STL 的源码分析。若想去了解 STL 的实现过程,可以去参考侯捷老师的相关视频、著作。在这,只列举了比较常用的三种容器:vector、map、set。
1. 简述
C++ STL(标准模板库)提供了通用的模板类和函数,在这些类和函数之中,集成可多种常用的算法和数据结构。其主要由容器(container)、迭代器(iterator)、算法(algorithm)这三个部分组成。
容器:用来管理某一类对象的集合。其中包含了大部分的数据结构,可以直接应用该函数实现相关功能。
迭代器: 提供了访问容器中对象的方法。
算法: 用来操作容器中的模板函数。包括了常用的 快排(sort)、查找(find)、插入(inser)、末尾添加(push_back)、末尾删除(pop_back)等。
命名空间:其使用相同的命名空间 using namespace std;
2. vector
0、头文件:#include <vector>
1、定义:向量。也可以理解成 “变长数组”,即长度依据需要而自行发生更改的数组,可以有效的解决数组溢出问题。
2、变量声明:vector<typedef> name;
(1)、说明:其中 typedef
可以是任何基本的数据类型,例如int、double、char
等;也可以是结构体类型,例如 vector<stuInfo> v
(其中 stuInfo
是结构体变量);也可以是 vector
容器本身,例如:vector<vector<int> >
注:在符号 > >
之间要添加空格,否则会将其视为移位操作,造成编译错误。
(2)、几种声明方式的区别:
vector<int> v; // 声明了一个 int 类型的 vector 容器
vector<int> v(n); // 声明了一个 int 类型、初始长度为 n 的 vector 容器,可以直接进行下标访问
vector<int> v(n, t); // 声明了一个 int 类型、初始长度为 n ,数值为 t 的 vector 容器,可以直接进行下标访问
vector<int> v[n]; // 声明了一个 二维 、且其中一组长度已经固定的 int 类型 vector 容器
vector<vector<int> > v; // 声明了一个 二维、两组长度均可以进行更改的 int 类型 vector 容器
3、元素的访问:一般有两种方式进行访问,通过下标进行访问、或通过迭代器进行访问。
(1)、通过下标访问。和访问普通数组的方式一致,直接访问 v[index]
即可,当然,该访问方式不能超过元素的范围。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v(10, -1);
for(int i=0;i<10;i++) {
printf("%d ",v[i]);
}
return 0;
}
(2)、通过迭代器的方式进行访问。迭代器(iterator) 可以理解为一种类似于指针的东西,其定义为:
vector<int>::iterator it = v.begin(); // 自己定义
auto it = v.begin(); // 或者通过编译器让其自动识别类型
迭代器访问的方式,分为正向迭代器输出、反向迭代器输出。容器范围的表示方式为 [v.begin, v.end)
以左闭右开的方式进行范围限制。例如:
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
// 进行向尾部插值操作
for(int i=0;i<10;i++)
v.push_back(i);
printf("\n正向迭代器输出:");
for(auto it=v.begin();it!=v.end();it++)
printf("%d ",*it);
printf("\n反向迭代器输出:");
for(auto rit=v.rbegin();rit!=v.rend();rit++)
printf("%d ",*rit);
return 0;
}
4、刷题中常用函数:
(1)、push_back(x)
- 向尾部添加一个元素 x
。
(2)、empty()
- 判断容器是否为空。
(3)、size()
- 获取容器的大小。(可以理解为容器中元素的个数)
(4)、clear()
- 清空容器中的所有元素。
(5)、pop_back()
- 删除尾部元素。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
vector<int> v;
// 进行向尾部插值操作
for(int i=0;i<10;i++)
v.push_back(i);
// 获取容器的长度
int len = v.size();
printf("通过下标正序输出:");
for(int i=0;i<len;i++)
printf("%d ",v[i]);
// 判断容器是否为空
if( v.empty() ) printf("\n当前容器为空!");
else printf("\n当前容器不为空!");
// 进行向尾部删除操作
for(int i=0;i<5;i++)
v.pop_back();
printf("\n反向迭代器输出:");
for(auto rit=v.rbegin();rit!=v.rend();rit++)
printf("%d ",*rit);
// 清空容器中的值
v.clear();
if( v.empty() ) printf("\n当前容器为空!");
else printf("\n当前容器不为空!");
return 0;
}
3. map
0、头文件:#include <map> / #include <unordered_map>
1、定义:映射。一种关联容器,可以实现 Key - Value
的键值匹配。
(1)、数组,就是一种映射,由 int
到 基本数据类型 int、char、double...
的映射,例如:int arr[4] = {1, 2, 3, 4};
其就是由 int
类型到 int
类型的映射。下标始终对应着 int
类型,其中0、1、2、3...
下标值,就对应着映射中的 Key
,而数组中存储的值 1, 2, 3, 4
,就对应着映射中的 Value
。而数组中的 Key
必须是 int
类型,而当 Key
是字符串等非整数类型的时候,其数组就不在适用。例如当存储学生成绩的时候,其键值对应为 name - score
,其中 name
的数据类型在通常情况下应当为 string
数据类型,这时就无法通过直接使用数组实现。因此,提出了 map
这种关联容器,从而实现任意的键值匹配。
(2)、map
可以将任何基本数据类型(包括 STL
容器)映射到任何基本数据类型(包括 STL
容器),如 string
到 int
类型的映射。
(3)、map
的本身是一种有序映射,其内部会实现自动排序的过程,当在进行类型定义的时候,若只需要实现映射即可,不需要按键排序的时候,可以使用 unordered_map
无序映射,其相比于有序映射而言,其查找的速度要更快一些,可节省些时间。
2、变量声明:map<typedef1, typedef2> mp;
说明:其中 typedef1、typedef2
可以是任何基本的数据类型,例如int、double、char
等;也可以是 STL
容器本身,例如:map<int, vector<int> >;
注:在符号 > >
之间要添加空格,否则会将其视为移位操作,造成编译错误。
3、元素的访问:
(1)、通过下标访问。和访问普通数组的方式一致,直接使用 mp[index]
的形式即可。注:当访问没有定义的键时,其不会出现报错,而是会自动生成该 Key,并且 Value = 0。例如:
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<char, int> mp;
mp['c'] = 10;
printf("c 的值为:%d\n",mp['c']);
// 在出现值更改的时候,其会进行覆盖
mp['c'] = 20;
printf("c 的值为:%d\n",mp['c']);
// 当访问没有定义的键时,其不会出现报错,而是会自动生成该 Key,并且 Value = 0
printf("a 的值为:%d\n",mp['a']);
return 0;
}
运行结果如下所示:
c 的值为:10
c 的值为:20
a 的值为:0
(2)、通过迭代器的方式进行访问。
#include <iostream>
#include <map>
using namespace std;
int main()
{
map<char, int> mp;
mp['m'] = 20;
mp['a'] = 30;
mp['p'] = 10;
// 正向迭代:
printf("正向迭代输出:\n");
for(auto it=mp.begin();it!=mp.end();it++)
printf("Key - %c, Value - %d\n",it->first, it->second);
// 反向迭代:
printf("反向迭代输出:\n");
for(auto rit=mp.rbegin();rit!=mp.rend();rit++)
printf("Key - %c, Value - %d\n",rit->first, rit->second);
return 0;
}
运行结果如下所示:
正向迭代输出:
Key - a, Value - 30
Key - m, Value - 20
Key - p, Value - 10
反向迭代输出:
Key - p, Value - 10
Key - m, Value - 20
Key - a, Value - 30
说明:从其输出结果之中,可以看出 map
会按 Key
从小到大的顺序进行自动排序。
(3)、当需要进行 一键多值 Key - Values
映射的时候,可以将 Value
的数据类型由基本数据类型,更改为 vector
容器,较为方便操作。也可以利用结构体的方式,将对应的 Values
结构体化。例如:
#include <iostream>
#include <map>
#include <vector>
#include <string>
using namespace std;
typedef struct {
int id, ser;
}stuInfo;
int main()
{
// 一、通过容器的方式进行 一键多值 的映射
printf("通过容器的方式:");
map<char, vector<int> > mp;
for(int i=0;i<3;i++)
mp['a'].push_back(i);
// 方式一:特定值的查找、输出
printf("\n\n通过下标方式输出:");
for(int i=0;i<mp['a'].size();i++) {
printf("%d ",mp['a'][i]);
}
// 方式二:通过迭代器的方式进行输出
printf("\n通过迭代器的方式输出:");
for(auto it=mp.begin();it!=mp.end();it++)
for(int i=0;i<it->second.size();i++)
printf("%d ",it->second[i]);
// 二、通过结构体方式,进行存储
printf("\n\n通过结构体方式:");
map<string, stuInfo> stu;
stu["李明"] = stuInfo{1001, 98};
stu["汤姆"] = stuInfo{1002, 86};
// 方式一:特定值的查找、输出
printf("\n\n通过下标方式输出:");
printf("\n李明的学号:%d、成绩:%d",stu["李明"].id, stu["李明"].ser);
printf("\n汤姆的学号:%d、成绩:%d",stu["汤姆"].id, stu["汤姆"].ser);
// 方式二:通过迭代器的方式进行输出
printf("\n\n通过迭代器的方式输出:");
for(auto it=stu.begin();it!=stu.end();it++)
printf("\n%s的学号:%d、成绩:%d",it->first.c_str(), it->second.id, it->second.ser);
return 0;
}
运行结果如下所示:
通过容器的方式:
通过下标方式输出:0 1 2
通过迭代器的方式输出:0 1 2
通过结构体方式:
通过下标方式输出:
李明的学号:1001、成绩:98
汤姆的学号:1002、成绩:86
通过迭代器的方式输出:
李明的学号:1001、成绩:98
汤姆的学号:1002、成绩:86
4、刷题中常用函数:
(1)、size()
- 获取 map
映射的对数。
(2)、clear()
- 清空 map
中的所有元素。
(3)、find()
- 查找 map
中是否存在键为 Key
的映射。
#include <iostream>
#include <map>
#include <vector>
#include <string>
using namespace std;
typedef struct {
int id, ser;
}stuInfo;
int main()
{
map<string, stuInfo> stu;
stu["李明"] = stuInfo{1001, 98};
stu["汤姆"] = stuInfo{1002, 86};
printf("当前已存储学生信息人数:%d\n",stu.size());
auto it = stu.find("李明"); // 返回映射的迭代器
if(it != stu.end()) // 当不为 end 的时候,说明该映射中存在该键
printf("%s的学号:%d、成绩:%d\n",it->first.c_str(), it->second.id, it->second.ser);
else // 否则,不存在该键
printf("不存在该学生!\n");
stu.clear();
printf("当前已存储学生信息人数:%d\n",stu.size());
it = stu.find("李明"); // 返回映射的迭代器
if(it != stu.end()) // 当不为 end 的时候,说明该映射中存在该键
printf("%s的学号:%d、成绩:%d\n",it->first.c_str(), it->second.id, it->second.ser);
else // 否则,不存在该键
printf("不存在该学生!\n");
return 0;
}
运行结果如下所示:
当前已存储学生信息人数:2
李明的学号:1001、成绩:98
当前已存储学生信息人数:0
不存在该学生!
4. set
0、头文件:#include<set>
1、定义:集合。也是一种关联容器,是一个内部自动有序且不含重复元素的容器。其适用于需要去除重复元素的情况。
2、变量声明:set<typedef> name;
说明:其中 typedef
的类型和绝大部分容器一致,其可以是任何基本数据类型,也可以是 STL
容器,而一般为 int
基本数据类型,用于整形数据结果值的去重操作。
3、元素的访问:
(1)、在 set
容器中, 只能使用迭代器的方式进行访问。其迭代器的访问方式,和上述情况一致。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
for(int i=0;i<5;i++) {
// 向容器中插入值
st.insert(1);
}
printf("容器大小为:%d\n",st.size());
printf("迭代输出:");
for(auto it=st.begin();it!=st.end();it++)
printf("%d ",*it);
for(int i=0;i<5;i++) {
// 向容器中插入值
st.insert(i);
}
printf("\n容器大小为:%d\n",st.size());
printf("迭代输出:");
for(auto it=st.begin();it!=st.end();it++)
printf("%d ",*it);
return 0;
}
运行结果:
容器大小为:1
迭代输出:1
容器大小为:5
迭代输出:0 1 2 3 4
4、刷题中常用函数:
(1)、insert(x)
- 将 x
插入到 set
容器中,并自动递增排序和去重。
(2)、find()
- 查找对应值。
#include <iostream>
#include <set>
using namespace std;
int main()
{
set<int> st;
for(int i=0;i<5;i++) {
// 向容器中插入值
st.insert(i);
}
auto it=st.find(6); // 返回值类型:迭代器
if( it!=st.end() )
printf("该容器中存在该值!\n");
else
printf("该容器中不存在该值!\n");
return 0;
}
运行结果:
该容器中不存在该值!