目录
1.4 关联式容器(Associative Container)-- 有序(元素key是有序)
1.5 无序容器(Unordered Container)-- 无序(元素的key是无序的)
1 容器分类(结构性分类)
1.1 容器分类简介
容器分为序列式容器和关联式容器(分为有序和无序),红色部分是C++2.0引入的。
-
序列式容器(Sequence Container):元素都是有序的,里面的空间可能是连续的,也可能是由指针一个个串起来的。序列式容器的特点是将数据放进容器之后,会按照用户放进去的顺序依次排列。
序列式容器 | 特点 | 额外学习材料 |
---|---|---|
array | 一段连续空间,不论是否使用,都会全部占用 | array |
vector | 尾部可进可出,当空间不够时会自动扩充 | vector |
deque | 双向都可扩充,两端都可进可出 | deque |
stack | 栈 | - |
queue | 队列 | - |
priority_queue | 优先级队列 | - |
list | 一个双向环状链表,有向前后和向后两个指针 | list |
forward_list | 一个单向链表,仅有向后一个指针 | forward_list |
-
关联式容器(Associative Container):元素是由key和value组成的,当然用key能很快找value,适用于查找操作(查找很快)。
关联式容器名 | 特点 | 实现 | 注释 | 额外学习材料 |
---|---|---|---|---|
set/multiset | key和value是同一个,BST存储是有序的 | 红黑树 | 加上multi意味着可以重复键值对 | set,multiset |
map/multimap | 每一个key对应一个value,BST存储是有序的 | 红黑树 | 加上multi意味着可以重复键值对 | map,multimap |
unordered_set/unordered_multiset | 相对于set/multiset,存储是无序的 | 哈希表 | 加上multi意味着可以重复键值对 | unordered_set,unordered_multiset |
unordered_map/unordered_multimap | 相对于map/multimap,存储是无序的 | 哈希表 | 加上multi意味着可以重复键值对 | unordered_map,unordered_multimap |
测试程序之辅助函数:
//--------------------------------------------------------------------
// 从键盘输入获取一个long类型。
long get_a_target_long()
{
long target=0;
cout << "target (0~" << RAND_MAX << "): ";
cin >> target;
return target;
}
// 从键盘输入获取一个string类型。
string get_a_target_string()
{
long target=0;
char buf[10];
cout << "target (0~" << RAND_MAX << "): ";
cin >> target;
snprintf(buf, 10, "%d", target);
return string(buf);
}
// 比较两个long
int compareLongs(const void* a, const void* b)
{
return ( *(long*)a - *(long*)b );
}
// 比较两个string
int compareStrings(const void* a, const void* b)
{
if ( *(string*)a > *(string*)b )
return 1;
else if ( *(string*)a < *(string*)b )
return -1;
else
return 0;
}
1.2 各个容器的结构关系
容器间的关系,以及各个容器所占用的字节大小如下所示:
1.3 序列式容器(Sequence Container)
1 Array
对比于c语言的数组,标准库进行封装,初始化的时候就要确定大小,不能扩展(注意在栈上有大小限制,不同的机器内存限制不一样)。
使用容器array:
#include <array>
#include <iostream>
#include <ctime>
#include <cstdlib> //qsort, bsearch, NULL
namespace jj01
{
void test_array()
{
cout << "\ntest_array().......... \n";
array<long,ASIZE> c;
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;
long target = get_a_target_long();
timeStart = clock();
::qsort(c.data(), ASIZE, sizeof(long), compareLongs);
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;
}
}
2 Vector
一种可以动态扩充的数组,只能在容器的末端进行扩充,当容量不足时会自动扩充,扩充规则为当前空间的2倍,内存由Vector背后的分配器进行,使用者不用关心内存分配问题;
使用容器vector:
//---------------------------------------------------
#include <vector>
#include <stdexcept>
#include <string>
#include <cstdlib> //abort()
#include <cstdio> //snprintf()
#include <iostream>
#include <ctime>
#include <algorithm> //sort()
namespace jj02
{
void test_vector(long& value)
{
cout << "\ntest_vector().......... \n";
vector<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));
}
catch(exception& p) {
cout << "i=" << i << " " << p.what() << endl;
//曾經最高 i=58389486 then std::bad_alloc
abort();
}
}
cout << "milli-seconds : " << (clock()-timeStart) << endl;
cout << "vector.max_size()= " << c.max_size() << endl; //1073747823
cout << "vector.size()= " << c.size() << endl;
cout << "vector.front()= " << c.front() << endl;
cout << "vector.back()= " << c.back() << endl;
cout << "vector.data()= " << c.data() << endl;
cout << "vector.capacity()= " << c.capacity() << endl << 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 << 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;
}
c.clear();
test_moveable(vector<MyString>(),vector<MyStrNoMove>(), value);
}
}
push_back()了一百万个元素,用了大概3000ms,和array比起来,慢了好多。(可能因为vector的扩容)
(估计不是因为扩容,而是因为每次需要开辟空间,因为array的空间是静态分配的。因为后面显示list插入一百万个元素耗时和vector差不多)
使用::find()几乎不费时,使用先排序再二分用时还挺长的。(可能是运气好,毕竟只找了一次,不能得出很严谨的结论)
3 Deque
双端队列,容器两端均可进行数据扩充;
deque的两端扩容是怎么实现的?按理说的话,vector的扩容是在另一个地方开辟足够的空间,然后再把老地方里面的元素copy过去,因为原地扩容的话可能地方不够用,但是deque竟然可以两端扩容,那么它是怎么实现的呢?deque的底层结构示意图如下:
deque其实是由一段一段的buffer组成的,每段buffer有一个固定的长度,deque维护的只是指向每个buffer的指针,当deque需要扩容时,就在前面或者后面新增一个buffer。也就是deque其实是分段连续的,但是deque却表现为整段连续,这当然是通过重载迭代器的相关操作符实现的。
下面是deque的测试情况,和之前容器表现都类似:
容器适配器stack和queue内部就是有一个deque,从而能实现栈和普通队列的行为。
4 List
双向环状链表,内存占用比Forward_List多;
list测试:
list有一个max_size()的成员函数,返回的值很大,好几亿。list自己有一个sort()的成员函数。
5 Forward_List(C++11新增)
单向链表,只能从尾部扩充。
Forward_List测试:
测试结果和list的相似。但是单链表没有back()和size()的成员函数
1.4 关联式容器(Associative Container)-- 有序(元素key是有序)
关联式容器可以理解为一种小型的关系型数据库,所以查找起来非常快。
1 Set/MultSet
目前各大厂商编译器都是由红黑树(RBtree)实现,保证每个子树根节点键值大于左子树所有节点的键值,小于右子树所有节点的键值,左右是高度平衡的防止某一个分支比较长,每个元素由key和value组成,set中key和value是同一个值,key和value是不分的,元素不可重复,当重复放入时容器会反弹回来;
MultSet表示元素可以重复。
multiset测试:
注意容器本身的find()和全局的find()的速度对比
set测试:
2 Map/Multimap
目前各大厂商编译器也都是由红黑树实现,区别于set,每个元素分为key和value,同样元素不可重复,当重复放入时容器会反弹回来;
Multimap表示元素可重复,也就是key-value可重复。
注意multimap不能用[ ]做插入。
map测试:
1.5 无序容器(Unordered Container)-- 无序(元素的key是无序的)
Unordered set和Unordered map底层是由哈希表(Hashtable)实现;用哈希表必然存在元素碰撞问题,所以各大编译器都采用Separate Chaining哈希表。
1 unordered_multiset
有一个bucket_count()的成员函数,能看到桶的数量。有一个bucket_size(i)的成员函数,能看到第i个桶的元素个数。
2 unordered_multimap
3 unordered_set
4 unordered_map
2 List容器结构分析
2.1 基本结构
G2.9版本,list的设计是双向环状链表,每个节点是一个指针,这个节点又由两个指针(一个向前指一个向后指)和数据部分data组成;
list整体数据部分是node,而node是list_node*类型,list_node是list的一个节点,所以list占用的内存大小是4(在32位机器上),当list从allocate拿内存时是以节点为单位的,也就是说除了数据data部分,还要有两个指针;
2.2 迭代器操作方式
迭代器的操作要回到节点内部,而不是直接跳到下一个地址,比如迭代器++操作,就是要回到节点内部找到next指针指到的位置,然后再去挪动迭代器,迭代器的操作,都是一堆运算符重载
注意:c++语法不允许后++加两次,因为后加加操作符重载后返回的不是引用(后加加里面有临时变量)
2.3 多版本对比介绍
注意:最新版本中,list数据部分(node节点)拆成了两个指针,list大小为两个指针的大小,为8(32位系统下)