Standard Template Library (STL)中有两种构件:容器和泛型算法。
容器:vector
和list
称为顺序性容器用于存放数据;key
和map
称为关联性容器,用于快速查找容器中的元素值。
泛型算法提供了许多可以作用于容器类及数组类型上的操作,达到“与操作对象的类型相互独立”的目的。算法只关心我们需要操作的范围,不关心我们究竟要对什么做操作。
对于不同的数据类型,可以使用模板来简化函数的数量;对于不同的容器,可以使用函数重载,来保证函数的功能的一致性,但这都过于繁琐。
对于array
和vector
来讲,找到一个位于其中的元素的一种解决办法就是输入首指针和容器大小。
template <typename T>
T* find(const T *array, int size, const T &value);
由于array
和vector
都是使用连续内存存放数据,那就可以将容器大小换成尾指针也可以得到需要操作的范围。
template <typename T>
T* find(const T *first, const T *last, const T &value);//last:标兵指针
这样就摆脱了参数列表里存在固定容器的问题。
接下来就要解决如何能够访问容器中特定位置的元素的问题,对于array
,有了首地址以及容器大小,就可以使用下标的方式访问里面的元素,我们也可以对指针借用这种思想。
template <typename T>
T* find(const T *first, const T *last, const T &value)
{
if( ! first || ! last )
return 0;
for( ; first != last; ++first )
if ( *first == value )
return first;
return 0;
}
这里的标兵指针一般都指着容器末尾元素的下一个指针,这样就完成了对array
和vector
的find
操作。但是值得额外注意的是,array
不能定义为空,vector
却可以。所以针对vector
仍需要额外去确定这个容器是否可用find()
函数。
template <typename T>
inline T* begin(const vector<T> &vec)
{
return vec.empty() ? 0 : &vec[0];
}
相应的也可以得到end()
函数,可以对vector
操作了。
对于list
容器,这样的办法却行不通,因为list
中,每一个元素不仅仅包含存储的数据,同时还有前向指针和后向指针(前向指向下一个,后向指向上一个)。对指针做运算只适用于连续的存储空间,这种链表式的存储没办法对指针做加减来访问具体数据。
所以,有另外的技巧可以对list
进行操作而不需要写重载函数来完成这个功能。
由于对于list单纯的对指针运算加减来找寻前后位置的元素已经不可用,那就需要将这个指针抽象出来,将对指针的运算从实际的加减变成抽象的加减。
//对于一个指针的加减
list<T>* Iter;
++Iter;
//并非是让指针增加list存储元素T的数据大小,而是让这个指针指向下一个元素。
将计算从原有的加减中抽象出来就可以很好的解决这种使用非连续内存的问题。于是,find()
函数就可以重写。
template <typename IteratorType, typename elemType>
IteratorType find(IteratorType first, IteratorType last,
const elemType &value)
{
for( ; first != last; ++first)
if(value == *first )
return first;
return last;
}
//Iterator可以这样定义
vector<string>::iterator iter=svec.begin();
如此find()
就有了更大的通用性,但是其中还有一些问题存在,在find()
中equality
计算符如果并没有提供,或者用户希望equality
有新的功能,那就有了一定局限性。一种处理办法是传入一个函数指针;另一种办法是使用function object
,这都是后文提到的方法。
文中还总结了一些泛型算法:
搜索算法(search algorithm):find()
, count()
, adjacent_find()
, find_if()
, count_if()
, binary_search()
, find_first_of()
。
排序(sorting)及次序整理(ordering)算法:merge()
, partial_sort()
, partition()
, random_shuffle()
, reverse()
, rotate()
, sort()
。
复制(copy)、删除(deletion)、替换(substitution)算法:copy()
, remove()
, remove_if()
, replace()
, replace_if()
, swap()
, unique()
。
关系算法(relational):equal()
, includes()
, mismatch()
。
生成(generation)与质变(mutation)算法:fill()
, for_each()
, generate()
, transform()
。
数值(numeric)算法:accmulate()
, adjacent_difference()
, partial_sum()
, inner_product()
。
集合(set)算法:set_union()
, set_difference()
。
对于每种容器以及string类,总有相同或类似的操作要做:
==
)和inequality(!=
)运算符,返回true或false。=
)运算符,将某个容器复制给另一个容器。empty()
会在容器无任何元素时返回true
,否则返回false
。size()
返回容器内目前持有的元素个数clear()
删除所有元素。 每种容器也有begin()
和end()
,这两个函数在前文也提及过。对于容器中的添加删改有着insert()
和erase()
insert()
将单一或者某一范围内的元素插入容器内。erase()
将容器内单一元素或某一范围内的元素删除。 文中提及的顺序性容器有:vector, list, deque
vector是使用一块连续内存存储元素,对于这个容器进行随机访问很容易,对其的末尾元素进行增删操作也很容易。list是一种双向链接的结构,使用的是非连续内存存储元素,对于这个容器进行插入操作很容易。deque与vector大致相似,不过对于deque,它对于首元素进行增删也是很容易的。
使用顺序容器需要包含头文件
#include
#include
#include
一共有五种方式定义容器
//1.产生空的容器
list<string> slist;
vector<int> ivec;
//2.产生特定大小的容器
list<int> ilist(1024);
vector<string> svec(32);
//3.产生特定大小的容器,并为每个容器指定初值
vector<int> ivec(1024,-1);
list<string> slist(32,"unassigned");
//4.通过iterator产生容器
int ia[8]={1,1,2,3,4,8,13,21};
vector<int> fib(ia,ia+8);
//5.根据某个容器产生新的容器
list<string> slist;//空容器
//填充slist...
list<string> slist2(slist);//将slist复制给slist2
相应的操作这个容器有对应的操作函数:pop_back(), push_back()。对末尾插入与删除,除vector之外,还有:push_front(), pop_front()。读取前端和后端会有:front(), back()。每个容器还有通用的插入函数:insert();通用的删除函数:erase()。相应都有重载类型。
PS:很久之前就希望开始写博客来记录自己的一些学习过程,但是一直没有实施,这一次终于由于疫情的影响写下了第一篇学习笔记,希望自己可以持之以恒,坚持下去。
本人是NUAA的在读研究生,目前研一,研究方向是CV,三维重建,愿意交流沟通的朋友可以在博客下留言私信,或者邮箱:[email protected]