1、容器
容器类是管理序列的类,是容纳一组对象或对象集的类。通过由容器类提供的成员函数,可以实现诸如向序列中插入元素,删除元素,查找元素等操作,这些成员函数通过返回迭代子来指定元素在序列中的位置。
(1)、顺序容器
vector 矢量容器 底层实现:不定长顺序表 #include
list 双向链表容器 底层实现:双向循环链表 #include 支持双向迭代器
dquen 双端队列容器 底层实现:双端队列 #include
template
void Show(Container& con)
{
Container::iterator it = con.begin();
while (it != con.end())
{
std::cout << *it << " ";
it++;
}
std::cout << std::endl;
}
#include
int main()
{
std::deque dqu;
for (int i = 0; i < 10; i++)
{
dqu.push_front(i + 1);
}
Show(dqu);
dqu.erase(dqu.end() - 3);
Show(dqu);
dqu.sort();
return 0;
}
#if 0
#include
int main()
{
std::list lst;
for (int i = 0; i < 10; i++)
{
lst.push_front(i + 1);
}
Show(lst);
lst.pop_front();
Show(lst);
lst.erase(++++lst.begin());
Show(lst);
//std::sort(lst.begin(), lst.end());
lst.sort();
Show(lst);
return 0;
}
#include
int main()
{
std::vector> vec;
std::vector ivec1;
std::vector ivec2;
vec.push_back(ivec1);
vec.push_back(ivec2);
std::vector> vec2(10);
vec2[0].push_back(1);
std::cout << vec2[0][0] << std::endl;
return 0;
}
int main()
{
std::vector vec1;
std::vector vec2(10);
std::vector vec3(10, 20);
int arr[] = { 123, 2, 3456, 7 };
int len = sizeof(arr) / sizeof(arr[0]);
std::vector vec4(arr, arr + len);
//Show(vec4);
for (int i = 0; i < 10; i++)
{
vec1.push_back(i + 1);
}
Show(vec1);
vec1.insert(vec1.begin() + 5,100);
Show(vec1);
std::sort(vec1.begin(), vec1.end());
Show(vec1);
vec1.pop_back();
vec1.erase(vec1.end() - 2);
Show(vec1);
std::cout << vec1.front() << std::endl;
std::cout << vec1.back() << std::endl;
return 0;
}
(2)、关联容器(红黑树(RBT)、支持双向迭代器 )
set 单重集合 不允许键值重复 #include
multiset 多重集合 #include
map 单重映射 存放键值(key-value)对 #include
multimap 多重映射 #include
#include
#include
#include
(3)、容器适配器(默认存放的是deque、不支持迭代器)
可以象顺序容器一样使用。但是它没有自己的构造和析构函数,它使用其实现类(如vector)的构造和析构函数。队列(queue)缺省用deque为基础,栈(stack)可用vector或deque为基础。
stack 栈 #include
quene 队列 #include
priority_queue(优先级队列):实现优先级队列。元素插入是自动按优先级顺序插入,使最高优先级元素首先从优先级队列中取出。常用矢量为基础容器。缺省时priority_queue用vector为基础数据结构。
标准库容器类 |
说明 |
顺序容器 vector(参量) deque(双端队列) list(列表) |
从后面快速插入与删除,直接访问任何元素 从前面或后面快速插入与删除,直接访问任何元素 从任何地方快速插入与删除,双链表 |
关联容器 set(集合) multiset(多重集合) map(映射) multimap(多重映射) |
快速查找,不允许重复值 快速查找,允许重复值 一对一映射,基于关键字快速查找,不允许重复值 一对多映射,基于关键字快速查找,允许重复值 |
容器适配器 stack(栈) queue(队列) priority_queue (优先级队列) |
后进先出(LIFO) 先进先出(FIFO) 最高优先级元素总是第一个出列 |
2、泛型算法
泛型算法不依赖于具体的容器,通用的算法更易于扩充。泛型算法中采用函数对象(function object)引入不同情况下同一算法的差异。它没有使用继承和多态,避免了虚函数的开销,使STL效率更高。
STL最大的优点是提供能在各种容器中通用的算法,例如插入、删除、查找、排序等等。
STL提供70种左右的标准算法。算法只是间接通过迭代子操作容器元素。
算法通常返回迭代子。一个算法通常可用于多个不同的容器,所以称为泛型算法(generic algorithm)。
算法分为:
1.修改容器的算法,即变化序列算法(mutating-sequence algorithm),如copy()、remove()、replace()、swap()等。
2.不修改容器的算法,即非变化序列算法(non-mutating-sequence algorithm),如count()、find()等。
3.数字型算法。
泛型算法分以下几类:
1.查找算法:有13种查找算法用各种策略去判断容器中是否存在一个指定值。equal_range()、lower_bound()和upper_bound()提供对半查找形式。
2.排序和通用整序算法:共有14种排序(sorting)和通用整序(ordering)算法,为容器中元素的排序提供各种处理方法。所谓整序,是按一定规律分类,如分割(partition)算法把容器分为两组,一组由满足某条件的元素组成,另一组由不满足某条件的元素组成。
3.删除和代替算法:有15种删除和代替算法。
4.排列组合算法:有2种算法。排列组合是指全排列。如:三个字符{a,b,c}组成的序列有6种可能的全排列:abc,acb,bac,bca,cab,cba;并且六种全排列按以上顺序排列,认为abc最小,cba最大,因为abc是全顺序(从小到大)而cba是全逆序(从大到小)。
5.生成和改变算法:有6种,包含生成(generate),填充(fill)等等。
6.关系算法:有7种关系算法,为比较两个容器提供了各种策略,包括相等(equal()),最大(max()),最小(min())等等。
7.集合算法:4种集合(set)算法提供了对任何容器类型的通用集合操作。包括并(union),交(intersection),差(difference)和对称差(symmetric difference)。
8.堆算法:有4种堆算法。堆是以数组来表示二叉树的一种形式。标准库提供大根堆(max_heap),它的每个结点的关键字大于其子结点的关键字。
9.算术算法:该类算法有4种,使用时要求包含头文件
3、迭代器
迭代器是面向对象版本的指针,它提供了访问容器或序列中每个对象的方法。这样就可以把算法用于容器所管理的序列。
(1)、C++标准库中有五种预定义迭代器,其功能最强最灵活的是随机访问迭代器。
(2)、功能型
1.反向迭代器
2.插入型迭代器 back_insert_iterator、front_insert_iterator、insert_iterator
类中 push_back、push_front、insert
3.流式迭代器 istream_iterator、ostream_iterator
结合find()算法讨论迭代子与泛型算法的关系。find()定义如下:
template<typename InputIterator,typename T >
InputIterator find(InputIterator first, InputIterator last,
const T value ){
for(;first!=last;++first) if(value==*first) return first;
return last}
可见,泛型算法不直接访问容器的元素,与容器无关。元素的全部访问和遍历都通过迭代子实现。并不需要预知容器类型。
#include
int main()
{
int arr[] = { 21, 34, 58, 675 };
int len = sizeof(arr) / sizeof(arr[0]);
std::vector vec(arr, arr + len);
// std::copy(vec.begin(), vec.end(), std::ostream_iterator(std::cout," "));
std::vector vec1;
std::copy(std::istream_iterator(std::cin), std::istream_iterator(), std::back_insert_iterator>(vec1));
Show(vec1);
return 0;
}
int main()
{
int arr[] = { 21, 34, 58, 675 };
int len = sizeof(arr) / sizeof(arr[0]);
std::vector vec(arr, arr+ len);
std::vector::iterator iit = vec.begin();
*iit = 20;
Show(vec);
std::back_insert_iterator> bii(vec);
*bii = 20;
Show(vec);
return 0;
}
int main()
{
int arr[] = { 21, 34, 58, 675 };
int len = sizeof(arr) / sizeof(arr[0]);
std::vector vec(arr, arr+ len);
std::vector::reverse_iterator eit = vec.rbegin();
while (eit != vec.rend())
{
std::cout << *eit << " ";
eit++;
}
std::cout << std::endl;
return 0;
}
4、函数对象(仿函数) #include
函数对象(function object)的概念与使用使算法摆脱了对不同类型数据个性操作的依赖。
在C++中,为了使程序的安全性更好,采用“引用”来代替指针作为函数的参数或返回值。在C++的泛型算法中类似地采用了“函数对象”(function object)来代替函数指针。函数对象是一个类,它重载了函数调用操作符(operator())。该操作符封装了应该被实现为一个函数的操作。典型情况下,函数对象被作为实参传递给泛型算法。和“引用”一样,“函数对象”独立使用比较少。函数对象亦称拟函数对象(function_like object)和函子(functor)。
函数对象与函数指针相比较有三个优点:第一,函数指针是间接引用,不能作为内联函数,而函数对象可以,这样速度更快;第二,函数对象可以拥有任意数量的额外数据,用这些数据可以用来缓冲当前数据和结果,提高运行质量,当然多数情况下不一定使用,上例中res就是一个额外数据;第三,编译时对函数对象作类型检查。
函数对象有多种来源:
1. 标准库预定义的一组算术,关系和逻辑函数对象;
2. 预定义的一组函数适配器,允许对预定义的函数对象进行特殊化或扩展;
3. 自定义函数对象。
operator()()
一元函数对象
二元函数对象
5、适配器
容器适配器
函数适配器
和容器类一样,函数对象也可以由函数适配器来特殊化或扩展一元(单参数)或二元(双参数)函数对象:
1.绑定器(binder):把二元函数对象中的一个参数固定(绑定),使之转为一元函数,C++标准库提供两种预定义的binder适配器:bind1st和bind2nd,分别绑定了第一或第二个参数。
2.取反器(negator):把函数对象的值翻转的适配器,如原来为小于,用了它后就变成了大于。C++标准库也提供了两种negator适配器:not1和not2。not1用于一元预定义函数对象;not2用于二元预定义函数对象。
6、空间配置器
对象的生成和销毁分离开来
在STL当中,allocator将这四个部分分别拆开,分别对应于alloc::allocate(), ::construct(), ::destroy(), alloc::deallocate(),
一级空间配置器:对(malloc、free)封装,(开辟内存足够大,大于128bt)
二级空间配置器:内存池的实现。(16个自由链表)
如果仅仅只有第一层配置器的话,会造成太多内存碎片。在第二级配置的过程中,每次会配置一大块内存,并维护对应的自由链表,若下次再有相同大小的内存需求,就直接从free-list中拨出;如果释放小额区块,则收到对应的free-list当中去。
STL为了提高效率,在空间的配置上设置了双层配置器,第一级就是使用传统的malloc()和free(), 第二级配置器则视情况采用不同的策略:当申请空间超过128bytes时,调用第一级配置器;当申请空间小于128bytes时,采用memory pool整理方式,而不是一味的求助于第一级配置器,其中,memory pool由malloc()配置而得。具体如下图所示:
当分配空间的时候,查找到free-list对应的下标,接下来就是链表删除的操作了
当返还空间的时候,查到free-list对应的下标,接下来就是链表的插入操作了
当allocate()发现free-list当中没有可用区块时,就调用refill()为其填充空间,新的空间来自内存池,默认获取20个新节点;若空间不足,获取节点个数可能会小于20;若内存池中剩余空间不足所1个申请的区块的大小,则先将剩余空间(若有)编入对应free-list,之后配置heap空间,申请40个对应区块大小+round_up(heap_size>>4)字节的内存,作为内存池容量。若分配失败,则有如下几种选择:1. 查看free-list中足够大的区块是否有尚未使用的空间;2. 调用第一级配置器,抛出异常。