STL是什么?
STL是 ——标准库,是一个高效的C++标准库,是一些 容器、算法和其他一些组件的集合。
STL是目的是为了标准化组件,这样就可以不用再构建新的组件、直接使用现成的组件
STL现在是C++的一部分,被内置在你的编译器中。
STL可以保存对象,内建对象和类对象。它们能安全的保存对象,并且定义一个我们能操作它的接口。
STL组件有什么?
STL主要分为三类:容器、算法、迭代器。
1.容器: 容器是用来管理某种特定对象的集合(可以是数组、链表或者类字典)
2.算法: 算法是用来处理对象集合的元素 (比如:sort\search\copy。通过迭代器的协助,我们只需要撰写一次算法,就可以将它应用于任一容器之上,这是因为任意容器的迭代器都提供一致的借口)
3.迭代器: 用来在一个对象集合的元素上做遍历操作。这个对象可能是容器、也可能是容器的一部分。但是每一种容器都提供了他们自己的迭代器算法,而这些迭代器该了解该种容器的内部结构。
STL 中数据和容器是分离的,数据存储在容器中,使用算法进行操作,迭代器是这两者之间的粘合剂,让算法与容器进行交互。
容器的分类
容器主要可分为以下这么两大类:
1.序列式容器: 可序群集
其中的每个元素都有其特定的位置——这个位置取决于 插入的时间和地点,与插入的元素的值无关。
如果你以追加的方式插入六个元素,那么将它们的排列方式将和插入顺序一致。
序列式容器包含: array、vector(向量)、list(列表)、duque(双端队列)、forward list。
- vector在头部和中间插入删除效率较低、在尾部插入删除效率较高。
- duque在头部尾部插入删除效率较高。
- list:如果想要频繁的在序列中间进行插入 删除操作并且不需要在序列内部进行长距离的跳转 可以选择list
2.关联容器: 已序群集
把元素按照某种规则排好序的集合——这个位置取决于特定的排列方式和插入元素的值,和插入次序无关。
序列化容器包含:set(集合)、multiset(多重集合)、map(映射)、multimap(多重映射)。
-
关联容器按照特定的排序准则进行对元素的自动排序。 排序准则以函数形式呈现:用来比较元素值(val)或者元素键(key)。缺省情况下,可以用 operator< 进行比较。也可以定义自己的排序函数,定义出不同的排序准则。
-
关联容器一般由二叉树构建。在二叉树中,每个元素一般都会有父节点和左节点、右节点。左子树的所欲元素都比父结点小,右子树的所有元素都比父结点大。关联容器的存放一般都和元素的类型 和 处理重复元素的方式。
-
关联式容器可以自动的进行排序。但是这并不意味着是排序的容器。也可以进行对元素进行手动排序。自动排序意义是为了在查询某个元素时,可以获得更高的效率。可以更放心的使用二分查找法,它为对数复杂度,并非线性复杂度。如果在1000个元素中查找某个元素,只需要比较10次,而非500次比较。
容器的特点介绍:
一:序列容器:
(1)vector: 向量容器。
-
概述: 和数组差不多,但是比数组更优越,因为数组不可以进行动态开辟,那么程序在进行运行的时候容易造成不适内存资源浪费,就是越界。vector的出现就弥补了这一缺陷,在开头和中间插入删除元素操作比较慢,在末尾插入删除比较快。底层是一个可动态增长的一位数组,内存可自增长、内次增长2倍。
-
特点:
-
拥有一段连续的内存地址,并且起始地址不变,可以支持很好的随机存取操作,也就是[]操作符。但是由于内存是连续的,所以在中间进行元素的插入和删除时,需要进行内存的拷贝,并且在扩 增时,需要复制一块足够大的内存并且进行内存的拷贝,这些都将大大影响vector的效率。
-
对于在头部和中间进行插入删除元素时,需要移动内存;如果你的元素时结构或者类的话,还需要构造函数和析构函数,所以性能不高。
-
末尾元素进行操作时,效率最高,因为一般不需要移动内存,只有当保留内存不够时需要。
-
随机访问每个元素、所需的时间为常量。
-
在末尾删除/添加元素所需的时间 与 元素数目没有关系。在中间或者开头 进行删除或者添加所需的时间随元素的数目呈线性变化。
-
可动态的增加或者删除元素、内存管理自动完成。但程序员可以用reserve()函数进行内存管理
-
-
迭代器失效:
vector的迭代器在 内存重新分配的时候就会失效(因为所指向的元素在该操作之后不再相同),当把超过capacity_size()个元素插入vector中,内存会重新分配,所有的迭代器都会失效。否则指向当前元素以后的所有元素都会失效。当删除元素时,指向该删除元素的以后的所有元素的迭代器都会失效。 -
优缺点及使用场景:
- 优点:因为可以进行随意的存取,即:[] 操作符 和 duque.at() ,所以查询效率高。
- 缺点:因为在头部和中间插入删除元素时,为了保证原有的相对次序,插入或者删除点之后的元素需要向后移动,所以插入删除的效率较低。
- 适用场景:变化较小,对象简单,频繁随机访问的场景。
//针对整型定义一个vector,插入6个元素,并打印:
#include "stdafx.h"
#include <iostream>
#include <vector>
using namespace std;
int main(int argc, char* argv[])
{
vector<int> vecTemp;
for (int i = 0; i<6; i++)
{
vecTemp.push_back(i);
}
for (int i = 0; i<vecTemp.size(); i++)
{
cout << vecTemp[i] <<" ";
}
return 0;
}
(2)deque: 双端队列容器
-
概述:
底层是动态开辟的二维数组。因为可以向两端发展,所以在头部或则尾部进行插入删除元素时,比较方便迅速;但是在中间进行插入删除时,就比较麻烦,需要移动其他元素。 -
特点:
- 是按页或者块分配存储器的,每页都有固定数目的元素。
- duque是 vector和list折中的方案,既有list的优点,又有vector 快速访问的优点。
- 随机访问每个元素,所需的时间为常量。
- 在末尾或者开头进行添加元素所需要的时间和元素数目没有关系。在中间删除或者添加元素所需要的时间随元素数目 呈线性变化。
- 可动态的增加或者删除元素,内存管理自动完成。
-
迭代器失效:
除了开头结尾外,在duque的任何地方添加元素都会使得duque的迭代器失效,在duque中间删除元素会使 迭代器失效,在duque的首尾部删除元素时,只会使所指向的那一个元素的迭代器失效。 -
优缺点及适用场景:
- 优点:支持随机访问,[] 和 duque.at(),所以查询效率高,两端可以进行 pop(出)和push(入)。
- 缺点:不适用中间操作;占用内存多。
- 适用场景:既要频繁的随机存取,又要关心两端数据元素的插入删除情况。
//声明了一个浮点类型的dque,并在容器头部插入6个元素,最后打印出所有元素
#include "stdafx.h"
#include <iostream>
#include <deque>
using namespace std;
int main(int argc, char* argv[])
{
deque<float> dequeTemp;
for (int i = 0; i<6; i++)
{
dequeTemp.push_back(i);
}
for (int i = 0; i<dequeTemp.size(); i++)
{
cout << dequeTemp[i] << " ";
}
return 0;
}
(3)list: 列表容器
- 概述:
- 底层是带头结点的双向链表容器
- 元素也可以放在堆上,每个元素都是放在一块内存上,它的内存可以是不连续的,通过指针来进行数据的访问。 这个特点使得它的随机存取变得很没有效率,因为它不能提供 [] 的重载,但是又因为链表的性质,所以又可以很方便的进行插入删除操作。
- 特点:
- 没有空间预留,所以每一个元素都会给分配一块内部,没删除一个元素都会释放它所占用的内存
- 不管在链表哪里都可以很方便的进行插入删除元素,不需要移动元素,也不需要每一个元素都进行构造、析构函数,所以常被用来当做 插入删除元素容器。
- 访问开始和最后 两个元素最快,其他元素时间一样。
- 不能随机访问一个元素
- 可双向遍历
- 在开头、末尾或者中间部位 删除或者添加元素的时间都是常量,可动态的增加、删除元素、内存管理自动完成。
- 迭代器失效:
增加元素会使迭代器失效。删除元素时,除了指向该元素的迭代器之外,其他迭代器不会失效。 - 优缺点及适用场景:
- 优点:内存不连续、动态操作,插入删除效率高
- 缺点:不能进行随机访问,相比于vector 占用内部多
- 适用场景:经常进行插入删除操作 并且 不经常进行随机访问的场景。
//以下例子产生一个空list,准备放置字符,然后将 'a' 至 'z' 的所有字符插入其中,利用循环每次打印并移除集合的第一个元素,从而打印出所有元素:
#include "stdafx.h"
#include <iostream>
#include <list>
using namespace std;
int main(int argc, char* argv[])
{
list<char> listTemp;
for (char c = 'a'; c <= 'z'; ++c)
{
listTemp.push_back(c);
}
while (!listTemp.empty())
{
cout <<listTemp.front() << " ";
listTemp.pop_front();
}
return 0;
}
- (4)forward list: 单向链表
- 特点:
- 不可双向遍历、只可从头到尾的遍历 特性类似于 list
二、关联容器:
- (1)set: 集合
- 概述:
底层:红黑树(平衡二叉树的一种),内部元素根据其值进行自动排序。每个元素都只出现一次,不允许重复。 - 特点:
- set中的元素都是排好序的;
- set集合中的元素没有重复的;
- set和map进行插入删除操作时,相比于其他容器的效率更高,因为不需要进行内存移动和内存拷贝。
- 优缺点和适用场景:
- 优点:使用平衡二叉树构成,便于元素查找、且保证了元素的唯一性,并自动排序。
- 缺点:每次插入的时候,都要调整红黑树,有效率有一定的影响。
- 适用场景:经常查找一个元素是否在 群集中需要排序的场景。
例子:
举例演示 set的两个特点:
#include "stdafx.h"
#include <iostream>
#include <set>
using namespace std;
int main(int argc, char* argv[])
{
set<int> setTemp;
setTemp.insert(3);
setTemp.insert(1);
setTemp.insert(2);
setTemp.insert(1);
set<int>::iterator it;
for (it = setTemp.begin(); it != setTemp.end(); it++)
{
cout << *it << " ";
}
return 0;
}
//输出结果:1 2 3。一共插入了4个数,但是集合中只有3个数并且是有序的,可见之前说过的set集合的两个特点,有序和不重复
当set集合中的元素为结构体时,该结构体必须实现运算符‘<’的重载:
#include "stdafx.h"
#include <iostream>
#include <set>
#include <string>
using namespace std;
struct People
{
string name;
int age;
bool operator <(const People p) const
{
return age < p.age;
}
};
int main(int argc, char* argv[])
{
set<People> setTemp;
setTemp.insert({"张三",14});
setTemp.insert({ "李四", 16 });
setTemp.insert({ "隔壁老王", 10 });
set<People>::iterator it;
for (it = setTemp.begin(); it != setTemp.end(); it++)
{
printf("姓名:%s 年龄:%d\n", (*it).name.c_str(), (*it).age);
}
return 0;
}
/*
输出结果
姓名:王二麻子 年龄:10
姓名:张三 年龄:14
姓名:李四 年龄:16
*/
//可以看到结果是按照年龄由小到大的顺序排列。另外string要使用c_str()转换一下,否则打印出的是乱码。
-
(2)multiset: 多重集合
- 性质与list类似,但是multiset允许重复元素, 可包括多个值相同的元素。
-
(3)map: 映射
- 概述:
- map底层是 红黑树。其元素都是 键值/实值 构成的一个对组。 每个元素都有一个键,是排序准则的基础,每个键都只能出现一次,不允许重复。
- map主要是用于 一对一的映射关系。map内部自建一颗红黑树,这棵树具有对数据自动排序的功能,所以在map内部所有的数据元素都是有序的。比如在一个班级中,每一个学生的学号和姓名是一一对应的。
- 特点:
- 自动建立起 key-val的对应关系。key和val可以是任意的类型。
- 概述:
-
(4)multimap: 多重映射
3.容器适配器: 提供顺序容器的特殊接口。