《Effective STL》学习笔记(第四部分)

6、仿函数、仿函数类、函数等

函数和类似函数的对象——仿函数——遍布STL。关联容器使用它们来使元素保持有 序;find_if使用它们来控制它们的行为;如果缺少它们,那么比如for_each和transform这样的组件就没有意义了;此外,not1和 bind2nd这样的适配器会积极地产生它们。

条款38:把仿函数类设计为用于值传递

STL函数对象在函数指针之后成型,所以STL中的习惯是当传给函数和从函数返回时函数对象也是值传递的(也就是拷贝)。最好的证据是标准的for_each声明,这个算法通过值传递获取和返回函数对象:

1
2
3
4
5
6
7
8
9
template
 
Function // 注意值返回
 
for_each(InputIterator first,
 
InputIterator last,
 
Function f); // 注意值传递

条款39:用纯函数做判断式

先介绍两个概念–“纯函数”与“判别式”:

纯函数是返回值只依赖于参数的函数。如果f是一个纯函数,x和y是对象,f(x, y)的返回值仅当x或y的值改变的时候才会改变。

判断式是返回bool(或者其他可以隐式转化为bool的东西)。判断式在STL中广泛使用。标准关联容器的比较函数是判断式,判断式函数常常作为参数传递给算法,比如find_if和多种排序算法。

判断式类是仿函数类,它的operator()函数是一个判断式,也就是,它的operator()返回true或false(或其他可以隐式转换到true或false的东西)。

重要结论:判断式函数必须是纯函数

条款40:使仿函数类可适配

假设有一个Widget*指针的list和一个函数来决定这样的指针是否确定一个有趣的Widget:

1
2
3
list widgetPtrs;
 
bool isInteresting( const Widget *pw);

如果要在list中找第一个指向有趣的Widget的指针,可以这样做:

1
2
3
4
5
6
7
8
9
list::iterator i = find_if(widgetPtrs.begin(), widgetPtrs.end(),
 
isInteresting);
 
if (i != widgetPtrs.end()) {
 
... // 处理第一个有趣的指向Widget的指针
 
}

但如果要找第一个指向不有趣的Widget的指针,显而易见的方法却编译失败:

1
2
3
4
5
list::iterator i =
 
find_if(widgetPtrs.begin(), widgetPtrs.end(),
 
not1(isInteresting)); // 错误!不能编译

取而代之的是,必须对isInteresting应用ptr_fun在应用not1之前:

1
2
3
4
5
6
7
8
9
10
11
list::iterator i =
 
find_if(widgetPtrs.begin(), widgetPtrs.end(),
 
not1(ptr_func(isInteresting))); // 没问题
 
if (i != widgetPtrs.end()) {
 
... // 处理第一个指向Widget的指针
 
}

解 释:ptr_fun做的唯一的事是使一些typedef有效,而四个标准函数适配器(not1、not2、bind1st和bind2nd)都需要这些 typedef,一些其他非标准STL兼容的适配器(比如来自SGI和Boost)也需要。提供这些必要的typedef的函数对象称为可适配的,而缺乏 那些typedef的函数对象不可适配。可适配的比不可适配的函数对象可以用于更多的场景,所以只要能做到你就应该使你的函数对象可适配。

条款41:了解使用ptr_fun、mem_fun和mem_fun_ref的原因

假设有一个可以测试Widget的函数,

1
void test(Widget& w); // 测试w,如果没通就标记为“failed”

此外,有一个Widget的容器:

1
vector vw; // vw容纳Widget

要测试vw中的每个Widget,显然可以这么使用for_each:

1
for_each(vw.begin(), vw.end(), test); // 调用#1(可以编译)

但如果test是Widget的成员函数而不是非成员函数,也就是说,Widget支持自我测试:

1
2
3
4
5
6
7
class Widget {
 
public :
 
void test(); // 进行自我测试;如果没通过就把*this标记为“failed”
 
};

如果使用for_each对vw中的每个对象调用Widget::test:

1
for_each(vw.begin(), vw.end(),&Widget::test); // 调用#2(不能编译)

