面向对象编程关注数据,泛型编程关注算法。
模板和迭代器:都是STL通用方法的组成部分,模板让算法独立于数据类型,迭代器让算法独立于容器类型。例如,对于在数组和在链表中查找特定值节点的find函数,模板提供了存储在容器中的数据类型的通用表示,还需要提供遍历容器中值的通用表示,这就是迭代器,理解迭代器的使用,类比模板。
迭代器需要注意的概念:
1.超尾
STL设计中所有区间都是形如[a, b)的,因此遍历一个完整的容器,最后是一个超尾,即指向最后一个元素后面的地址。对于数组这来说,最后一个元素后面的地址是可以获取到的,可以直接用来判断是否到达超尾,对于链表,可以使用NULL充当超尾。
2.p++和++p
例如,对一个链表的迭代器,需要设计一个迭代器类,为这个类重载++,如下:
iterator & operator++()
{
p = p->next;
return * this;
}
iterator operator++(int)
{
iterator tmp = *this;
p = p->next;
return tmp;
}
这里注意两个问题,一个是返回值,++p先++然后使用p,因此直接返回*this即可,返回值是引用;对于p++,先使用p然后++,因此使用一个临时的迭代器tmp保存当前*this,最后返回的是局部变量tmp,不能使用引用,必须是值。另一个问题是为了区分两种版本,p++使用了一个int类型的参数,这个参数仅仅用来提示编译器,因此不需要提供形参名。
对于p++的理解:p是链表节点,这个节点是迭代器类的private数据。使用迭代器的时候,创建一个迭代器对象,这个对象中的私有数据部分p指向一个链表节点,这个时候说这个迭代器当前访问的节点是p,迭代器能遍历链表,意思就是p一直在移动,每次指向一个链表节点,相当于迭代器一直在移动。要访问迭代器当前结点的值,需要定义一个operator*方法对p解引用 。
使用迭代器的时候,不需要知道内部是怎么实现的。一般不显式使用迭代器,而是使用STL函数,比如需要遍历的时候,使用for_each函数。C++11和ruby等脚本语言中使用如下方式遍历:
int scores[2] = {1, 2};
for (x : scores)
cout << x << endl;
迭代器和容器主导权:容器需要适应迭代器的要求。
STL定义5种迭代器,层级关系如下,其中下层具备上层的所有功能,还有自己额外的功能
输入和输出是对程序来说的,对于上面的find函数,使用输入迭代器即可,含义是从容器中读取值,作为输入传给程序
1.输入迭代器
只读、单通行、只能递增。意思是,每次遍历和上一次遍历的顺序不保证一致,迭代器递增后,先前值不一定可以解引用。find可以使用输入迭代器,因为只需要一次遍历
2.输出迭代器
只写、单通行、只能递增。只写的例子是,cout到屏幕
3.正向迭代器
读写、多次同行。保证每次遍历按照相同的顺序,对正向迭代器递增后,若保存了前面的值,可以解引用,可以加const变成只读
4.双向迭代器
支持递减
5.随机访问迭代器
支持随机访问和用于元素比较的关系运算符
概念、模型、改进:对应面向对象编程中的类、对象、继承,如下:
数组和STL容器:数组不是STL容器,但是对于STL来说,迭代器是STL算法的入口,指针是迭代器,因此STL算法可以使用指针来对基于指针的非STL容器进行操作。
STL算法:所谓算法,即一系列解决问题的清晰指令,对于STL来说,find(),sort(),copy()这些函数就是算法。迭代器是STL算法入口的意思是,这些算法操作的是迭代器,也就是说只能通过迭代器才能使用这些算法
STL预定义迭代器:
1.ostream_iterator类迭代器
是输出流的迭代器,可以用于cout或者fout,格式如下:
ostream_iteratorouter(cout, "");
2.istream_iterator类迭代器
是输入流的迭代器,使用的例子如下:
copy(istream_iterator(cin), istream_iterator(),
dice.begin());
使用cin作为构造函数参数表示使用cin管理的输入流,无参数表示读取失败,例如读取到了文件结尾或者其他问题
3.reverse_iterator类迭代器
对其递增将导致递减,不直接递减的原因是为了使用现有的STL算法,比如copy,在前两个参数的区间之间是递增的,使用如下:
copy(dice.rbegin(), dice.rend(), osteram_iterator(cout, ""));
4.back_insert_iterator,front_insert_iterator,insert_iterator
dice.begin()这样的迭代器不支持自动调整内存大小,会覆盖,这3种迭代器可以,原因是,这些插入迭代器可以使用vector类的push_back等方法,而这些方法内部有动态内存管理机制。使用时将容器类型作为模板参数,将容器对象作为构造函数入参,原因是,需要知道容器类型,这样才能调用vector::push_back等方法。如下:
dice.begin() <===> back_insert_iterator >back_iter(dice)
意思是,这两种都是得到一个关于dice的迭代器。注意copy是一个算法,调整容器内存是根据迭代器种类决定的,而不是copy
for_each算法格式:
#include
void output(const int x)
{
cout << x << endl;
}
for_each(dice.begin(), dice.end(), output);
容器概念、容器类型
7种序列容器:deque、list、queue、priority_queue、stack、vector、forward_list(C++11),序列要求元素线性排序。容器提供了一些方法应该使用,比如a[i]和a.at[i],都是返回第i个元素,at会对i越界的情况进行检查并抛出异常
vector:动态数组,支持随机访问,动态改变大小,尾部操作O(1)时间,头部操作O(N)时间
deque:双端队列,支持随机访问,头尾操作都是O(1)
list:双链表,提供两个方法insert和splice,前者复制副本,后者将原始区间移动
forward_list:C++11中提供,单链表
queue:队列,标准的先进先出队列,是一个适配器,底层使用deque
priority_queue:优先队列,是适配器,底层使用vector,默认是最大的元素放在队首,可以自定义函数对象修改规则
stack:栈,是适配器,底层是vector
C++11中的array不是STL容器,因为长度是固定的,但是和普通数组一样,一些copy,for_each算法可以对其进行操作
关联容器:键值对,通常用树实现,有4种:set、multiset、map、multimap,set是集合,没有键和值的概念,map是键值对
无序关联容器:关联容器是排序的,使用树,查找效率不高,无序关联容器使用hash,提升查找效率,c++11提供4种无序关联容器:unordered_set,unordered_multiset,unordered_map,unordered_multimap