Effective STL学习总结四(30-50)

30条:确保空间足够大

可以用 back_inserter/front_inserter/inserter 在函数里向容器插入元素

    vector vecData;

    copy( istream_iterator( cin ),
    istream_iterator(),back_inserter( vecData ));

        

可以过 reserve预申请足的空间,减少在插入的过程中,由于空间不足,而且导致再申请,影响效率

vecData.reserve( RES_LEN );
31条:了解各种与排序有关的选择
#include
using namespace std;
 
1. partial_sort取得所有元素中,前N个元素排序放在前N的位置,N个元素有序
partial_sort( vecData.begin(),
                vecData.begin() + 3,
                     vecData.end()); 
 
2.nth_element 将最好的n个元素放在前面,并不关心他们是否有序    
    nth_element( vecData.begin(),
        vecData.begin() + 2,
        vecData.end()
        );              

注意第二个参数的不同,同样是对3个元素排序!!

 

3. sort/stable_sort

 

32条:如果确实需要删除元素,则需要在remove这一类算法之后调用erase

remove( vecData.begin(), vecData.end(), "kong" );

虽然调用了remove,但是容器中的元素数目却不会因此而减少,remove不是真正意思上的删除!!

要真正删除需要再调用erase

vecData.erase( remove( vecData.begin(),vecData.end(), "kong" ),    //earse begin

                            vecData.end()                                                           //earse end

                        );

 

只有list的remove能真止删除到元素!!真是有点混乱呀!

 

后面的几个条款平时用得比较少,等有需要时再翻书查了,这里一笔带过

33条:对包含指针的容器使用remove这一类算法时要特别小心

删除容器的指针,并不能删除指针指向的对象

 

34条:注意哪些算法要求使用排序的区间作为参数

 

35条:通过mismatch或lexicographical_compare实现简单的忽略大小写的字符串比较

 

36条:理解copy_if的正确实现

 

37条:使用accumulate或者for_each进行区间统计

#include

 

int sum = accumulate( vecData.begin(), vecData.end(), 0.0 );   //开始位置,结束位置,初始值

方便快捷,也不用手工写循环体

再加上 multiplies( ) 可以将累加的改为累积

 

也可以自定义函数:

struct Point {
    Point( double iX, double iY ):x(iX),y(iY){}
    double x,y;
};
class PointAvg:
    public binary_function
{
public:
    PointAvg():xSum(0),ySum(0),numPoints(0){}
    const Point operator() ( const Point& avg, const Point& p )
    {
        ++numPoints;
        xSum += p.x;
        ySum += p.y;
        return Point( xSum/numPoints, ySum/numPoints );
    }
//    bool operator<( const Point& p )
//    {
//        return xSum < p.x;
//    }
private:
    size_t numPoints;
    double xSum;
    double ySum;

};

调用:

    Point sum = accumulate( vecData.begin(), vecData.end(), Point(0,0), PointAvg() );

    cout<< sum.x <<" "<

 

---------------

也可以使用for_each,先要改写PointAvg

class PointAvg2:
    public unary_functionvoid>
{
public:
    PointAvg2():xSum(0),ySum(0),numPoints(0){}
    void operator() ( const Point& p )
    {
        ++numPoints;
        xSum += p.x;
        ySum += p.y;
    }
    Point result() const
    {
        return Point( xSum/numPoints, ySum/numPoints );
    }
private:
    size_t numPoints;
    double xSum;
    double ySum;

};

    Point sum = for_each( vecData.begin(), vecData.end(),PointAvg2() ).result();

    cout<< sum.x <<" "<

 

 

 

38条:遵循按值传递的原则来设计函数子类

通常使用排序 qsort会以函数指针的形式传比较比函数

 

void qsort( void* base, size_t nmemb, size_t size,  int( *cmpfcn )( const void*, const void*)  );

其实直接使用sort会比qsort效率更高,因为sort传入的比较函数子,是以内联方式展开,而qsort是通过指针调用函数,sort减少了函数调用开销,在密集运算时,才会体现出效率上的优势。

 

