心胸决定格局,眼界决定境界...

STL六大组件

容器(Container)

算法(Algorithm)

迭代器(Iterator)

仿函数(Function object)

适配器(Adaptor)

空间配置器(allocator)

1、容器

作为STL的最主要组成部分--容器,分为向量(vector),双端队列(deque),表(list),队列(queue),堆栈(stack),集合(set),多重集合(multiset),映射(map),多重映射(multimap)。

容器

特性

所在头文件

向量vector

可以用常数时间访问和修改任意元素,在序列尾部进行插入和删除时,具有常数时间复杂度,对任意项的插入和删除就有的时间复杂度与到末尾的距离成正比,尤其对向量头的添加和删除的代价是惊人的高的

<vector>

双端队列deque

基本上与向量相同,唯一的不同是,其在序列头部插入和删除操作也具有常量时间复杂度

<deque>

表list

对任意元素的访问与对两端的距离成正比,但对某个位置上插入和删除一个项的花费为常数时间。

<list>

队列queue

插入只可以在尾部进行,删除、检索和修改只允许从头部进行。按照先进先出的原则。

<queue>

堆栈stack

堆栈是项的有限序列,并满足序列中被删除、检索和修改的项只能是最近插入序列的项。即按照后进先出的原则

<stack>

集合set

由节点组成的红黑树,每个节点都包含着一个元素,节点之间以某种作用于元素对的谓词排列,没有两个不同的元素能够拥有相同的次序,具有快速查找的功能。但是它是以牺牲插入删除操作的效率为代价的

<set>

多重集合multiset

和集合基本相同,但可以支持重复元素具有快速查找能力

<set>

映射map

由{键,值}对组成的集合,以某种作用于键对上的谓词排列。具有快速查找能力

<map>

多重集合multimap

比起映射,一个键可以对应多了值。具有快速查找能力

<map>

STL容器能力表:

 

 

2、算法

算法部分主要由头文件<algorithm>,<numeric>和<functional>组成。< algorithm>是所有STL头文件中最大的一个,它是由一大堆模版函数组成的,可以认为每个函数在很大程度上都是独立的,其中常用到的功能范 围涉及到比较、交换、查找、遍历操作、复制、修改、移除、反转、排序、合并等等。<numeric>体积很小,只包括几个在序列上面进行简单数学运算的模板函数,包括加法和乘法在序列上的一些操作。<functional>中则定义了一些模板类,用以声明函数对象

STL的算法也是非常优秀的,它们大部分都是类属的,基本上都用到了C++的模板来实现,这样,很多相似的函数就不用自己写了,只要用函数模板就可以了。

我们使用算法的时候,要针对不同的容器,比如:对集合的查找,最好不要用通用函数find(),它对集合使用的时候,性能非常的差,最好用集合自带的find()函数,它针对了集合进行了优化,性能非常的高。

 

3、迭代器

它的具体实现在<itertator>中,我们完全可以不管迭代器类是怎么实现的,大多数的时候,把它理解为指针是没有问题的(指针是迭代器的一个特例,它也属于迭代器),但是,决不能完全这么做。

迭代器功能

输入迭代器

Input iterator

、Reads forward

istream

输出迭代器

Output iterator

向前写

Writes forward

ostream,inserter

前向迭代器

Forward iterator

向前读写

Read and Writes forward

 

双向迭代器

Bidirectional iterator

向前向后读写

Read and Writes forward and

backward

list,set,multiset,map,mul

timap

随机迭代器

Random access iterator

随机读写

Read and Write with random

access

vector,deque,array,string

 

4、仿函数

1仿函数(functor)的概念编辑

仿函数(functor),就是使一个类的使用看上去象一个函数。其实现就是类中实现一个operator(),这个类就有了类似函数的行为,就是一个仿函数类了
在我们写代码时有时会发现有些功能的实现的代码,会不断的在不同的成员函数中用到,但是又不好将这些代码独立出来成为一个类的一个成员函数。但是又很想复用这些代码。写一个公共的函数,可以,这是一个解决方法,不过函数用到的一些变量,就可能成为公共的全局变量,再说为了复用这么一片代码,就要单立出一个函数,也不是很好维护。这时就可以用仿函数了,写一个简单类,除了那些维护一个类的成员函数外,就只是实现一个operator(),在类实例化时,就将要用的,非参数的元素传入类中。这样就免去了对一些公共变量的全局化的维护了。又可以使那些代码独立出来,以便下次复用。而且这些仿函数,还可以用关联,聚合,依赖的类之间的关系,与用到他们的类组合在一起,这样有利于资源的管理(这点可能是它相对于函数最显著的优点了)。如果在配合上模板技术和policy编程思想,那就更是威力无穷了,大家可以慢慢的体会。

