此系列文章都是根据侯捷老师的系列教学视频进行编写,记录自己在学习STL过程中的知识点。
C++标准库
STL(Standard Template Library)标准模板库,以头文件的形式呈现:
- C++标准库的头文件不带有后缀,例如
#include <vector>
- 新式的C头文件不带有后缀,例如
#include <cstdio>
- 旧式的C头文件带有后缀,例如
#include <stdio.h>
C++标准库的重要网页(查找函数说明,还有例子说明):
STL六大部件
STL一共有六个部件,相辅相成各自有各自的功能:
- 容器:最重要的部件之一,用来存放东西,存放东西就涉及到使用什么数据结构存取,因为也分为序列容器、关联容器和不定序容器(后面讲解)
- 分配器:容器既然是存取东西的,那么就一定涉及到内存,C++标准库中把内存都封装好,让我们看不到,而这一部分的工作就是分配器做的(主要做容器与内存之间的连通)
- 算法:最重要的部件之一,按照拥护要求对容器中的数据进行不同的计算方法,算法中也涉及到全局算法和容器自带算法(后面讲解)
- 迭代器:既然算法是需要计算容器中的元素,那么就需要一个东西能够取出容器中的元素,这就是迭代器(泛型指针,指向容器中的元素)
- 适配器:在STL中适配器起转换的作用,比如容器适配器stack和queue,它们实际上都是以deque容器为底层的,只是为了实现堆栈的先入先出和先入后出的特性而转换的
- 仿函数:当你用一个算法去求加法的时候,如果是数字可以直接相加,但是如果是一棵树一个人一列表格等抽象化的东西,怎么相加呢,这就是仿函数实现的,可以自定义算法的运行规则
容器分类与测试
上图中就是所有的容器分类,每一列分别代表不同的容器类别(数据结构):
- Sequence Containers(序列容器):也就是我们熟知的数组、链表等数据结构。序列容器的优点在于存储方便(不同容器有不同的存储特性),但是想要查找数据就会很慢
- Associative Containers(关联容器):使用**红黑树(就是完全平衡的二叉树)**存储,优点在于可以快速查找(因为每次存取都会移动树使其完全平衡,令其一直有快速搜索的能力)
- Unordered Containers(不定序容器):使用哈希表存储,其实跟关联容器是一样的优点,其实有些也把这个容器归并与关联容器
注意:所有的容器都是符合一个原则:左闭右开(即所有容器的begin
都是指向容器的第一个元素,end
指向容器的最后一个元素再+1的位置)
array
就是我们熟知的数组,既然是数组,就需要在使用前指定数组的大小(一般不怎么用,都是使用vector
)
#include <array>
#define ASIZE 500000
void test_array()
{
cout << "\ntest_array().......... \n";
array<long,ASIZE> c; //数组array不仅需要指定数据类型,还需要指定数据量有多少,这是数组的特性
clock_t timeStart = clock();
for(long i=0; i< ASIZE; ++i) {
c[i] = rand(); //数组中随机放置数值
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "array.size()= " << c.size() << endl;
cout << "array.front()= " << c.front() << endl;
cout << "array.back()= " << c.back() << endl;
cout << "array.data()= " << c.data() << endl; //.data()得到的是指针,指向数组的第一个元素
long target = get_a_target_long(); //输入我们要查找的数值
timeStart = clock();
//这里使用了::是为了告诉编译器这是从全局函数中找qsort函数,平常我们写的时候可以不用写::
//这里的compareLongs就是使用了自定义的排序规则
::qsort(c.data(), ASIZE, sizeof(long), compareLongs); //这是使用的qsort排序算法
//从c中使用bsearch函数二分查找,找到target,将指针返回pIem
long* pItem = (long*)::bsearch(&target, (c.data()), ASIZE, sizeof(long), compareLongs);
cout << "qsort()+bsearch(), milli-seconds : " << (clock()-timeStart) << endl; //看看排序和查找一共花了多久
if (pItem != NULL)
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
}
输出如下:
test_array()..........
milli-seconds : 12
array.size()= 500000
array.front()= 22596
array.back()= 17507
array.data()= 0x5c40817120
target (0~32767):23456
qsort()+bsearch(), milli-seconds : 78
found, 23456
vector
常用的容器之一,跟数组最主要的不同就是一端可以自动增长。注意:vector
空间不够进行扩充时,是在另一个地方找到一个两倍大的地方,再把数据都迁移过去
#include <vector>
void test_vector(long& value)
{
cout << "\ntest_vector().......... \n";
vector<string> c;
char buf[10];
//下面存数据使用了try catch语句,主要是为了防止存放数据太多导致程序崩溃
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
c.push_back(string(buf));
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort(); //如果数据过多了,就会运行到这了,abort函数就会退出程序
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "vector.max_size()= " << c.max_size() << endl;
cout << "vector.size()= " << c.size() << endl; //vector中有多少元素
cout << "vector.capacity()= " << c.capacity() << endl << endl; //vector的容量是多少
string target = get_a_target_string();
//下面使用了两个方法进行查找
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);
cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl << endl;
else
cout << "not found! " << endl << endl;
timeStart = clock();
sort(c.begin(), c.end());
cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl;
timeStart = clock();
string* pItem = (string*)::bsearch(&target, (c.data()),c.size(), sizeof(string), compareStrings);
cout << "bsearch(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != NULL)
cout << "found, " << *pItem << endl << endl;
else
cout << "not found! " << endl << endl;
}
输出如下:
test_vector()..........
milli-seconds : 366
vector.max_size()= 288230376151711743
vector.size()= 500000
vector.capacity()= 524288
target (0~32767): ->23456
std::find(), milli-seconds : 1
found, 23456
sort(), milli-seconds : 520
bsearch(), milli-seconds : 2
found, 23456
list
list是一个双向链表,每个元素都存放着两个指针,分别指向前一个和后一个元素
#include <list>
void test_list(long& value)
{
cout << "\ntest_list().......... \n";
list<string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
c.push_back(string(buf)); //使用push_back进行数据的存放
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "list.size()= " << c.size() << endl;
cout << "list.max_size()= " << c.max_size() << endl; //357913941
cout << "list.front()= " << c.front() << endl;
cout << "list.back()= " << c.back() << endl;
string target = get_a_target_string();
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);
cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
timeStart = clock();
c.sort(); //使用了容器自身的sort函数
cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;
}
需要注意的是:最后用了c.sort()
函数,这是使用了容器自带的算法函数而没有使用全局的sort
算法函数:
- 表明,有的容器自身是带有一些算法函数的,当使用算法的时候,使用自身带有的算法函数一般情况下都会比全局算法函数要快(而下面的例子可能赶巧了)
test_list()..........
milli-seconds : 199
list.size()= 500000
list.max_size()= 192153584101141162
list.front()= 30596
list.back()= 25823
target (0~32767): ->23456
std::find(), milli-seconds : 2
found, 23456
c.sort(), milli-seconds : 935
forward_list
这是一个单向列表,整体的使用方式跟list一样
#include <forward_list>
void test_forward_list(long& value)
{
cout << "\ntest_forward_list().......... \n";
forward_list<string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
c.push_front(string(buf)); //注意这里使用的是push_front,而不是push_back
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "forward_list.max_size()= " << c.max_size() << endl; //536870911
cout << "forward_list.front()= " << c.front() << endl;
string target = get_a_target_string();
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);
cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
timeStart = clock();
c.sort(); //使用算法自带函数
cout << "c.sort(), milli-seconds : " << (clock()-timeStart) << endl;
}
forward_list
使用的是push_front
进行数据的存放,还有个容器是slist
容器,这是以前旧版本的名字,用法跟forward_list
一样:
test_forward_list()..........
milli-seconds : 307
forward_list.max_size()= 230584300921369395
forward_list.front()= 15262
target (0~32767): ->23456
std::find(), milli-seconds : 1
found, 23456
c.sort(), milli-seconds : 1069
deque
deque是个双向队列,从一个内存的角度出发,开辟的数据怎么可能前后都能够一直扩充,从下图中可以看出:
- deque容器实际上存放的是指针,每个指针都指向一个buffer,每个buffer中都存放8个元素(实际上每个buffer有很多元素),因此deque前后都可以扩充指针大小
- 每次扩充数据的时候,如果当前buffer不够了,就重新扩充一个buffer,因此每次只会扩充出一个buffer的大小(减少资源浪费)
- 容器的迭代器指向的是buffer中的元素,那么迭代器指向前一个buffer的最后一个元素时,再++操作后,会自动转向下一个buffer的头(第一个元素),实现的方式是重载了++运算符
对比各个容器扩充数据的方式:
- vector每次扩充都是两倍扩充,那么就很容易造成资源浪费
- list每次扩充都是一个元素大小
- deque每次扩充一个buffer,能够减少一定程度的资源浪费
#include <deque>
void test_deque(long& value)
{
cout << "\ntest_deque().......... \n";
deque<string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
c.push_back(string(buf)); //也可以push_front
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "deque.size()= " << c.size() << endl;
cout << "deque.front()= " << c.front() << endl;
cout << "deque.back()= " << c.back() << endl;
cout << "deque.max_size()= " << c.max_size() << endl;
string target = get_a_target_string();
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target);
cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
timeStart = clock();
sort(c.begin(), c.end()); //使用的是全局算法函数
cout << "sort(), milli-seconds : " << (clock()-timeStart) << endl;
}
输出如下:
test_deque()..........
milli-seconds : 111
deque.size()= 500000
deque.front()= 5809
deque.back()= 15313
deque.max_size()= 288230376151711743
target (0~32767): ->23456
std::find(), milli-seconds : 0
found, 23456
sort(), milli-seconds : 597
stack/queue
stack(先进后出) | queue(先进先出) |
---|---|
- stack和queue的底层容器都是deque容器,因此这两个叫容器适配器
- 为了满足先进后出(先进先出)的特性,这两个没有迭代器指向其中的元素,因为如果有迭代器指向元素,就可以修改元素,就不能满足特性了
- 由于没有迭代器,也就没有算法,只要
push
和pop
set/multiset
使用二叉树进行数据存储,是一个完全平衡二叉树,保证搜索数据的快速:
set
存储的元素不能相同,multiset
存储的元素可以相同。因此输出中set.size()= 32768
而multiset.size()= 500000
#include <set>
void test_multiset(long& value)
{
cout << "\ntest_multiset().......... \n";
multiset<string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
c.insert(string(buf)); //使用insert进行数据插入
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "multiset.size()= " << c.size() << endl;
cout << "multiset.max_size()= " << c.max_size() << endl;
string target = get_a_target_string();
{
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target); //比 c.find(...) 慢很多
cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
}
{
timeStart = clock();
auto pItem = c.find(target); //比 std::find(...) 快很多
cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
}
}
使用自带的算法函数find
会更快,这是关联容器使用二叉树的特点:
test_multiset()..........
milli-seconds : 1199
multiset.size()= 500000
multiset.max_size()= 144115188075855871
target (0~32767): ->23456
std::find(), milli-seconds : 52
found, 23456
c.find(), milli-seconds : 0
found, 23456
map/multimap
使用二叉树结构,每个节点都是由key
和value
组成,可以使用key
进行搜索:
map
存储的key
不能相同,multimap
存储的key
可以相同。
#include <map>
void test_multimap(long& value)
{
cout << "\ntest_multimap().......... \n";
multimap<long, string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
//multimap 不可使用 [] 做 insertion
c.insert(pair<long,string>(i,buf));
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "multimap.size()= " << c.size() << endl;
cout << "multimap.max_size()= " << c.max_size() << endl; //178956970
long target = get_a_target_long();
timeStart = clock();
auto pItem = c.find(target);
cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, value=" << (*pItem).second << endl;
else
cout << "not found! " << endl;
}
输出如下:
test_multimap()..........
milli-seconds : 668
multimap.size()= 500000
multimap.max_size()= 128102389400760775
target (0~32767):23456
c.find(), milli-seconds : 2
found, value=1669
unordered_set/unordered_multiset
除了存储结构不同,剩下都跟前面差不多。哈希表的一列就是篮子,每个元素都在各个篮子下挂着,使用单向链表的方式存储:
- 由于使用的是单向链表,那么在查找数据的时候,某个链表就不能很长(否则查找很慢),因此哈希表中不会有某几个篮子非常长
#include <unordered_set>
void test_unordered_multiset(long& value)
{
cout << "\ntest_unordered_multiset().......... \n";
unordered_multiset<string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
c.insert(string(buf));
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "unordered_multiset.size()= " << c.size() << endl;
cout << "unordered_multiset.max_size()= " << c.max_size() << endl; //357913941
cout << "unordered_multiset.bucket_count()= " << c.bucket_count() << endl;
cout << "unordered_multiset.load_factor()= " << c.load_factor() << endl;
cout << "unordered_multiset.max_load_factor()= " << c.max_load_factor() << endl;
cout << "unordered_multiset.max_bucket_count()= " << c.max_bucket_count() << endl;
for (unsigned i=0; i< 20; ++i) {
cout << "bucket #" << i << " has " << c.bucket_size(i) << " elements.\n";
}
string target = get_a_target_string();
timeStart = clock();
auto pItem = find(c.begin(), c.end(), target); //比 c.find(...) 慢很多
cout << "std::find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
timeStart = clock();
auto pItem = c.find(target); //比 std::find(...) 快很多
cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, " << *pItem << endl;
else
cout << "not found! " << endl;
}
输出如下:
.bucket_count()
就是篮子数量,篮子数量一定会比数据数量多- 哈希表的每次扩充,都会将数据打散,重新放置
unordered_set
存储的元素不能相同,unordered_multiset
存储的元素可以相同。因此输出中unordered_set.size()= 32768
而unordered_multiset.size()= 500000
test_unordered_multiset()..........
milli-seconds : 606
unordered_multiset.size()= 500000
unordered_multiset.max_size()= 192153584101141162
unordered_multiset.bucket_count()= 712697
unordered_multiset.load_factor()= 0.70156
unordered_multiset.max_load_factor()= 1
unordered_multiset.max_bucket_count()= 192153584101141162
bucket #0 has 0 elements.
bucket #1 has 9 elements.
bucket #2 has 0 elements.
bucket #3 has 0 elements....
target (0~32767): ->23456
std::find(), milli-seconds : 18
found, 23456
c.find(), milli-seconds : 0
found, 23456
unordered_map/unordered_multimap
整体跟前面一样:
#include <unordered_map>
void test_unordered_multimap(long& value)
{
cout << "\ntest_unordered_multimap().......... \n";
unordered_multimap<long, string> c;
char buf[10];
clock_t timeStart = clock();
for(long i=0; i< value; ++i)
{
try {
snprintf(buf, 10, "%d", rand());
//multimap 不可使用 [] 進行 insertion
c.insert(pair<long,string>(i,buf));
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "unordered_multimap.size()= " << c.size() << endl;
cout << "unordered_multimap.max_size()= " << c.max_size() << endl; //357913941
long target = get_a_target_long();
timeStart = clock();
auto pItem = c.find(target);
cout << "c.find(), milli-seconds : " << (clock()-timeStart) << endl;
if (pItem != c.end())
cout << "found, value=" << (*pItem).second << endl;
else
cout << "not found! " << endl;
}
输出如下:
test_unordered_multimap()..........
milli-seconds : 325
unordered_multimap.size()= 500000
unordered_multimap.max_size()= 192153584101141162
target (0~32767):23456
c.find(), milli-seconds : 1
found, value=381
分配器
分配器…总的来说,可以使用自定义,但是没必要…