侯捷STL学习笔记

参考文献

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模式):

STL采用的是GP(Generic Programming)的编程模式;
我们知道:OOP(Object-Orientd programming)面向对象思想 数据和操作在同一个类;OOP企图将datas和methods关联在一起;
侯捷STL学习笔记_第1张图片
GP(Generic Programming)却是将datas和methods分隔开来;
采用GP的原因:Containers和Algorithms团队各自可以进行独立开发,两者可以通过Iterator团队进行沟通;
侯捷STL学习笔记_第2张图片


知识点一:
问题:list为什么不能用std::find()?
解释:list不能随机跳转,只能一个个查找下去。std::find只支持RandomAccessIterator(可随意跳转的指针)
知识点二:
所有algorithms,其内最终涉及元素本身的操作,无非就是比大小


二、结构:STL六大部件(components)

1.容器(Containers):    容器存储数据
2.分配器(Allocators):  分配器为容器分配内存
3.算法(Algorithms):    算法处理容器的数据
4.迭代器(Iterators):   迭代器为算法提供访问容器的方式
5.仿函数(Functors):    仿函数为类似不同的类相加减提供支持
6.适配器(Adapters):    仿函数适配器为仿函数使用算法提供支持

侯捷STL学习笔记_第3张图片

// 一个例子说明六大部件!!!

#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的个数

三、容器

前提规定:所有容器规定为 “ 前闭后开 ” 区间(涵盖第一个不涵盖最后一个)
侯捷STL学习笔记_第4张图片

3.1容器分类(宏观上两大类):

【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):同放一个内存,内存放这几个数据的链表

侯捷STL学习笔记_第5张图片

3.2容器特点

(1)、vector:可变大小数组。支持快速随机访问。在尾部之外的位置插入或删除元素可能很慢。
(2)、deque:双端队列。支持快速随机访问。在头尾位置插入/删除速度很快。
(3)、list:双向链表。只支持双向顺序访问。在list中任何位置进行插入/删除操作速度都很快。
(4)、forward_list:单向链表。只支持单向顺序访问。在链表任何位置进行插入/删除操作速度都很快。
(5)、array:固定大小数组。支持快速随机访问。不能添加或删除元素。
(6)、string:与vector相似的容器,但专门用于保存字符。随机访问快。在尾部插入/删除速度快。

具体实现特点参见参考文献3;

容器间实现的互相关系(继承):
侯捷STL学习笔记_第6张图片


四、分配器(allocators)

前附:分配器不建议直接使用,因为其功能是为容器提供内存分配服务的,属于沟通容器和底层系统的部件,无需过多考虑,只要理解结构和功能即可;

不同版本(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;
侯捷STL学习笔记_第7张图片
而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;
侯捷STL学习笔记_第8张图片

五、算法

从语言层面来讲,STL的六大组件,其他的都是类模板(class template),只有算法属于函数模板(function template)

从功能流程上来说:算法是看不见容器的,它对容器一无所知,只能通过迭代器来获得所有需要的信息;
迭代器需要能够回答算法的所有需求,这样才能实现算法的功能;


六、迭代器

概念:迭代器是一种“能够遍历某个序列内的所有元素”的对象。它可以透过与一般指针一致的接口来完成自己的工作。不同的迭代器具有不同的”能力“(行进和存取能力)

迭代器类型:
5种迭代器之间的继承关系:
侯捷STL学习笔记_第9张图片
侯捷STL学习笔记_第10张图片
【1】Input迭代器:
Input迭代器只能一次一个向前读取元素,按此顺序一个个传回元素值。几乎所有迭代器都具备Input迭代器的能力,而且通常更强。纯粹Input迭代器的一个典型例子就是“从标准输入装置读取数据”的迭代器。
下表列出了Input迭代器的各种操作行为:
侯捷STL学习笔记_第11张图片
【2】Output迭代器:
Output迭代器和Input迭代器相反,其作用是将元素值一个个写入。
下表列出Output迭代器的各种操作行为:
侯捷STL学习笔记_第12张图片
【3】Forward(前向)迭代器:
Forward迭代器是Input迭代器和Output迭代器的结合,具有Input迭代器的全部功能和Output迭代器的大部分功能。
下表总结了Forward迭代器的所有操作:
侯捷STL学习笔记_第13张图片
【4】Bidirectional(双向)迭代器:
Bidirectional迭代器在Forward迭代器的基础上增加了回头遍历的能力。换言之,它支持递减操作符,用以进行一步一步的后退操作。
侯捷STL学习笔记_第14张图片
【5】Random Access(随机存取)迭代器:
Random Access迭代器在Bidirectional迭代器基础之上再增加随机存取能力。因此它必须提供“迭代器算术运算”(加减某个偏移量、能处理距离问题)。
下面列出Random Access迭代器的新增操作:
侯捷STL学习笔记_第15张图片
每种迭代器内部要实现下面5种功能,这样才能回答算法对容器的问题,更好的服务于算法:
侯捷STL学习笔记_第16张图片
迭代器相关辅助函数:参考本篇博客
迭代器的使用博客
【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<>为预设排序准则。要使用这些仿函数,必须包含头文件。
下面列出了所有这些仿函数:
侯捷STL学习笔记_第17张图片


八、适配器

本质上是一个桥梁,起到改造、包装作用。

【1】容器适配器
典型代表就是 stack 和 queue,因为两者都是继承了sequence这种底层容器,然后只提供了empty()、size()等几种函数操作接口,这种改造就可以视为是一种容器适配器。

【2】迭代器适配器
参见本篇博客

【3】函数适配器
所谓“函数配接器”,是指能够将仿函数和另一个仿函数(或某个值,或一般函数)结合起来的仿函数。函数配接器也声明在 functional 头文件中。

//例如以下语句:
find_if(coll.begin(),coll.end(),bind2nd(greater()),42)

其中bind2nd是将一个二元仿函数(greater<>)转换成一元仿函数。
它通常将第二参数传给“由第一参数指出”的二元仿函数,作为后者的第二参数。

下面列出了预定义的函数配接器:
侯捷STL学习笔记_第18张图片
在这里插入图片描述

你可能感兴趣的:(C++)