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

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

    vector<string> vecData;

    copy( istream_iterator<string>( cin ),
    istream_iterator<string>(),back_inserter( vecData ));

        

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

vecData.reserve( RES_LEN );

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

 

3. sort/stable_sort

 

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能真止删除到元素!!真是有点混乱呀!

 

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

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

 

 

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

 

 

#include <numeric>

 

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

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

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

 

也可以自定义函数:

struct Point {
    Point( double iX, double iY ):x(iX),y(iY){}
    double x,y;
};
class PointAvg:
    public binary_function<Point,Point,Point>
{
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 <<" "<<sum.y<< endl;

 

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

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

class PointAvg2:
    public unary_function<Point,void>
{
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 <<" "<<sum.y<< endl;

 

 

 

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_function<T,void>
{
private:
    Widget w;
    int x;
    ...
public:
    virtual void operator()( const T& val ) const;
    ...

};

 

 

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

template<typename T>
class BPFCImpl: public unary_function<T,void>
{
private:
    Widget w;
    int x;
    ...
    virtual ~BPFCImpl();
    virtual void operator()( const T& val ) const;
friend class BPFC<T>;
};
 
template<typename T>
class BPFC: public unary_function<T,void>
{
private:
    BPFCImpl<T> * 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_function<T,void>

同样 binary_function也需要 binary_function<T,T,void>

not1

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

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

 

关于函数子的详细可看

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

 

41条:

mem_fun定义:

template <typename R, typename C>

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"<<endl;

}

 

Void g_test( widget * w ) {

         w->test();

}

 

int main()  {

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

    vector<widget*> 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<T>与operator<具有相同的语义

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

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

namespace std {
    template<typename T>
    struct less< auto_ptr<T> >: public binary_function< auto_ptr<T>, auto_ptr<T>, bool>
    {
        bool operator()( const auto_ptr<T>& a,
                         const auto_ptr<T>& b ) const
        {
            return less<T*>()( 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();
    }

};

multiset<Widget,MaxCompare> widgets;

 

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

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

list<Widget> lw;
...
for( list<Widget>::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<int> 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<Widget>::iterator VWIter;
typedef pair<VWIter,VWIter> VWIterPair;
VWIterPair p = equal_range( vw.begin(), vw.end(), w );
if( p.first != p.second ) // 存在
{

}

 

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

 

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

如有

vector<double> v;

...

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

 

inline bool doubleGreater( double d1, double d2 )

{    return d1 > d2 ; }

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

 

通过100w数据对比

greater<double>()        125

doubleGreater              328

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

 

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

这是因为:

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

void sort( vector<double>::iterator first, vector<double>::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)

 

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

Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library》是一本由Scott Meyers撰写的经典书籍,旨在帮助读者更好地使用C++标准模板库(STL)。 这本书总结50条关于STL使用技巧和最佳实践。以下是其中的一些要点: 1. 使用STL容器时,确保在需要插入和删除元素时优先选择vector而不是list。因为vector在插入和删除操作上效率更高。 2. 当需要快速查找某个元素时,使用set或map等基于红黑树的容器。 3. 如果需要按照某个自定义的排序规则来对容器中的元素进行排序,可以使用set或者priority_queue。 4. 在迭代器失效的情况下,不要试图重新利用这些迭代器,而应该重新获取新的迭代器。 5. 使用算法库中提供的算法,如for_each、find、copy等,可以减少编写重复代码的工作。 6. 使用STL中的函数对象函数适配器,可以更好地处理算法的复杂需求。 7. 使用STL中的智能指针(如shared_ptr和unique_ptr)来管理动态分配的内存,可以避免内存泄漏的问题。 8. 避免使用裸指针和new/delete操作符,在可能的情况下使用STL中的容器和智能指针。 9. 使用STL提供的容器适配器,如stack、queue和priority_queue,可以方便地实现特定功能的容器。 10. 注意STL中的异常处理机制,正确处理异常可以提高程序的健壮性。 总之,通过学习并遵循《Effective STL: 50 Specific Ways to Improve Your Use of the Standard Template Library》中的技巧和最佳实践,我们可以更有效地利用STL,提高程序的性能和可维护性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值