背景
下周数据结构实验就要开始考试了,所以总结一点C++的标准模板库来学习,希望下周考试能轻松度过
(〃’▽’〃)(〃’▽’〃)(〃’▽’〃)
1 什么是标准模板库(STL)?
1.1 C++标准模板库与C++标准库的关系
C++标准模板库其实属于C++标准库的一部分,C++标准模板库主要是定义了标准模板的定义与声明,而这些模板主要都是类模板,我们可以调用这些模板来定义一个具体的类;与之前的自己手动创建一个函数模版或者是类模板不一样,我们使用STL就不用自己来创建模板了,这些模板都定义在标准模板库中,我们只需要学会怎么使用这些类模板来定义一个具体的类,然后能够使用类提供的各种方法来处理数据。
1.2 STL六大组件
容器(containers)、算法(algorithms)、迭代器(iterators)、函数对象(functors)、适配器(adapters)、分配器(allocators)
2 容器分类
STL对定义的通用容器分三类:顺序性容器、关联式容器和容器适配器。
- 顺序性容器:vector、deque、list
- 关联性容器:set、multiset、map、multimap
- 容器适配器:stack、queue
2.1 vector 容器
vector向量是一种顺序性容器。相当于数组,但其大小可以不预先指定,并且自动扩展。它可以像数组一样被操作,由于它的特性我们完全可以将vector 看作动态数组。
在创建一个vector后,它会自动在内存中分配一块连续的内存空间进行数据存储,初始的空间大小可以预先指定也可以由vector 默认指定。当存储的数据超过分配的空间时,vector会重新分配一块内存块,但这样的分配是很耗时的,在重新分配空间时它会做这样的动作:
- 首先,vector 会申请一块更大的内存块;
- 然后,将原来的数据拷贝到新的内存块中;
- 其次,销毁掉原内存块中的对象(调用对象的析构函数);
- 最后,将原来的内存空间释放掉。
当vector保存的数据量很大时,如果此时进行插入数据导致需要更大的空间来存放这些数据量,那么将会大大的影响程序运行的效率,所以我们应该合理的使用vector。
2.1.1 初始化vector对象的方式:
1 2 3 4
| vector<T> v1; // 默认的初始化方式,内容为空 vector<T> v2(v1); // v2是v1的一个副本 vector<T> v3(n, i) // v3中包含了n个数值为i的元素 vector<T> v4(n); // v4中包含了n个元素,每个元素的值都是0
|
2.1.2 vector常用函数
empty():判断向量是否为空,为空返回真,否则为假
begin():返回向量(数组)的首元素地址
end(): 返回向量(数组)的末元素的下一个元素的地址
clear():清空向量
front():返回得到向量的第一个元素的数据
back():返回得到向量的最后一个元素的数据
size():返回得到向量中元素的个数
push_back(数据):将数据插入到向量的尾部
pop_back():删除向量尾部的数据
2.1.3 遍历方式
vector向量支持两种方式遍历,因为可以认为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
| #include <iostream> #include <vector> using namespace std; int main(void) { vector<int> vec;
vec.push_back(1); vec.push_back(2); vec.push_back(3); vec.push_back(4); vec.push_back(5); cout << "向量的大小:" << vec.size() << endl; // 数组下标方式遍历vector for (int i = 0; i < vec.size(); i++) cout << vec[i] << " "; cout << endl; // 迭代器方式遍历vector vector<int>::iterator itor = vec.begin(); for (; itor != vec.end(); itor++) cout << *itor << " "; cout << endl; return 0; }
|
输出结果为:
2.2 双向链表list
学过数据结构的同学都知道,对于一个双向链表来说主要包括3个元素:
- 指向前一个链表节点的前向指针
- 有效数据
- 指向后一个链表节点的后向指针
链表相对于vector向量来说的优点在于:
(1)动态的分配内存,当需要添加数据的时候不会像vector那样,先将现有的内存空间释放,再次分配更大的空间,这样的话效率就比较低了。
(2)支持内部插入、头部插入和尾部插入
缺点:不能随机访问,不支持[]方式和vector.at()、占用的内存会多于vector(非有效数据占用的内存空间)
2.2.1 初始化list对象的方式:
1 2 3 4 5
| list<int> L0; //空链表 list<int> L1(3); //建一个含三个默认值是0的元素的链表 list<int> L2(5, 2); //建一个含五个元素的链表,值都是2 list<int> L3(L2); //L3是L2的副本 list<int> L4(L1.begin(), L1.end()); //L4含L1一个区域的元素[begin, end]。
|
2.2.2 list常用函数
begin():返回list容器的第一个元素的地址
end():返回list容器的最后一个元素之后的地址
rbegin():返回逆向链表的第一个元素的地址(也就是最后一个元素的地址)
rend():返回逆向链表的最后一个元素之后的地址(也就是第一个元素再往前的位置)
front():返回链表中第一个数据值
back():返回链表中最后一个数据值
empty():判断链表是否为空
size():返回链表容器的元素个数
clear():清除容器中所有元素
insert(pos, num):将数据num插入到pos位置处(pos是一个地址)
insert(pos, n, num):在pos位置处插入n个元素num
erase(pos):删除pos位置处的元素
push_back(num):在链表尾部插入数据num
pop_back():删除链表尾部的元素
push_front(num):在链表头部插入数据num
pop_front():删除链表头部的元素
sort():将链表排序,默认升序
……
2.2.3 遍历方式
双向链表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
| #include <iostream> #include <list> using namespace std;
int main(void) { list<int> l1; // 插入元素方式演示 l1.push_front(1); // 头部插入 l1.push_back(2); // 尾部插入 l1.insert(l1.begin(), 3); // 开始位置插入 l1.insert(l1.end(), 4); // 结束位置插入 cout << "list链表是否为空:" << l1.empty() << endl; cout << "list链表中元素个数:" << l1.size() << endl; cout << "list链表第一个元素:" << l1.front() << endl; cout << "list链表最后一个元素:" << l1.back() << endl; // 遍历链表正向 list<int>::iterator itor = l1.begin(); for (; itor != l1.end(); itor++) cout << *itor << " "; cout << endl; // 遍历链表逆向 list<int>::reverse_iterator reitor = l1.rbegin(); for (; reitor != l1.rend(); reitor++) cout << *reitor << " "; cout << endl; // 将链表排序 l1.sort(); itor = l1.begin(); cout << "重新排序之后正向遍历:"; for (; itor != l1.end(); itor++) cout << *itor << " "; cout << endl; // 清除容器中的所有元素 l1.clear(); cout << "清除容器所有元素之后大小:" << l1.size() << endl; return 0; }
|
输出结果为:
2.3 stack
std::stack 类是容器适配器,它给予程序员栈的功能——特别是 FILO (先进后出)数据结构。
2.3.1 stack常用函数
empty():检查底层的容器是否为空
top():访问栈顶元素
size():返回容纳的元素数
push():向栈顶插入元素
pop():删除栈顶元素
swap():交换内容
2.3.2 常用函数的使用
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
| #include <iostream> #include <stack> using namespace std; int main() { int i; //创建栈 s stack<int> s; //将元素压入栈 for(i = 0; i < 10; i++) { s.push(i); }
cout << "栈s中元素的个数为:" << s.size() << endl; while(!s.empty()) { cout << s.top() << " "; //获取栈顶元素 s.pop(); //弹出栈顶元素 } cout << endl;
if(s.empty()) { cout << "栈s现在为空" << endl; }
return 0; }
|
输出结果为:
2.4 queue容器
C++队列queue模板类的定义在头文件中,queue模板类需要两个模板参数,一个是元素类型,一个容器类型,元素类型是必要的,容器类型是可选的,默认为deque类型。
C++队列Queue是一种容器适配器,它给予程序员一种先进先出(FIFO)的数据结构。
2.4.1 初始化list对象的方式:
1 2
| queue<int> q1; queue<double> q2;
|
2.4.2 queue常用函数
top:访问队头元素
empty():如果queue中没有元素的话,返回true。
size():返回queue中元素的个数。
front():返回queue中第一个元素的引用,若queue是常量,返回一个常引用,若queue为空,返回值是未定义的。
back():返回queue中最后一个元素的引用,若queue是常量,返回一个常引用,若queue为空,返回值是未定义的。
push():在queue的尾部添加一个元素的副本。这是通过调用底层容器的成员函数push_back()来完成的。
pop():删除queue中的第一个元素。
swap(queue &other_q):将当前queue中的元素和参数queue中
的元素交换。它们需要包含相同类型的元素。也可以调用全局函数模板 swap(q1, q2) 来将当前q1中的元素和q1中的元素中的元素交换
2.4.3 队列使用
对queue进行遍历,可以将其赋予一个副本,然后将副本边输出,边弹出元素。
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
| #include <iostream> #include <queue> using namespace std; int main() { int e, n, m, i; queue<int> q1, q2; queue<int> q3, q4;
for(i = 0; i < 10; i++) { q1.push(i); q3.push(i); q2.push(i + 10); q4.push(i + 10); }
cout << "队列q1\n"; if(!q1.empty()) cout << "dui lie bu kong\n"; n = q1.size(); cout << n << endl; m = q1.back(); cout << m << endl; for(i = 0; i < n; i++) { e = q1.front(); cout << e << " "; q1.pop(); } cout << endl; if(q1.empty()) cout << "dui lie bu kong\n";
cout << "队列q2\n"; if(!q2.empty()) cout << "dui lie bu kong\n"; n = q2.size(); cout << n << endl; m = q2.back(); cout << m << endl; for(i = 0; i < n; i++) { e = q2.front(); cout << e << " "; q2.pop(); } cout << endl; if(q2.empty()) cout << "dui lie bu kong\n";
swap(q3, q4); cout << "队列q1与队列q2交换后\n";
cout << "队列q1\n"; for(i = 0; i < n; i++) { e = q3.front(); cout << e << " "; q3.pop(); } cout << endl;
cout << "队列q2\n"; for(i = 0; i < n; i++) { e = q4.front(); cout << e << " "; q4.pop(); } cout << endl;
return 0; }
|
输出结果为:
2.4.4 优先队列使用
priority_queue<Type, Container, Functional>
Type为数据类型, Container为保存数据的容器,Functional为元素比较方式。
如果不写后两个参数,那么容器默认用的是vector,比较方式默认用operator<,也就是优先队列是大顶堆,队头元素最大。
2.4.4.1 优先输出大数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <queue> using namespace std; int main() { priority_queue<int> p; p.push(1); p.push(2); p.push(8); p.push(5); p.push(43); for(int i = 0; i < 5; i++) { cout << p.top() << endl; p.pop(); } return 0; }
|
输出结果为:
43
8
5
2
1
2.4.4.2 优先输出小数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <iostream> #include <queue> using namespace std; int main() { priority_queue<int, vector<int>, greater<int> >p; p.push(1); p.push(2); p.push(8); p.push(5); p.push(43); for(int i = 0; i < 5; i++) { cout << p.top() << endl; p.pop(); } return 0; }
|
输出结果为:
1
2
5
8
43
2.4.4.3 自定义优先级,重载默认的 < 符号
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
| #include <iostream> #include <queue> using namespace std; struct Node { int x, y; Node(int a = 0, int b = 0) : x(a), y(b) {} }; struct cmp { bool operator() (Node a, Node b) { if(a.x == b.x) return a.y > b.y; return a.x > b.x; } }; int main() { priority_queue<Node, vector<Node>, cmp> p; for(int i = 0; i < 10; ++i) p.push(Node(rand(), rand())); while(!p.empty()) { cout << p.top().x << ' ' << p.top().y << endl; p.pop(); } return 0; }
|
输出结果为:
491 9961
5436 4827
11942 2995
15724 19169
16827 23281
18467 41
24464 26962
26500 6334
28145 5705
29358 11478
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,可以邮件至 xingshuaikun@163.com。