对于函数对象的使用,有两点:

1.函数对象必须尽可能小,否则拷贝的开销非常昂贵

2.函数必须是单态的(不是多态),即不能使用虚函数,否则可能出现剥离问题(slicing problem):在对象拷贝过程中,派生部分可能会被去掉,而仅保留了基类部分。

解决办法是:将所需的数据和虚数从函数子类中分离出来,放到一个新的类中;然后在函数子类中包含一个指针,指向这个新类的对象。

比如:有一个包含大量数据且使用了多态性的函数子类:

template<typename T>
class BPFC: public unary_functionvoid>
{
private:
    Widget w;
    int x;
    ...
public:
    virtual void operator()( const T& val ) const;
    ...

};

 

 

那么,就应该创建一个小巧的,单态类,包含一个指针,指向实现类,并将所有数据,虚函数都放在实现类中:

template<typename T>
class BPFCImpl: public unary_functionvoid>
{
private:
    Widget w;
    int x;
    ...
    virtual ~BPFCImpl();
    virtual void operator()( const T& val ) const;
friend class BPFC;
};
 
template<typename T>
class BPFC: public unary_functionvoid>
{
private:
    BPFCImpl * pImpl;
public:
    void operator()( const T& val ) const
    {
        pImpl->operator( val ); //指过分离的子类调用,保持作为函数对象的父类简洁Bridge Pattern
    }

};

1.注意拷贝构造函数能正确处理到BPFCImpl对象

2.引入shared_ptr来管理比较方便

 

 

39条:确保判别式是“纯函数”

 

40条:若一个类是函数子,则应使它可配接

可配接(adaptable)的函数对象,提供了必要的类型定义

如果operator() 只有一个实参,就要继承 unary_function如果有两个,则要继承binary_function

unary_function 必须定义参数类型和返回类型 unary_functionvoid>

同样 binary_function也需要 binary_functionvoid>

not1

bind1st()      创建一个函数对象,该函数对象将值V作为第一个参数A。

bind2nd()    创建一个函数对象,该函数对象将值V作为第二个参数B。

 

关于函数子的详细可看

C++ STL速查手册笔记 http://sinojelly.blog.51cto.com/479153/214389

 

41条:

mem_fun定义:

template

mem_fun_t< R, C> mem_fun(  R ( C::*pmf ) ()  ); //C是类,R是成员函数返回值

 

R ( C::*pmf ) () –> 成员函数指针,mem_fun接受这样的一个成员函数指针,并返回mem_fun_t< R, C>的模板类型,它提供 operator()函数对象,调用通过参数传递进来的成员函数。-> 这样就可以看成是非成员函数的调用了!

 

class widget   {

public:

    widget(){};

    ~widget(){};

    void test();

};

 

void widget::test() {                   //将成员函数传递给for_each

    cout<<"hello"<

}

 

Void g_test( widget * w ) {

         w->test();

}

 

int main()  {

    widget * w =new widget(); // widget  w;

    vector v;

    v.push_back( w );

v.push_back( w );

//第一种

for_each( v.begin(), v.end(), g_test  );

for_each( v.begin(), v.end(),  ptr_fun( g_test)  ); //一样效果,ptr_fun指明是非成员函数

 

//第二,三种

for_each( v.begin(), v.end(),  widget::test  ); //编译不通过

for_each( v.begin(), v.end(), mem_fun( &widget::test) ); //指针类型使用mem_fun,如果是值类型mem_fun_ref.

 

    delete w;

}

 

42条:确保less与operator<具有相同的语义

尽量不要去特列化std名字空间下的模板,大多数情况下有更好的选择 

但是也是可以改写的,下面是一个使用auto_ptr特别化less的例子,可编译通过:

namespace std {
    template<typename T>
    struct less< auto_ptr >: public binary_function< auto_ptr, auto_ptr, bool>
    {
        bool operator()( const auto_ptr& a,
                         const auto_ptr& b ) const
        {
            return less()( a.get(), b.get() );
        }
    };

}

其实,只要实现一个比较函数子,就不用特例std下的less,不过就是需要在声明容器的时候指定比较函数:

