每一个算法接受至少一对用来指示将被操作对象区间的迭代器,比如,min_element 可以找出此区间中的最小的值,而 accumulate 则对区间内的元素作某种形式的整体求和运算,partition 将区间内的元素分割为满足和不满足某某判决条件的两个部分。算法执行时,他们进行检查指示给它的区间中的每一个元素,并且按照所期望的方式进行: 从区间中的起点循环到结束点。有一些算法,比如 find 和 find_if,可能在遍历完成前就返回了,但是即使是这些算法,内部都包含一个循环。
算法内部是一个循环,STL 的广泛涉及面意味着,很多要用循环实现的任务,可以改用算法实现。
1 class Widget 2 { 3 public: 4 void redraw() const; 5 . . . 6 }
使用循环
1 list<Widget> lw; 2 . . . 3 for(list<Widget>::iterator i = lw.begin(); i != lw.end(); i++) 4 i->redraw();
使用 for_each 算法
1 for_each( lw.begin(), lw.end(), mem_fun_ref(&Widget::redraw) );
有三个理由:
效率: 算法通常比程序员产生的循环更高效
正确性: 写循环时比调用算法更容易产生错误
可维护性: 算法通常使代码比相应的显示循环更干净、更直观
效率:算法往往比循环减少了大量的函数调用次数(上例中,多次调用 lw.begin()),STL实现者知道 begin 和 end 用的很频繁,多以尽可能把他们实现得最高效,几乎肯定的是 inline 它们。实现者可以利用知道的容器具体实现优势,用库的使用者无法采用方式来优化遍历。所有STL算法使用的计算机科学都比一般的 C++ 程序员能拿的出的算法更复杂。几乎不可能被打败的 sort 及同组算法(比如,stable_sort(), nth_element()等);适用于有序区间的搜索算法(比如,binary_search, lower-bound等);就算很平凡的任务,比如从连续内存容器中除去一些对象,使用 erase-remove 惯用法都比绝大多数程序员写得更高效.
正确性:写循环时,比麻烦的事在于确保所有使用的迭代器(a)有效,并且(b)指向所期望的地方,假设有一个数组,想获得每一个元素,把它加上41,然后将结果插入一个 deque 的前端
1 size_t fillArray( double* pArray, size_t arraySize );//函数向数组写入数据,返回写入double个数 2 double data[maxNumDoubles]; 3 deque<double> d; 4 . . . 5 size_t numDoubles = fillArray( data, maxNumDoubles ); 6 for(size_t i = 0; i < numDoubles; i++) 7 d.insert( d.begin(), data[i] + 41 ); // 每个数据在 d 的前端插入 data[i] + 41 8 // 这段代码有一个bug!
这可以执行,但是插入元素与在 data 中对应的元素是反序的
不想反序可能想这样修改:
1 deque<double>::iterator insertlocation = d.begin(); // 记下d的起始迭代器 2 for( size_t; i < numDoubles; i++ ) 3 d.insert( insertLocation++, data[i] + 41 );// 插入data[i]+41,然后insertLocation递增 4 // 这段代码也有 bug
这样使得每次调用 deque::insert 后,都导致所有指向 deque 内部的迭代器失效
改为:
1 deque<double>::iterator insertlocation = d.begin(); 2 for( size_t i = 0; i < numDoubles; i++ ) 3 { 4 insertLocation = d.insert( insertLocation, data[i] + 41 ); 5 ++insertLocation; 6 }
调用算法 transform:
1 transform(data, data+numDoubles, inserter(d, d.begin()), bind2nd(plus<double>(), 41));
把迭代器扔给算法,让他们考虑操纵迭代器时的各种诡异