[TOC]
1.STL的介绍
STL,标准模板库,有惠普实验室开发的一系列标准化组件,目前是 C++ 的一部分,内建在编译器中。STL 一个重要特点是数据结构与算法的分离,这使得 STL 变得非常通用。
STL 具有高可重用性、高性能、高移植性、跨平台的优点。
STL 代码广义上分为三类:container(容器)、iterator(迭代器)和 algorithm(算法),容器和算法通过迭代器进行无缝连接。
2.容器概览
一个容器就是一些特定类型对象的集合,标准库中容器根据逻辑结构不同分为两大类,顺序容器和关联容器。
顺序容器主要有以下几种:
容器类 解释 特点 vector 可变大小数组 支持快速随机访问,在尾部之外的位置插入或删除元素可能较慢 deque 双端队列 支持快速随机访问,在头尾位置插入/删除速度较快 list 双向链表 只支持双向顺序访问,在任何位置插入/删除速度较快 forward_list 单向链表 只支持单向顺序访问,在任何位置插入/删除速度较快 array 固定大小数组 支持快速随机访问,不能添加/删除元素 string 与 vector 类似 专门用于保存字符,随机访问快,在尾部插入/删除快 顺序容器中,通常 vector 是最好的选择,除非有更好的理由选择其他容器。
关联容器中元素是按关键字来保存和访问的,分为两类,有序关联容器和无序关联容器。
其中有序关联容器主要有以下几种:
容器类 解释与特点 map 关联数组:保存关键字 – 值对 set 关键字即值,即只保存关键字的容器 multimap 关键字可重复出现的 map multset 关键字可重复出现的 set
3.vector 容器
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82// 1. 包含容器对应的头文件,并使用名称空间
using namespace std;
int main()
{
// 2. 因为是一个模板,所以需要在实例化时指定类型
// 初始化 10 个元素大小
vector<int> vec1(10);
// 初始化 10 个 2
vector<int> vec2(10, 2);
// 使用后面的值进行初始化
vector<int> vec3 = { 1, 2, 3, 4, 5 };
// 拷贝构造函数
vector<int> vec4(vec3);
// 3. 增加容器的元素
// 在末尾添加元素
vec3.push_back(10);
// 在开头位置添加元素
vec3.insert(vec3.begin(), 1);
// 4. 删除元素
// 删除结尾的元素
vec3.pop_back();
// 删除第二个元素
vec3.erase(vec3.begin() + 3);
// 5. 如何查看和修改元素
// 使用下标修改
vec3[0] = 100;
// 修改首元素的值
vec3.front() = 200;
// 修改尾元素的值
vec3.back() = 300;
// 获取对应位置的元素,但是会判断是否越界
vec3.at(1) = 400;
// 6. 遍历容器的几种方式
// 6.1. 常规遍历,用下标
// 这个方式一般只用于显示和修改元素数据(不增加或删除)
for (int i = 0; i < vec3.size(); ++i)
printf("%d ", vec3[i]);
printf("\n");
// 6.2. 常规遍历,用迭代器 auto 自动类型识别
//根据 begin 函数返回值类型推测变量类型
for (auto i = vec3.begin(); i != vec3.end(); ++i)
printf("%d ", *i);
printf("\n");
// 6.3. 使用列表循环,将 vec3 的所有元素依次赋值给 i
// 不添加引用就是值拷贝,不能修改原来的数据
// 这个方式一般只用于显示和修改元素数据(不增加或删除)
for (auto & i : vec3)
printf("%d ", i);
printf("\n");
// 6.4. 使用 while 的方式进行遍历
auto begin = vec3.begin();
while (begin != vec3.end())
{
printf("%d ", *begin);
++begin;
}
printf("\n");
// 在循环的过程中,不能随意的增加和删除元素
// 会导致迭代器无效,使程序崩溃
for (auto i = vec3.begin(); i != vec3.end(); ++i)
{
// 删除时需要接收返回的新迭代器
i = vec3.insert(vec3.begin(), 10);
}
// 1 2 3 4 5 6 7
// b e
// end 迭代器不能用于解引用
return 0;
}
其他方法
方法名 作用 vec.clear() 清空所有元素 vec.reserve() 改变容量大小 vec.resize() 改变元素个数 vec.rbegin() 返回一个反向 iterator,指向最尾端元素 vec.rend() 返回一个 iterator,指向第一个元素 …… ……
4. list 容器
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60// 002_容器的使用_list.cpp
using namespace std;
int main()
{
list<int> listNum;
// 增
listNum.push_back(100);
listNum.push_back(300);
listNum.insert(listNum.begin(), 200);
listNum.push_front(0);
// **注意** : list迭代器只能使用++,或--, 不能使用+运算符
// 删
// 只能用迭代器, 但如果要删除第2个元素
list<int>::iterator itr = listNum.begin();
// itr = itr + 3;list迭代器只能使用++,或--, 不能使用+运算符
++++itr; // 自增2次指向第2个元素
// 所有容器的erase都会删除迭代器指向的元素
// 然后删除之后itr指向就无效了, 因此就变成了一个无效迭代器
// erase会通过返回值返回一个有效的迭代器(一般是删除位置的下一个元素的迭代器)
itr = listNum.erase(itr);
// 改
// 只能通过迭代器去修改
// 例如:修改第1个元素
itr = listNum.begin();
cout << "第1个元素: " << *itr << endl;
*itr = 111111;
cout << "第1个元素: " << *itr << endl;
// 例如:修改第2个元素
++itr; // 自增找到第二个元素
cout << "第2个元素: " << *itr << endl;
*itr = 111111;
cout << "第2个元素: " << *itr << endl;
// 查
// 通过迭代器来遍历
for (auto i = listNum.begin(); i != listNum.end(); ++i) {
cout << *i << ',';
}
cout << endl;
// 通过新式循环来遍历
for (auto& d : listNum) {
cout << d << ',';
}
cout << endl;
//访问元素
//分别取出第一个元素和最后一个元素
cout << listNum.front() << " " << listNum.back() << endl;
//获取元素个数
cout << listNum.size() << endl;
return 0;
}
5. map 容器
使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95// 003_容器的基本使用_map.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
using namespace std;
int main()
{
// 什么是键值对?
// 键 : 相当于一个索引.相当于目录中的一下.
// 值 : 就是一堆真正要保存的数据. 键相当于值的摘要.用于代表这个值.
// 例如 , 姓名:小明. 姓名:小红, 其中姓名就是键,小明就是值
// 年龄: 18 , 年龄就是键,18就是值.
// 4544545 : 小名
// 4544545就是键,小名值.
// 总是用键来索引值.
// <string, string>
// 第一个string指的是键的类型
// 第二个string指的是值的类型
// 当需要将数据保存到map容器中时,
// 保存的数据的键值对必须都是string类型.
map<string, string> m1;
map<int, string> m2;
// 增
// pair是一个键值对类
// map就是用于保存键值对类的平衡二叉树.
// map是一个平衡二叉树(红黑树)
// 树中节点的数据类型是 std::pair<typename,typename>
// 插入数据到map中的时候, 必须先给出键值对来初始化一个pair对象
// 然后再pair对象插入到map中.
pair<string, string> p1("姓名", "小明");
m1.insert(p1);
//p1是一个键值对对象
cout << "p1.first=" << p1.first << endl; // 键值对中的键
cout << "p1.second=" << p1.second << endl;// 键值对中的值
// 也可以直接在实参中调用pair的构造函数来构造一个pair对象
m1.insert(pair<string, string>("年龄", "18"));
// 第三种方法,直接使用`[]`运算符来插入
// 实际上[]运算符会在二叉树中以"性别"作为键来搜索pair对象
// 如果没有搜索到, 函数会自动在树中插入一个用"性别"作为键
// 的pair对象, 然后再将新pair对象值赋值成"男"
m1["性别"] = "男";
// m1["姓名"]运算符会在树中以"姓名"作为键找到对应的pair
// 对象, 然后将pair对象的值返回.
string str = m1["姓名"]; // str被赋值成"小明"
str = m1["aaa"]; // 如果获取一个不存在的键的时候,就会插入一个,值有可能会随机(取决于值的类型有无构造函数)
// 删
// 直接传入一个键就行了. 对应的键值对就会被删除.
m1.erase("姓名");
// 改
cout << "性别:" << m1["性别"] << endl;
m1["性别"] = "女";
cout << "性别:" << m1["性别"] << endl;
// 查
// map保存的节点是pair对象
// 所以map的迭代器指向的元素都是pair对象.
//
cout << "遍历map\n";
for (auto itr = m1.begin();
itr != m1.end();
++itr)
{
itr->first; //(*itr).first
cout << itr->first << "="
<< itr->second << endl;
}
// <int, string>
// 第一个int指的是键的类型
// 第二个string指的是值的类型
m2.insert(pair<int, string>(1, "123456"));
m2.insert(pair<int, string>(2, "456789"));
m2[100] = "1111";
cout << "遍历map\n";
for (auto itr = m2.begin();
itr != m2.end();
++itr)
{
itr->first; //(*itr).first
cout << itr->first << "="
<< itr->second << endl;
}
return 0;
}