1.侯捷-STL与泛型编程笔记(第一讲、容器概述——0.概述)
2.侯捷-STL与泛型编程笔记(第二讲、源码分析——0.源码)
3.STL"源码"剖析-重点知识总结
4.STL学习笔记
5.STL学习笔记:仿函数
6.STL学习笔记:迭代器适配器
C++ 标准库(STL大部分属于C++标准库)—— STL和标准库的关系
STL 标准模板库
标准库以header files形式呈现(头文件)
C++标准库的header files不带副档名,如:#include
新式C header files,如:#include
旧式C header files,如:include
新式headers内组件封装在namespace “std”(新式统一规定都在std)
using namespace std;(全部加载)
using std::cout;(cout单个)
STL采用的是GP(Generic Programming)的编程模式;
我们知道:OOP(Object-Orientd programming)面向对象思想 数据和操作在同一个类;OOP企图将datas和methods关联在一起;
GP(Generic Programming)却是将datas和methods分隔开来;
采用GP的原因:Containers和Algorithms团队各自可以进行独立开发,两者可以通过Iterator团队进行沟通;
知识点一:
问题:list为什么不能用std::find()?
解释:list不能随机跳转,只能一个个查找下去。std::find只支持RandomAccessIterator(可随意跳转的指针)
知识点二:
所有algorithms,其内最终涉及元素本身的操作,无非就是比大小
1.容器(Containers): 容器存储数据
2.分配器(Allocators): 分配器为容器分配内存
3.算法(Algorithms): 算法处理容器的数据
4.迭代器(Iterators): 迭代器为算法提供访问容器的方式
5.仿函数(Functors): 仿函数为类似不同的类相加减提供支持
6.适配器(Adapters): 仿函数适配器为仿函数使用算法提供支持
// 一个例子说明六大部件!!!
#include
#include
#include
#include
using namespace std;
int main()
{
int ia[6] = {27, 210, 12, 47, 109, 83};
vector> vi(va,ia + 6);//<>符号表示模板,allocator是一个分配器模板,一般vector都会自动默认使用分配器
cout << count_if(vi.begin(), vi.end(), not1(bind2nd(less(), 40)));
return 0;
}
// vector是一个容器containers
// count_if是一个算法algorithm,计算vi里面的个数
// vi.begin(), vi.end()是一个迭代器iterator
// less是一个仿函数function
// bind2nd是一个适配器function adapter,绑定第二个参数为40
// notl是一个适配器function adapter,表示否定
// 整个表达,vi大于等于40的个数
前提规定:所有容器规定为 “ 前闭后开 ” 区间(涵盖第一个不涵盖最后一个)
【1】顺序容器(Sequence Containers):
(1)Array(固定元素个数)C++11
(2)Vector(尾部个数可以扩充)
(3)Deque(头尾个数可以扩充)
(4)List(双向链表)
(5)Forward-List(单向链表)C++11
【2】关联容器(Associative Containers):
(1)Set/Multiset(key=value)
(2)Map/Multimap(key对应value;multimap允许重复元素,map不允许有重复)
不定序容器(Unordered Containers)(本质上属于关联容器)
HashTable Separate chaining(不定序容器使用hashtable):同放一个内存,内存放这几个数据的链表
(1)、vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
(2)、deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
(3)、list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
(4)、forward_list:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。
(5)、array:固定大小数组。支持快速随机访问。不能添加或删除元素。
(6)、string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。
具体实现特点参见参考文献3;
不同版本(VC6,VC2013等等)共同的调用流程:allocators—>调用new—>调用malloc;
Operator new() 和 malloc() 区别——new最后都是用到malloc;(所有的最后都归结到malloc()!!!)
示例:allocators简单用法(vc6版本)
Int p = allocator().allocator(512,(int)0); //第一个参数是个数,第二个是获取的类型
allocator().deallocate(p, 512);
其他版本 allocators 分配的都是一块一块的,每个块都有一个cookie(00000041),其中有记录这个块多大的信息,但是小块空间的话,会浪费资源,因为每个都有cookie;
而GNU版本的allocators和alloc不一样,容器用的分配器是alloc(2.9版本)。它的分配方式是一串一串的,有16块,每个占8bit,每块不带cookie;节省了cookie 的开销浪费(一次拿一串,每次malloc都要一个cookie)
简单来说:
allocators是一次malloc一块,每次都有cookie;
Alloc是一次拿一串(8的倍数),这一串只有一个cookie;
G4.9版本下的分配器是又换到了一次malloc一块的版本-allocator,2.9版本是使用alloc;
从功能流程上来说:算法是看不见容器的,它对容器一无所知,只能通过迭代器来获得所有需要的信息;
迭代器需要能够回答算法的所有需求,这样才能实现算法的功能;
迭代器类型:
5种迭代器之间的继承关系:
【1】Input迭代器:
Input迭代器只能一次一个向前读取元素,按此顺序一个个传回元素值。几乎所有迭代器都具备Input迭代器的能力,而且通常更强。纯粹Input迭代器的一个典型例子就是“从标准输入装置读取数据”的迭代器。
下表列出了Input迭代器的各种操作行为:
【2】Output迭代器:
Output迭代器和Input迭代器相反,其作用是将元素值一个个写入。
下表列出Output迭代器的各种操作行为:
【3】Forward(前向)迭代器:
Forward迭代器是Input迭代器和Output迭代器的结合,具有Input迭代器的全部功能和Output迭代器的大部分功能。
下表总结了Forward迭代器的所有操作:
【4】Bidirectional(双向)迭代器:
Bidirectional迭代器在Forward迭代器的基础上增加了回头遍历的能力。换言之,它支持递减操作符,用以进行一步一步的后退操作。
【5】Random Access(随机存取)迭代器:
Random Access迭代器在Bidirectional迭代器基础之上再增加随机存取能力。因此它必须提供“迭代器算术运算”(加减某个偏移量、能处理距离问题)。
下面列出Random Access迭代器的新增操作:
每种迭代器内部要实现下面5种功能,这样才能回答算法对容器的问题,更好的服务于算法:
迭代器相关辅助函数:参考本篇博客
迭代器的使用博客
【1】advance()可令迭代器前进
【2】distance()可以处理迭代器之间的距离
【3】iter_swap()可交换两个迭代器所指内容
仿函数(functor),就是使一个类的使用看上去像一个函数。
具体实现就是类中重载了一个operator(),由此生成的类对象,在使用时就有了类似函数的行为,就是一个仿函数类了。
//一个示例:
class X{
public:
return-value operator()(arguments) const;
...
};
//然后就可以把这个类别的对象当做函数调用:
X fo;
...
fo(arg1,arg2) //等价于fo.operator()(arg1,arg2);
仿函数的优势:
1.仿函数比一般函数更灵巧,因为它可以拥有状态。
2.每个仿函数都有其型别。因此可以将仿函数的型别当做template参数传递。
3.执行速度上,仿函数通常比函数指针更快。
仿函数的作用:
1.仿函数可当做排序准则
2.拥有内部状态的仿函数
3.for_each()的返回值
C++标准程序库提供了许多预定义的仿函数。
对对象排序或进行比较时,一般都以less<>为预设排序准则。要使用这些仿函数,必须包含头文件。
下面列出了所有这些仿函数:
本质上是一个桥梁,起到改造、包装作用。
【1】容器适配器
典型代表就是 stack 和 queue,因为两者都是继承了sequence这种底层容器,然后只提供了empty()、size()等几种函数操作接口,这种改造就可以视为是一种容器适配器。
【2】迭代器适配器
参见本篇博客
【3】函数适配器
所谓“函数配接器”,是指能够将仿函数和另一个仿函数(或某个值,或一般函数)结合起来的仿函数。函数配接器也声明在 functional 头文件中。
//例如以下语句:
find_if(coll.begin(),coll.end(),bind2nd(greater()),42)
其中bind2nd是将一个二元仿函数(greater<>)转换成一元仿函数。
它通常将第二参数传给“由第一参数指出”的二元仿函数,作为后者的第二参数。