Chinaunix首页 | 论坛 | 博客
  • 博客访问: 298444
  • 博文数量: 63
  • 博客积分: 814
  • 博客等级: 军士长
  • 技术积分: 700
  • 用 户 组: 普通用户
  • 注册时间: 2010-05-09 15:46
文章分类

全部博文(63)

文章存档

2017年(1)

2016年(4)

2015年(13)

2014年(9)

2012年(3)

2011年(33)

分类: C/C++

2015-04-08 11:25:12

1、容器(Containers):各种数据结构,如Vector,List,Deque,Set,Map,用来存放数据,STL容器是一种Class Template,就体积而言,这一部分很像冰山载海面的比率。
2、算法(Algorithms):各种常用算法如Sort,Search,Copy,Erase,从实现的角度来看,STL算法是一种Function Templates
3、迭代器(Iterators):扮演容器与算法之间的胶合剂,是所谓的“泛型指针”,共有五种类型,以及其它衍生变化,从实现的角度来看,迭代器是一种将:Operators*,Operator->,Operator++,Operator--等相关操作予以重载的Class Template。所有STL容器都附带有自己专属的迭代器——是的,只有容器设计者才知道如何遍历自己的元素,原生指针(Native pointer)也是一种迭代器。
4、仿函数(Functors): 行为类似函数,可作为算法的某种策略(Policy),从实现的角度来看,仿函数是一种重载了Operator()Class 或 Class Template。一般函数指针可视为狭义的仿函数。
5、配接器(适配器)(Adapters):一种用来修饰容器(Containers)或仿函数(Functors)或迭代器(Iterators)接口的东西,例如:STL提供的QueueStack,虽然看似容器,其实只能算是一种容器配接器,因为 它们的底部完全借助Deque,所有操作有底层的Deque供应。改变Functor接口者,称为Function Adapter;改变Container接口者,称为Container Adapter;改变Iterator接口者,称为Iterator Adapter。配接器的实现技术很难一言蔽之,必须逐一分析。
6、分配器(Allocators):负责空间配置与管理,从实现的角度来看,配置器是一个实现了动态空间配置、空间管理、空间释放的Class Template

                                                                                                                             ——STL源码剖析》





函数子类(函数对象)的由来:


在STL里,我们经常需要对容器内元素进行批量操作,手写循环确实是个方法,但是,代码冗余,而且效率普遍偏低,这就需要一个批量操作方法,能够对容器内的元素进行批量操作,那么,怎么操作,操作什么,这就需要用户去定义了,你需要告诉程序,你想要什么样的操作,一般人对于这种问题的第一反应就是函数指针,确实,通过传递一个函数指针可以让程序执行特定的操作,这也确实是一个方法,但是按CPP Primer Plus上的原文来说,它有一个很大的弊端,那就是函数指针是必须指定参数以及返回类型的,而STL属于泛型编程,加入用函数指针来操作的话,是违反泛型思想的本质的,因此,这就需要一个能取代函数指针的东西了,而这个东西就是所谓的函数对象。
判别式类是指函数子类,它的operator()函数是一个判别式,也就是说,它的operator()返回true 或false .  
例子:effective 41条 

template
class MeetsThreshold:public std::unary_function{
private:
         const T threshold ;
public:
       MeetsThreshold(const T& threshold);
       bool operator()(const Widget&) const ;

};

struct WidgetNameCompare:public std::binary_function{
       bool operator()(const Widget& lhs,const Widget& rhs) const;
};

注意operator()的类型是const Widget& 但是给unary_function binary_function 的类型是Widget . 一般情况下传递unary_function binary_function非指针类型要去掉const 和 引用。如果operator() 参数是指针类型,情况就不一样了, 二者要一样。如下