struct MaxCompare: public binary_function< Widget, Widget, bool >
{
    bool operator() ( const Widget& lhs, const Widget& rhs ) const
    {
        return lhs.maxSpeed() < rhs.maxSpeed();
    }

};

multisetMaxCompare> widgets;

 

43条:算法调用优先手写的循环

通常我们要调用一个容器类的函数时有:

list lw;
...
for( list::iterator i = lw.begin(); i != lw.end(); ++i )
{
    i->redraw();

}

也可以用for_each完成:

 

 

有几方面的优势:

(1)效率,通常比自己写的循环效率高 -- 自己写部份的lw.end调用了n次,而for_each只调用了1次

(2)正确性,自己写循环更容易出错

(3)可维护性,更加简洁

 

另外还有关于transform/deque insert/compose2等的讨论

 

44条:容器的成员函数优先于同名的算法

特别对于效率至上的程序

如set s;中找一个元素,可以用

s.find( n );

也可以用

find( s.begin(), s.end(), n );

成员函数的查找效率高出100倍以上!!

 

45条:正确区分count, find, binary_search, lower_bound, upper_bound和equal_range

在一个未排序的区间里,count用作存在测试,

if( count( lw.begin(), lw.end(), w ) ) {

... //存在

}

如果用find

if( find( lw.begin(), lw.end(), w ) != lw.end() ) {

... //存在

}

 

从效率来讲, find更高,因为当找到元素时就退出查找,count需要遍历到末尾

 

在一个排序的区间里,有更好的选择

binary_search/lower_bound/upper_bound/equel_range

binary_search只返回元素是否存在,返回bool,不返回位置

 

lower_bound指出元素第一个值出现位置,或者适合于插入的位置(不存在时)

 

upper_bound指向区间中与查找值等价的最后一个元素的下一个位置

 

equel_range返回一对迭代器,第一个指向与lower_bound相同的迭代器,第二个返回与upper_bound相同的迭代器,注意因为是有序区间内,所以lower_bound与upper_bound之间的元素是相等连续的

也可以用equel_range作存在性判断,同时,也完成了count的工作:

typedef vector::iterator VWIter;
typedef pair VWIterPair;
VWIterPair p = equal_range( vw.begin(), vw.end(), w );
if( p.first != p.second ) // 存在
{

}

 

cout<< distance( p.first, p.second );

 

46条:考虑使用函数对象而不是函数作为STL算法的参数

如有

vector v;

...

sort( v.begin(),v.end(), greater() );    //使用函数对象作为比较

 

inline bool doubleGreater( double d1, double d2 )

{    return d1 > d2 ; }

sort( v.begin(),v.end(), doubleGreater );    //使用手写的内联函数

 

通过100w数据对比

greater()        125

doubleGreater              328

通过函数对象效率更高!!

 

其实函数对象greater::operator() 在编译时期就是以内联展开,才会使得效率提高,但是为什么手工写的内联函数没有相同的效果呢?

这是因为:

doubleGreater  是以指针形式传递到 sort, 会以以下方式展开

void sort( vector::iterator first, vector::iterator second,

                       bool (*comp)( double, double ) );    //比较函数

参数comp是一个函数指针,编译器通过这个指针调用doubleGreater函数,也就是因为这个指针,抑制了内联机制!!

这也是一个事实: C++的sort比C的qsort效率高!!

 

47条:避免产生"直写型"(write-only)的代码

 

48条:总是包含(#include)正确的头文件

 

49条:学会分析与STL相关的编译诊断信息

通常在VC中使用STL有长长的警告,如何屏蔽stl中的警告

#pragma warning(disable: 4786)

 

50条:熟悉与STL相关的Web站点

SGI STL http://www.sgi.com/tech/stl/

STLport http://www.stlport.org

Boost http://www.boost.org

boost已经作为C++0x标准库,常用编译器都以来支持

for_each( lw.begin(), lw.end(), mem_fun_ref(&Widget::redraw));

你可能感兴趣的:(C/C++)