仿函数,又或叫做函数对象,是STL六大组件之一;仿函数虽然小,但却极大的拓展了算法的功能,几乎所有的算法都有仿函数版本。例如,查找算法find_if就是对find算法的扩展,标准的查找是两个元素相等就找到了,但是什么是相等在不同情况下却需要不同的定义,如地址相等,地址和邮编都相等,虽然这些相等的定义在变,但算法本身却不需要改变,这都多亏了仿函数。仿函数(functor)又称之为函数对象(function object),其实就是重载了()操作符的struct,没有什么特别的地方

仿函数(functor)在各编程语言中的应用编辑

C

C语言使用函数指针回调函数来实现仿函数,例如一个用来排序的函数可以这样使用仿函数
#include <stdlib.h>
/* Callback function */
int compare_ints_function(void*A,void*B)
{
return*((int*)(A))<*((int*)(B));
}
/* Declaration of C sorting function */
void sort(void*first_item,size_t item_size,void*last_item,int(*cmpfunc)(void*,void*));
int main(void)
{
int items[]={4,3,1,2};
sort((void*)(items),sizeof(int),(void*)(items +3), compare_ints_function);
return 0;
}

C++

在C++里,我们通过在一个类中重载括号运算符的方法使用一个函数对象而不是一个普通函数。
class compare_class
{
public:
bool operator()(int A, int B)const{return A < B;}
};
// Declaration of C++ sorting function.
template<class ComparisonFunctor>
void sort_ints(int* begin_items, int num_items, ComparisonFunctor c);
int main()
{
int items[]={4, 3, 1, 2};
compare_class functor;
sort_ints(items, sizeof(items)/sizeof(items[0]), functor);
}
 

如以下代码定义了一个二元判断式functor:

struct IntLess
{
bool operator()(int left, int right) const
{
   return (left < right);
};
};

最基本的仿函数举例:

/**

Function Object

*/

class PrintInt
{
public:
    void operator()(int elem) const
    {
        std::cout<<elem<<' ';
    }
};

void testPrintInt()
{
    std::vector<int> intVec;
    for(int i=1;i<10;++i)
        intVec.push_back(i);
    for_each(intVec.begin(),intVec.end(),
             PrintInt());    ///PrintInt()产生此类型的一个临时对象,当for_each算法的一个参数
    std::cout<<std::endl;
}

STL中for_each()算法大致实现如下:
template <typename Iterator, typename operation>
operation for_each(Iterator b,Iterator e,operation op)
{
    while(b++ != e)
    {
        op(*b);
    }
    return op;
}

在本例中,for_each()调用PrintInt::operator(*b);

为什么要使用仿函数呢?

1).仿函数比一般的函数灵活

2).仿函数有类型识别,可以作为模板参数

3).执行速度上仿函数比函数和指针要更快的。

怎么使用仿函数?

除了在STL里,别的地方你很少会看到仿函数的身影。而在STL里仿函数最常用的就是作为函数的参数,或者模板的参数。

在STL里有自己预定义的仿函数,比如所有的运算符,=,-,*,、比如'<'号的仿函数是less

template<class _Ty>
struct less   : public binary_function<_Ty, _Ty, bool>
{ // functor for operator<
        bool operator()(const _Ty& _Left, const _Ty& _Right) const
                   { // apply operator< to operands
                              return (_Left < _Right);
                   }
};

从上面的定义可以看出,less从binary_function<...>继承来的,那么binary_function又是什么的?

template<class _Arg1, class _Arg2, class _Result>
struct binary_function
{ // base class for binary functions
        typedef _Arg1 first_argument_type;
        typedef _Arg2 second_argument_type;
      typedef _Result result_type;
};

