C++ STL学习笔记(2) 容器结构与分类
接着学习侯捷老师的C++ STL!
在使用容器的时候,需要明白容器中元素之间在内存里的关系是什么样的,是连续的,还是非连续的。
容器可以分为两类:
1. sequence container , 即序列容器
a. Array 连续空间 , 大小固定,不能扩充
b. Vector 后部可以扩充(当然扩充工作由Allocator完成),我们只需将元素放进去即可
c. Deque 双向的队列,前后都可以扩充
d. List 链表, 双向链表
e. Forward-List ; c++ 11 引进, 单向链表
2. associative container , 即关联式容器 (key,value)
a. set,内部用红黑树实现。(红黑树:一种高度平衡的二叉树,会自动调整左右的大小,方便查找),对于set, 它的key就是value
multiset,放入的元素的内容可以有重复。set则不能重复。
b.map: 具有key和value
multimap,放入的元素的key可以有重复。map则不能重复。
3. unordered container, 可翻译为不定序, 元素放入容器中,其次序是不定的,也可以看作一种关联式容器
a. unordered set/multiset
b. unordered map/multimap
上面的不定序用哈希表来表示的话会更加的直观:
侯捷老师PPT中的哈希表的结构:
通过哈希表,也能很好的解释为什么上述的容器可以翻译为不定序。
可以将上述的每个一深色的方块看作一个bucket,每个篮子中的元素用链表表示(seperate chaining), hash可以理解为:假如有二十个空间,现在放入一个元素进去,元素放入的位置需要通过计算得到,假设元素a经过计算需要放到第三个位置去,元素b经过计算也要放到第三个位置去,这时就会发生碰撞。一种解决方法是:通过一定的计算方式将他们分开放置。另一种方法是:既然碰撞,索性都放到同一个bucket中,以链表表示。这是目前最好的做法。
这样做面临一个问题: 如果一个bucket的链表太长,而查找的元素恰好又在最后一个,那么在查找过程中,这个bucket的链表查找效率会非常低,所以会对bucket的链表长度做某一些限制,如果链表长度超过限制,则会将所有的元素都打散重新进行计算排列位置,这就是为什么会称为不定序的原因。因为每次放入一个元素,原来的排列顺序可能会被打乱。
(void 类型的指针)
1. aray测试:
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <array>
const int arraySize = 100000;
using std::cout;
using std::endl;
int main()
{
Timer timer; // 计时器
std::array<int, arraySize> c;
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(0, 100);
e.seed(10); // 设置生成随机数的种子
for (int i = 0; i < arraySize; i++)
{
c[i] = u(e);
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.data() << endl; // return pointer to mutable data array 返回指针 数组中第一个元素的地址
cout << c.size() << endl; // 大小
cout << c.front() << endl; // 第一个元素
cout << c.back() << endl; // 最后一个元素
return 0;
}
2. 容器vector放入元素,且排序
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <vector>
#include <cstdlib> // qsort snprintf
#include <string>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::vector<std::string> c;
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push_back(std::string(buf));
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.data() << endl; // return pointer to mutable data array 返回指针
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.front() << endl;
cout << c.back() << endl;
cout << c.capacity() << endl; // 实际的空间的大小 vector中内存分配以两倍增长的
timer.reset(); // 计时器归零
std::string target("3456"); // 查找
auto iter = ::find(c.begin(), c.end(), target); // :: 全局函数
if (iter == c.end())
cout << "Not find string" << endl;
else
cout << "Find string " << *iter << endl;
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
return 0;
}
3. list双向链表
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <list>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::list<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push_back(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort();
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.max_size() << endl; // 最大的元素个数
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.front() << endl;
cout << c.back() << endl;
timer.reset(); // 计时器归零
std::string target("3456"); // 查找
auto iter = ::find(c.begin(), c.end(), target); // :: 全局函数
if (iter == c.end())
cout << "Not find string" << endl;
else
cout << "Find string " << *iter << endl;
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
timer.reset();
c.sort(); // 排序
std::cout << "Sort Elapsed " << timer.elapsed() << " ms" << std::endl;
return 0;
}
4. forward_list单向链表
这个的用法于双向链表list基本相同
6. deque 双向队列
一个容器占用一定的内存后,就不能再向外扩充了。而是以别的方式进行扩充,例如,对于vector,它是按照每次两倍的内存进行扩充的,但是它的扩充策略是先找到另一块两倍大小的内存,然后再将其原来的内容复制过去。
那么deque的扩充策略是什么样的,两端都可以扩充?
deque的内部结构:
可以看出deque是分段连续的,map中存储指针,每个指针指向一段连续的buffer,当一个buffer放满元素的时候,会自动分配下一个buffer。其中buffer是连续的。
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <deque>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::deque<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push_back(std::string(buf));
// c.push_front(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.max_size() << endl; // 最多能存储的元素个数
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.front() << endl;
cout << c.back() << endl;
timer.reset(); // 计时器归零
std::string target("3456"); // 查找
auto iter = ::find(c.begin(), c.end(), target); // :: 全局函数
if (iter == c.end())
cout << "Not find string" << endl;
else
cout << "Find string " << *iter << endl;
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
return 0;
}
7. stack, 我们非常熟悉的栈,出战,入栈操作
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <stack>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::stack<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push(std::string(buf));
// c.push_front(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
return 0;
}
8. queue,用法于stack非常类似,不做详细的介绍了
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <queue>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::queue<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.push(std::string(buf));
// c.push_front(std::string(buf));
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
return 0;
}
对于stack和queue,它们内部实际上是通过deque实现的,所以也可以将他们称为容器适配器。
关联式容器
1. 使用multiset容器
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <set>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::multiset<std::string> c; // 双向链表
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::string(buf)); //set inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
std::string target("3456");
timer.reset();
auto iter = ::find(c.begin(), c.end(), target); // 通用的find函数
if (iter == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
cout << "found target " << *iter << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
cout << "found target " << *pItem << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}
这里有必看介绍一下运行的结果:
可以看到,set内部的find函数比通用的::find()函数速度快很多,set内部利用红黑树实现,所以它的查找速度是非常快的。但是也可以发现,关联式容器在插入数据的时候,速度还是相当慢的。
2. 使用容器multimap
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <map>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::multimap<int, std::string> c; // key, value
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::pair<int, std::string>(i, std::string(buf))); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
// std::string target("3456")
int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << pItem->first << " | " << pItem->second << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}
3. unordered_multiset
不定序, 这种multiset的实现是通过hash table进行的,而不是经典的红黑树
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <unordered_set>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::unordered_multiset<std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::string(buf)); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
cout << c.max_bucket_count() << endl;
cout << c.bucket_count() << endl; // bucket的数量
// 关联式容器的查找
std::string target("3456");
// int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << *pItem << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
int bucket_cnt = c.bucket_count(); // 遍历一下bucket
for (int i = 0; i < 10; i++)
{
cout << "Bucket #" << i << " has " << c.bucket_size(i) << " elements" << endl;
}
return 0;
}
可以查看hash table中数据存储的具体情况,例如可以看到bucket的数量,以及bucket中存储的元素个数
运行结果:
从结果可以看到,bucket的数量居然是大于元素的个数,实际上,bucket的个数确实是大于元素个数,因为从上面的hash table图可以发现,有的bucket中存放了多个元素,有的hash table中却是空的。bucket的个数大于元素个数是为了避免一个bucket过长(会降低查找元素的效率),所以每当元素个数大于bucket的个数的时候,bucket的个数就会按照两倍扩充,然后将元素打散,重新按照一定的规则计算元素放置的位置。
4. unordered multi_map
与上面的multi_set类似
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <unordered_map>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::unordered_multimap<int, std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(pair<int, std::string>(i, std::string(buf))); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
cout << c.max_bucket_count() << endl;
cout << c.bucket_count() << endl; // bucket的数量
// 关联式容器的查找
// std::string target("3456");
int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << pItem->first << " | " << pItem->second << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
int bucket_cnt = c.bucket_count(); // 遍历一下bucket
for (int i = 0; i < 10; i++)
{
cout << "Bucket #" << i << " has " << c.bucket_size(i) << " elements" << endl;
}
return 0;
}
5 set集合,于multi_set的用法类似
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <set>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::set<std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c.insert(std::string(buf)); //map inset
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
std::string target("3456");
// int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << *pItem << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}
5 map,与multi_map的用法类似
// ConsoleApplication1.cpp : 定义控制台应用程序的入口点。
#include "stdafx.h"
#include <iostream>
#include "Timer.h"
#include <random>
#include <map>
#include <cstdlib> // qsort snprintf abort
#include <string>
#include <stdexcept>
const int arraySize = 100000;
using std::cout;
using std::endl;
int compare(void* x, void* y)
{
return (*(int*)x - *(int*)y); // 指针类型转换
}
int main()
{
Timer timer; // 计时器
std::map<int, std::string> c; // 不定序, 通过hash table实现
std::default_random_engine e; // 默认的随机数引擎
std::uniform_int_distribution<int> u(1000, 20000);
e.seed(10); // 设置生成随机数的种子
char buf[10];
for (int i = 0; i < arraySize; i++)
{
try
{
int rnd = u(e); //随机数
std::snprintf(buf, sizeof(buf), "%d", rnd); // 将随机数写入c_string
c[i] = std::string(buf); // map可以用这种写法
}
catch (std::exception& exp)
{
cout << exp.what() << endl; // 会抛出标准异常类中的异常 bad_alloc
std::abort(); // 退出程序
}
}
std::cout << "Elapsed " << timer.elapsed() << " ms" << std::endl;
cout << c.size() << endl; // vector中的实际元素的个数,arraySize;
cout << c.max_size() << endl;
// 关联式容器的查找
// std::string target("3456");
int target = 300;
timer.reset(); // 计时器归零
auto pItem = c.find(target); // multiset内部的find函数 返回指针
if (pItem == c.end())
{
cout << "Not found!" << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
else
{
// 输出key,value的值
cout << "found target " << pItem->first << " | " << pItem->second << " Time Elapsed " << timer.elapsed() << " ms" << endl;
}
return 0;
}
后续的unordered set, unordered map的用法与前面的都很相似。
----------------------------------------------------------分割线--------------------------------------------------------------