struct WidgetNameCompare:public std::binary_function<const Widget*,const Widget*,bool>{
       bool operator()(const Widget* lhs,const Widget* rhsconst;
};

这样就完成了可配接函数对象
list widgets;
list::reverse_iterator i1 = find_if(widgets.rbegin(),widgets.rend(),not1(MeetsTreshold(10)));    //最后一个不符合阈值10的widget


list::reverse_iterator i2 = find_if(widgets.begin(),widgets.end(),bind2nd(WidgetNameCompare(),w));

引入函数子类概念,如上所述,STL 泛型思想就统一了,不必非得引入函数指针而违背泛型原则,这样的STL就正统了。
STL 函数子类: less plus greater.... 需要整理
为了更好理解上面,关于函数子类原理可以查资料了解一下。思想就是类可以保存本类指针成员(非静态),并且重载了operator(),从而随时可以用指针去调用operator();
函数子类是值传递和返回,参见effective  38 条
函数子类判别式应是“纯函数” 参见effective  39 条


ptr_fun men_fun men_fun_ref 的由来


先说函数对象配接器mem_fun mem_fun_ref :

如果我有一个函数f和一个对象x,我希望在x上调用f,而且我在x的成员函数之外。C++给我三种不同的语法来实现这个调用:
 
f(x);    // 语法#1:当f是一个非成员函数
x.f();  // 语法#2:当f是一个成员函数,而且x是一个对象或一个对象的引用
p->f(); // 语法#3:当f是一个成员函数,而且p是一个对象的指针
 
现在,假设我有一个可以测试Widget的函数:
void test();

vector vw;
for_each(vw.begin(),vw.end(), test);                      
//#1 编译通过

但是假如 test是Widget 的成员函数,即Widget 支持自测:
class Widget{
public:
...
        void test();       //自测,如果不通过,*this 标记为“失败”  
...
};


for_each 在生个Widget对象vw上调用自测 Widget::test成员函数:

for_each(vw.begin(),vw.end(),&Widget::test);          //#2 编译不通过

那么容器存的是Widget* 指针呢,情况会怎么样

list lpw;
for_each(lpw.begin(),lpw.end(),&Widget::test);        //#3 编译不通过

看一下for_each源码
template
Function for_each(InputIterator, begin,InputIterator end,Function f)
{
    while(begi != end)  f(*begin++);
}

可见调用语法是#1, 所以#2 #3 不通过。 

所以就有了mem_fun men_fun_ref ,能过这二个函数配接器使#2 #3 调整成 #1 。

所以上面的代码改成如下:
for_each(vw.begin(),vw.end(),mem_fun_ref(&Widget::test));          //#2 通过 正确

list lpw;
for_each(lpw.begin(),lpw.end(),mem_fun(&Widget::test));        //#3 通过 正确
STL 中凡是能接受判别式的地方,就既可以接受一个真正的函数,也可以授受一个判别式类的对象。 反之亦然。

再说下 ptr_fun 函数对接器,ptr_fun 是针对普通函数,就是#1情况,如上所述,#1是正确的方式, 这时不需要Ptr_fun, 什么时需要呢? 就是需要配合其他配接器的时候才需要用它,比如4个标准函数对接器 not1 not2 bind1st bind2nd 时,就需要加上ptr_fun(被迫), 如下面这段话的解释。

ptr_fun函数只是简单的对实际函数进行包装,其真实目的只是定义适配器需要的typedef,一个ptr_fun有关的可选策略是只有当你被迫时才使用它。如果当typedef是必要时你忽略了它,你的编译器将退回你的代码。然后你得返回去添加它。
   
mem_funmem_fun_ref的情况则完全不同。只要你传一个成员函数给STL组件,你就必须使用它们,因为,除了增加typedef(可能是或可能不是必须的)之外,它们把调用语法从一个通常用于成员函数的适配到在STL中到处使用的。当传递成员函数指针时如果你不使用它们,你的代码将永远不能编译。

如果void test() 改成 bool test()const,   mem_fun mem_fun_ref 是不是可以使成员函数替代判别式类 ? 答案是肯定的,看代码

class TestClass1{

    

public:

    TestClass1(int d):data(d)

    {}

    

    bool test() const

    {

        return data>4;

    }

private:

    int data;

};

    TestClass1 *t1 = new TestClass1(1);

    TestClass1 *t2 = new TestClass1(2);

    TestClass1 *t3 = new TestClass1(4);

    TestClass1 *t4 = new TestClass1(5);

    

    std::vector lpt;

    lpt.push_back(t1);

    lpt.push_back(t2);

    lpt.push_back(t3);

    lpt.push_back(t4);


    std::vector::iterator it = std::find_if(lpt.begin(),lpt.end(),std::mem_fun(&TestClass1::test)); //it 指向t4 data =5


还是上面的代码改造一下,test 传入一个参数用来判断,找出大于值大于 val 的元素。

class TestClass1{

    

public:

    TestClass1(int d):data(d)

    {}

    

    bool test(int val) const

    {

        return data>val;

    }

private:

    int data;

};

std::vector::iterator it = std::find_if(lpt.begin(),lpt.end(),std::bind2nd(std::mem_fun(&TestClass1::test),3));        //right it指向了t3 data =4

ok,到此基本介绍完了,这里留个疑问,就是上面为什么是std::bind2nd,test(int val)就只有一个参数。 关于bind1st bind2nd 另一篇细讲。

mem_fun men_fun_ref 源码分析,不错
http://blog.csdn.net/jxlczjp77/article/details/1751917

http://www.cnblogs.com/tracylee/archive/2012/11/15/2772410.html


阅读(1410) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~