其实binary_function只是做一些类型声明而已,别的什么也没做,但是在STL里为什么要做这些呢?如果你要阅读过STL的源码,你就会发现,这样的用法很多,其实没有别的目的,就是为了方便,安全,可复用性等。但是既然STL里面内定如此了,所以作为程序员你必须要遵循这个规则,否则就别想安全的使用STL。

比如我们自己定一个仿函数。可以这样:

template <typename type1,typename type2>
class func_equal :public binary_function<type1,type2,bool>
{
        inline bool operator()(type1 t1,type2 t2) const//这里的const不能少
            {
                 return t1 == t2;//当然这里要overload==

             }
}

我们看这一行: inline bool operator()(type1 t1,type2 t2) const//这里的const不能少
inline是声明为内联函数,我想这里应该不用多说什么什么了,关键是为什么要声明为const的?要想找到原因还是看源码,加入如果我们这里写一行代码,find_if(s.begin(),s.end(),bind2nd(func_equal(),temp)),在bind2nd函数里面的参数是const类型的,const类型的对象,只能访问cosnt修饰的函数

binary_function(二元函数)相对的是unary_function(一元函数),其用法同binary_function

struct unary_function {
typedef _A argument_type;
typedef _R result_type;
};

注:仿函数就是重载()的class,并且重载函数要为const的,如果要自定义仿函数,并且用于STL接配器,那么一定要从binary_function或者,unary_function继承

 

5、适配器

适配器是用来修改其他组件接口的STL组件,是带有一个参数的类模板(这个参数是操作的值的数据类型)。STL定义了3种形式的适配器:容器适配器,迭代器适配器,函数适配器。

容器适配器:包括栈(stack)、队列(queue)、优先(priority_queue)。使用容器适配器,stack就可以被实现为基本容器类型(vector,dequeue,list)的适配。可以把stack看作是某种特殊的vctor,deque或者list容器,只是其操作仍然受到stack本身属性的限制。queue和priority_queue与之类似。容器适配器的接口更为简单,只是受限比一般容器要多。

迭代器适配器:修改为某些基本容器定义的迭代器的接口的一种STL组件。反向迭代器和插入迭代器都属于迭代器适配器,迭代器适配器扩展了迭代器的功能。

函数适配器:通过转换或者修改其他函数对象使其功能得到扩展。这一类适配器有否定器(相当于"非"操作)、绑定器、函数指针适配器。函数对象适配器的作用就是使函数转化为函数对象,或是将多参数的函数对象转化为少参数的函数对象。

例如:

在STL程序里,有的算法需要一个一元函数作参数,就可以用一个适配器把一个二元函数和一个数值,绑在一起作为一个一元函数传给算法。

bind1st和bind2nd函数用于将一个二元算子(binary functor,bf)转换成一元算子(unary functor,uf)。

我们在做比较的时候所写的表达式像 x > k ,x < k,这里的k是一个参数表示你程序里面的表达式要和k值去比较。上面这两个表达式对应的应该是bind2nd ,简单的理解就是把k作为比较表达式的第二个参数。如果使用bind1st则对应的表达式是 k > x,k < x,也就是把k作为比较表达式的第一个参数。
例如:
find_if(coll.begin(), coll.end(), bind2nd(greater <int>(), 42));
这句话就是找coll中第一个大于42的元素。
greater <int>(),其实就是">"号,是一个2元函数
bind2nd的两个参数,要求一个是2元函数,一个是数值,结果是一个1元函数。
bind2nd就是个函数适配器

 

eg:

1、首先看一个容器的操作:
void f(std::vector<int> &vect)
{
    std::vector<int>::iterator firstOne;
    for (firstOne = vect.begin();
    firstOne != vect.end();
    ++firstOne)
    {
        doSomething(*firstOne, "Some string literal");
    }
}

这个f调用就是完成对容器vect的迭代,并在迭代过程中,处理iter值和一个不变的字符串,至于dosomething完成什么功能,根本不必关心。

这里有人肯定要说,不是用for_each就可完成这种功能吗,可for_each只接受一个参数的函数。如下所示:
funcation for_each(iterator beg_it, iterator end_it, funcation func);

那么怎样让func能够绑定当前iterator值和一个不变的字符串呢?如果成功,问题就OK了。