解释:STL里的一个普遍习惯:函数和函数对象总使用非成员函数的语法形式调用,而不直接支持成员函数的调用

为了解决这个问题,mem_fun和mem_fun_ref便提出来了:

1
2
3
4
5
list lpw; // 同上
 
...
 
for_each(lpw.begin(), lpw.end(),mem_fun(&Widget::test)); // 这个现在可以编译了

条款42:确定less<T>表示operator<

7、 使用STL编程

条款43:尽量用算法调用代替手写循环

本条款将证明调用算法通常比手写的循环更优越,有三个理由:

● 效率:算法通常比程序员产生的循环更高效。

● 正确性:写循环时比调用算法更容易产生错误。

● 可维护性:算法通常使代码比相应的显式循环更干净、更直观。

如:有一个支持重画的Widget类:

1
2
3
4
5
6
7
8
9
10
11
class Widget {
 
public :
 
...
 
void redraw() const ;
 
...
 
};

要重画一个list中的所有Widget对象,可以使用这样一个循环:

1
2
3
4
5
6
7
8
9
10
11
12
13
list lw;
 
...
 
for (list::iterator i =
 
lw.begin();
 
i != lw.end(); ++i) {
 
i->redraw();
 
}

也可以用for_each算法来完成:

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

而第二种更优,提倡使用。

条款44:尽量用成员函数代替同名的算法

有 些容器拥有和STL算法同名的成员函数。关联容器提供了count、find、lower_bound、upper_bound和 equal_range,而list提供了remove、remove_if、unique、sort、merge和reverse。大多数情况下,应该 用成员函数代替算法。这样做有两个理由:首先,成员函数更快;其次,比起算法来,它们与容器结合得更好(尤其是关联容器)。

条款45:注意count、find、binary_search、lower_bound、upper_bound和equal_range的区别

你有一个容器或者你有一个由迭代器划分出来的区间——你要找的东西就在里面。你要

怎么完成搜索呢?可用的工具有:count、count_if、find、find_if、binary_search、lower_bound、

upper_bound和equal_range。面对着它们,怎么做出选择?下面是一个总结:

条款46:考虑使用函数对象代替函数作算法的参数

把STL函数对象——化装成函数的对象传递给算法所产生的代码一般比传递真的函数高效,同时,代码会更容易移植。

条款47:避免产生只写代码

“只写代码”指的是容易写,但很难读和理解的代码。举例:

假设有一个vector<int>,要去掉vector中值小于x而出现在至少和y一样大的最后一个元素之后的所有元素。下面代码立刻出现在你脑中吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
vector v;
 
int x, y;
 
...
 
v.erase(
 
remove_if(find_if(v.rbegin(), v.rend(),
 
bind2nd(greater_equal(), y)).base(),
 
v.end(),
 
bind2nd(less(), x)),
 
v.end());

这段代码是正确的,但极难理解和维护,应避免这样的代码。

条款48:总是#include适当的头文件

● 几乎所有的容器都在同名的头文件里,比如,vector在<vector>中声明,list在<list>中声明等。例外的 是<set>和<map>。<set>声明了set和multiset,<map>声明了map和 multimap。

● 除了四个算法外,所有的算法都在<algorithm>中声明。例外的是accumulate(参见条款37)、 inner_product、adjacent_difference和partial_sum。这些算法在<numeric>中声明。

● 特殊的迭代器,包括istream_iterators和istreambuf_iterators(参见条款29),在<iterator>中声明。

● 标准仿函数(比如less<T>)和仿函数适配器(比如not1、bind2nd)在<functional>中声明。

条款49:学习破解有关STL的编译器诊断信息

条款50:让你自己熟悉有关STL的网站

本条款推荐了几个学习STL的网站,分别为:

● SGI STL网站,http://www.sgi.com/tech/stl/

● STLport网站,http://www.stlport.org/

● Boost网站,http://www.boost.org/。(译注:如果访问不了,可以试试http://boost.sourceforge.net/

原创文章,转载请注明: 转载自董的博客

本文链接地址: http://dongxicheng.org/cpp/effective-stl-part4/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值