STL应用篇.docx
《STL应用篇.docx》由会员分享,可在线阅读,更多相关《STL应用篇.docx(20页珍藏版)》请在冰豆网上搜索。
STL应用篇
引子
STL主要是为我们提供了丰富的容器和算法,以至于我们在设计程序的时候只用考虑一些更为复杂的结构,比如树状结构,除此之外就是设计的问题。
另外,STL作为一个开放的源码库,良好的泛型结构设计,高效的算法实现都为开发者提供了很好的参考。
但是,正因为STL的灵活性,以及高效性,使得对STL的使用和了解也不算是很容易,我在平时的工作中也深有体会,因此希望能够系统总结一下STL的基本使用方法,以及分析其部分实现,借以提高程序设计能力。
1.什么是STL?
1)STL是一套很好的框架,最核心的部件包括:
容器,迭代器和算法
2)容器包括两大类:
a)序列型:
vector,deque,list;b)关联型:
set,multiset,map,multimap
3)迭代器是一个“可遍历STL容器内全部或部分元素的”对象。
按照功能不同可分为五类:
input,output,forward,双向迭代器,随机迭代器。
作用:
提供遍历该聚合类型的接口,而不暴露该类型的实现。
2.STL中的容器
2.1什么时候使用vector
Vector的特点:
1)支持运行时加入新的元素,但是在任意位置插入元素效率较低,因为vector的元素是存放在一段连续的内存中,以提高随机访问的效率。
但如果你需要大量在任意位置插入元素,则最好用List,因为List采用的链表来管理内部元素。
2)不提供巨大的操作集,如sort(),find(),max(),min();而将这些通用的操作交给泛型算法提供。
Vector中如何存储类类型?
2.2map是什么意思?
当String作为Key时,map内部是如何计算Key的值的呢?
3.STL中的String类
4.STL中的迭代器
为什么要使用迭代器?
在STL中,迭代器属于某一个容器类,比如vector:
:
Iteratoriter=vect.begin()
迭代器(Iterator)的实现
5.STL中的算法
搜索(search):
排序(sorting):
删除(deletion):
算术(numberic):
关系(relational):
生成(generation):
STL应用篇
概要
STL是泛型编程(GenericProgramming,GP)和C++结合的产物。
STL主要由几个核心部件组成:
迭代器、容器、算法、函数对象、适配器。
容器即物之所属;算法是解决问题的方式;迭代器是对容器的访问逻辑的抽象,是连接算法和容器的纽带,通过添加了一种间接层的方式实现了容器和算法之间的独立。
本文从应用的角度对STL的方方面面进行了简单的介绍。
关键词
STL,C++,应用,SGI,GP,泛型,迭代器,容器,算法
目录
1.概述
2.基础
2.1内联
2.2函数对象
2.3函数模板
2.4类模板
3.迭代器
3.1输入迭代器
3.2输出迭代器
3.3前向迭代器
3.4双向存取迭代器
3.5随机存取迭代器
4.容器
4.1共性
4.2顺序容器
4.2.1vector
4.2.2list
4.2.3deque
4.3关联容器
4.3.1set
4.3.2multiset
4.3.3map
4.3.4multimap
4.3.5其它
5.算法
5.1改变序列的算法
5.2不改变序列的算法
5.3排序以及相关算法
5.4常用数字算法
6.适配器(Adaptor)
6.1容器适配器
6.2迭代器适配器
6.3函数适配器
7.资源
参考文献
1.概述
泛型编程思想最早缘于A.Stepanov提出的部分算法可独立于数据结构的论断。
20世纪90年代初A.Stepanov和MengLee根据泛型编程的理论用C++共同编写了STL。
但直至1998年,STL才成为C++的正式标准。
在后来的几年中,各大主流编译器也都相继加入了对STL的支持,至此STL才开始得到广泛的应用。
STL体现的是泛型编程的核心思想:
独立数据结构和算法(这是一种独立于OO的编程哲学)。
STL主要由几个核心部件组成,即迭代器、容器、算法、函数对象、适配器。
容器即物之所属;算法是解决问题的方式;迭代器是对容器的访问逻辑的抽象,是连接算法和容器的纽带;迭代器通过添加了一种间接层的方式实现了容器和算法之间的独立;函数对象,就是重载了operator()操作符的对象;适配器是通过组合特定的容器实现的一种新的数据结构。
在后续的内容中,我们将对几个核心部件的基础应用进行详细的描述。
2.基础
C++产生的历史背景赋予了C++太多的职责,比如兼容C、综合的编程语言等,这些虽然赋予了C++强大的功能,但同时也扔给了极大的复杂度。
在这篇文章中,我们并不打算将你带入C++的复杂地带,但是需要你有一定的C++基础,比如类、结构等。
STL深深地植根于C++的基础设施,这其中包括了内联、函数对象、函数模板、类模板等。
2.1.内联
内联是C++中一种特殊的语言机制,使用inline来标识。
C++在编译inline标识的函数时,将根据特定的规则将inline函数的代码直接插入到inline函数的调用位置,以此来提高程序的运行效率,但同时也在一定程度上导致了代码的膨胀。
请看下面的例子:
inline全局函数
inlinevoidmax{…};
inline成员函数
classA{
public:
inlinevoidmax{…};
}
2.2.函数对象
函数对象,就是重载了operator()操作符的对象。
相对函数指针,函数对象可以具有状态,更安全,更灵活,基本没有额外的开销。
在STL中,函数对象经常被用作算法的输入参数或容器的实例化的参数。
请看下面的例子:
定义函数对象类
classsLessThan{
public:
LessThan(intval):
m_val(val){}
booloperator()(intval){returnm_valprivate:
intm_val;
};
定义函数对象
LessThanless(5);
调用定义函数对象
less(10);//返回为true
2.3.函数模板
C++中的模板是STL实现的技术基础。
C++中的模板可分为函数模板和类模板。
函数模板抽象了针对不同类型的同一性质的操作。
如针对int/long/string的max操作。
请看下面的例子:
定义求取两个类型的值或对象的最大值的操作
template
Tmax(Ta,Tb){returna>b?
a:
b;}
求取两个int值的最大值
max(0,10);//返回10
求取两个string对象的最大值
max(string(“Hello”),string(“world”));//返回string(“world”)
2.4.类模板
C++中的模板是STL实现的技术基础。
C++中的模板可分为函数模板和类模板。
类模板抽象了针对不同类型的同一类事务。
如针对int/long/string的Stack。
请看下面的例子:
定义一个通用堆栈Stack
template
classStack{
public:
inlinevoidpush(constT&value){…}
Tpop(){…}
voidclear(){…}
boolempty()const{…}
};
声明一个int类型的Stack
typdefStackIntStack;
声明一个string类型的Stack
typdefStackIntStack;
3.迭代器
STL中的迭代器是C++指针的泛化,它在算法和容器之间充当一个中间层,为处理不同的数据结构提供了一致的方式。
迭代器也可以看作是对容器数据结构访问的一种约束。
STL中的迭代器可分为类:
随机存取迭代器(random-access-iterator),双向存取迭代器(bidirectional-access-iterator),前向迭代器(forwarditerator),输入迭代器(input-iterator),输出迭代器(output-iterator)。
它们之间的继承关系如下图:
input-iterator
output-iteartor
forward-iterator:
output-iteartor,input-iterator
bidirectional-access-iterator:
forward-iterator
random-access-iterator:
bidirectional-access-iterator
图一迭代器关系图
3.1.输入迭代器
输入迭代器也可以称之为前向的只读访问器,首先它提供了对容器的只读访问,其次它只能在容器中进行前向迭代(即只提供++操作)。
所有的容器的迭代器都具有输入迭代器的特征。
通过输入迭代器你可以进行下面三种操作:
1.V=*X++
2.V=*X,X++
3.V=*X,++X
注:
V为值,X为迭代器
3.2.输出迭代器
输出迭代器也可以称之为前向的只写访问器,首先它提供了对容器的只写访问,其次它只能在容器中进行前向迭代(即只提供++操作)。
通过输出迭代器你可以进行下面三种操作:
1.*X++=V
2.*X=V,X++
3.*X=V,++X
注:
V为值,X为迭代器
3.3.前向迭代器
前向迭代器继承自输入和输出迭代器,因此具有输入和输出迭代器的所有特征,也即提供对容器数据结构的读写访问但也只具有前向迭代的能力(即只提供++操作)。
因此,你可以对前向迭代器进行操作:
R==S/++R==++S。
请看下面的例子:
定义一个利用前向迭代器进行线性查找的算法
template
ForwardIteratorlinear_search(ForwardIteratorfirst,ForwardIteratorlast,constT&value)
{
for(;first!
=last;++first){
if(*first==value)
returnfirst;
}
returnlast;
}
测试
intia[]={35,3,23};
vectorvec(ia,ia+3);
vector:
:
iteratorit=linear_search(vec.begin(),vec.end(),100);
if(it!
=vec.end())
std:
:
cout<<"Found"<else
std:
:
cout<<"NotFound"<测试结果为:
NotFound
3.4.双向存取迭代器
双向存取迭代器从前向迭代器继承过来,因而具有前向迭代器的所有特征,双向存取迭代器还具有后向访问能力(即只提供--操作)。
请看下面的例子:
定义一个利用双向迭代器排序算法
template
voidsort_me(BidirectionalIteratorfirst,BidirectionalIteratorlast,Comparecomp)
{
for(BidirectionalIteratori=first;i!
=last;++i)
{
BidirectionalIterator_last=last;
while(i!
=_last--)
{
if(comp(*i,*_last))
iter_swap(i,_last);
}
}
}
测试
intia[]={123,343,12,100,343,5,5};
vectorvec(ia,ia+7);
sort_me(vec.begin(),vec.end(),less());
copy(vec.begin(),vec.end(),ostream_iterator(cout,""));
std:
:
cout<测试结果为:
5512100123343343
3.5.随机存取迭代器
随机存取迭代器从双向存取迭代器继承过来,因而具有双向存取迭代器的所有特征。
所不同的是,利用随机存取迭代器你可以对容器数据结构进行随机访问,因而随机存取迭代器还可以定义下面的操作:
1.operator+(int)
2.operator+=(int)
3.operator-(int)
4.operator-=(int)
5.operator[](int)
6.operator-(random-access-iterator)
7.operator>(random-access-iterator)
8.operator<(random-access-iterator)
9.operator>=(random-access-iterator)
10.operator<=(random-access-iterator)
在STL中,随机存取双向迭代器只能作用于顺序容器。
请看下面的例子:
测试输出vector里面的数据
intia[]={123,343,12,100,343,5,5};
vectorvec(ia,ia+7);
for(inti=0;istd:
:
cout<测试结果为:
1233431210034355
4.容器
容器即物之所在。
容器是STL的核心部件之一,是迭代器的依附,是算法作用的目标。
STL中的容器可分为顺序容器(SequenceContainer)和关联容器(AssociativeContainer)。
容器适配器(ContainerAdaptor)是对顺序容器(SequenceContainer)或关联容器(AssociativeContainer)进行包装而得到的一种具有更多约束力(或功能更强大)的容器。
下表列出的是STL中的主要(标准和非标准的)容器:
顺序容器
(SequenceContainer)
容器
备注
vector
stack
非STL标准,
vector的适配器(Adaptor)
list
slist
非STL标准,
list的适配器(Adaptor)
deque
priority_queue
非STL标准,
deque的适配器(Adaptor)
queue
非STL标准,
deque的适配器(Adaptor)
关联容器
(AssociativeContainer)
set
底层数据结构是RB-tree(红黑树)
multiset
底层数据结构是RB-tree(红黑树)
map
底层数据结构是RB-tree(红黑树)
multimap
底层数据结构是RB-tree(红黑树)
hashtable
非STL标准
hash_set
非STL标准,底层数据结构是hashtable
hash_map
非STL标准,底层数据结构是hashtable
hash_multiset
非STL标准,底层数据结构是hashtable
hash_multimap
非STL标准,底层数据结构是hashtable)
4.1.共性
所有的容器都是物之所在,这就决定了它们必然存在很多共性,这些共性包括迭代器、大小等属性。
容器与容器之间的主要区别体现在对数据的操作上。
每类容器都包含四个迭代器:
iterator(正向迭代器)、const_iterator(常正向迭代器)、reverse_iterator(反向迭代器)、const_reverse_iterator(常反向迭代器)。
因此你可以按照下面的方式获取每个容器的相应的迭代器:
获取正向迭代器
C:
:
iteratorit=c.begin();
C:
:
iteratorit=c.end();
获取反向迭代器
C:
:
reverse_iteratorit=c.rbegin();
C:
:
reverse_iteratorit=c.rend()
获取常正向迭代器
C:
:
const_iteratorit=c.begin();
C:
:
const_iteratorit=c.end();
获取常反向迭代器
C:
:
const_reverse_iteratorit=c.rbegin();
C:
:
const_reverse_iteratorit=c.rend()
注:
C为容器类型,c为C的实例
所有容器是数据的存在之处,可以看作的是数据的集合,因此它们都会有大小、是否为空等属性,因此你可以按照下面的方式获取所有的容器的公共属性:
获取容器的大小
c.size();
判断容器是否为空
c.empty();
4.2.顺序容器
顺序容器中所有的元素在容器中的物理位置都是按照特定的次序进行存放的,区别于关联容器的是顺序容器中的元素的位置都是既定的。
被纳入STL标准的顺序容器包括vector、list、deque。
顺序容器之间的共性除了容器之间应有的共性之外,还有对数据操作的接口(非实现)上:
c.push_back
c.pop_back
c.push_front
c.pop_front
c.back
c.front
c.erase
c.remove
4.2.1. vector
vector和数组具有同样的内存处理方式。
不同于数组的是:
数组是静态空间,一旦分配了就不能被改变,因而空间的分配非常地不灵活;vector是动态空间,即空间可以被动态分配,因而空间的分配很灵活。
可以说vector是相对数组的一种更高级的数据结构。
vector中的迭代器的种类为随机存取迭代器(random-access-iterator)。
vector不同于其它顺序容器(SequenceContainer)的是,它具有capacity属性,可以通过vec.capacity()来获取。
请看下面的例子:
构造vector
intia[]={123,343,12,100,343,5,5};
vectorvec(ia,ia+7);//{123,343,12,100,343,5,5}
vectorvec1(2,4);//{4,4}
vectorvec2(4);{0,0,0,0}
输出vector中的所有数据
vector:
:
iteratoritEnd=vec.end();
for(vector:
:
iteratorit=vec.begin();it!
=itEnd;++it)
std:
:
cout<<*it<添加数据
vector:
:
iteratorit(vec.rbegin().base());
vec.insert(it,1);
或
vector:
:
iteratorit=vec.begin();
vec.insert(it,1);
4.2.2. list
list是链表的抽象数据结构(ADT)。
list中的所有数据在空间分配上不一定是连续存放的。
相对vector,list没有capaciy属性。
list中的迭代器的种类为双向存取迭代器(bidirectional-access-iterator)。
请看下面的例子:
构造list
intia[]={123,343,12,100,343,5,5};
listls;
listls(ia,ia+7);//{123,343,12,100,343,5,5}
listls(2,4);//{4,4}
listls(4);{0,0,0,0}
输出list中的所有数据
list:
:
iteratoritEnd=ls.end();
for(list:
:
iteratorit=ls.begin();it!
=itEnd;++it)
std:
:
cout<<*it<4.2.3. deque
deque,即双向链表。
相对vector,deque也是连续空间,但不是vector的连续线性空间。
deque中的迭代器的种类为随机存取迭代器(random-access-iterator)。
deque成员函数如下表:
函数
描述
c.assign(beg,end)
c.assign(n,elem)
将[beg;end)区间中的数据赋值给c。
将n个elem的拷贝赋值给c。
c.at(idx)
传回索引idx所指的数据,如果idx越界,抛出out_of_range。
c.back()
传回最后一个数据,不检查这个数据是否存在。
c.begin()
传回迭代器中的第一个数据。
c.clear()
移除容器中所有数据。
dequec
dequec1(c2)
dequec(n)
dequec(n,elem)
dequec(beg,end)
c.~deque()
创建一个空的deque。
复制一个deque。
创建一个deque,含有n个数据,数据均已缺省构造产生。
创建一个含有n个elem拷贝的deque。
创建一个以[beg;end)区间的deque。
销毁所有数据,释放内存。
c.empty()
判断容器是否为空。
c.end()
指向迭代器中的最后一个数据地址。
c.erase(pos)
c.erase(beg,end)
删除pos位置的数据,传回下一个数据的位置。
删除[beg,end)区间的数据,传回下一个数据的位置。
c.front()
传回地一个数据。
c.get_allocator
使用构造函数返回一个拷贝。
c.insert(pos,elem)
c.insert(pos,n,elem)
c.insert(pos,beg,end)
在pos位置插入一个elem拷贝,传回新数据位置。
在pos位置插入>n个elem数据。
无返回值。
在pos位置插入在[beg,end)区间的数据。
无返回值。
c.max_size()
返回容器中最大数据的数量。
c.pop_back()
删除最后一个数据。
c.pop_front()
删除头部数据。
c.push_back(elem)
在尾部加入一个数据。
c.push_front(elem)
在头部插入一个数据。
c.rbegin()
传回一个逆向队列的第一个数据。