在解决这个问题必须要用到适配器函数,如bind1nd, bind2st之流的捆绑函数。在解析这两个函数之前,先看看Funcation的类声明:

2、Funcation的类声明:
template <class Arg, class Arg2, class Res>
struct binary_function {
    typedef Arg first_argument_type;
    typedef Arg2 second_argument_type;
    typedef Res result_type;
};

ok, 我们自己的func也可继承这个基类,哈哈,改进后的dosomething声明:
class dosomething:public
std::binary_funcation<int, const char *, void>
{
//其中,int是我们当前iterator值类型,const char *是要传递的固定不变的字符串,void是我们func的返回值。看看下面的重载() 声明,就明白了:
public:
       void  operator()(int ival, const char *s)
       {
             // 在这里添加你想干的事情,记住,ival就是当前iterator的值, s是需要绑定的不变字符串
     }
};


3、bind1st和bind2nd的选择
从如上的dosomething可以看出,需要绑定的是s这个不变字符串,是第二个参数,所以当然选择bind2nd,如果dosomething的声明如下:

class dosomething:public
std::binary_funcation<const char *,  int , void>
{
//其中,int是我们当前iterator值类型,const char *是要传递的固定不变的字符串,void是我们func的返回值。看看下面的重载() 声明,就明白了:
public:
       void  operator()(const char *s, int)
       {
             // 在这里添加你想干的事情,记住,ival就是当前iterator的值, s是需要绑定的不变字符串
     }
};

那么就当然选择bind1st了,因为需要绑定的不变参数s是第一个参数。
我靠,原来这两个函数没什么本质区别,只是根据用户定义函数参数的顺序有关

 

6、空间配置器

STL的内存配置器在我们的实际应用中几乎不用涉及,但它却在STL的各种容器背后默默做了大量的工作,STL内存配置器为容器分配并管理内存。统一的内存管理使得STL库的可用性、可移植行、以及效率都有了很大的提升。

SGI-STL的空间配置器有2种,一种仅仅对c语言的malloc和free进行了简单的封装,而另一个设计到小块内存的管理等,运用了内存池技术等。在SGI-STL中默认的空间配置器是第二级的配置器。

SGI使用时std::alloc作为默认的配置器。

A).alloc把内存配置对象构造的操作分开,分别由alloc::allocate()::construct()负责,同样内存释放对象析够操作也被分开分别由alloc::deallocate()和::destroy()负责。这样可以保证高效,因为对于内存分配释放和构造析够可以根据具体类型(type traits)进行优化。比如一些类型可以直接使用高效的memset来初始化或者忽略一些析构函数。对于内存分配alloc也提供了2级分配器来应对不同情况的内存分配。

B).第一级配置器直接使用malloc()和free()来分配和释放内存。第二级视情况采用不同的策略:当需求内存超过128bytes的时候,视为足够大,便调用第一级配置器;当需求内存小于等于128bytes的时候便采用比较复杂的memeory pool的方式管理内存

C).无论allocal被定义为第一级配置器还是第二级,SGI还为它包装一个接口,使得配置的接口能够符合标准即把配置单位从bytes转到了元素的大小:

                  template<class T, class Alloc>

class simple_alloc

{

public:

     static T* allocate(size_t n)

     {

         return 0 == n ? 0 : (T*)Alloc::allocate(n * sizeof(T));

     }

 

     static T* allocate(void)

     {

         return (T*) Alloc::allocate(sizeof(T));

     }

 

     static void deallocate(T* p, size_t n)

     {

         if (0 != n) Alloc::deallocate(p, n * sizeof(T));

     }

 

     static void deallocate(T* p)

     {

         Alloc::deallocate(p, sizeof(T));

     }

}   

 

d).内存的基本处理工具,它们均具有commt or rollback能力。

template<class InputIterator, class ForwardIterator>

ForwardIterator

uninitialized_copy(InputIterator first, InputIterator last, ForwardIterator result);

 

template<class ForwardIterator, class T>

void uninitialized_fill(ForwardIterator first, ForwardIterator last, const T& x);

 

template<class ForwardIterator, class Size, class T>

ForwardIterator

uninitialized_fill_n(ForwardIterator first, ForwardIterator last, const T& x)

posted @ 2014-01-24 23:40  WELEN  阅读(3490)  评论(0编辑  收藏