[7 使用STL] 43. 算法调用优先于手写的循环

STL是由容器、迭代器、算法以及函数对象组成的。本章主要讨论编程时什么时候使用循环、什么时候使用算法、什么时候使用容器的成员函数。

由于STL算法涉及很广,所以意味着本该写循环完成的任务也可以用STL算法完成。假如有一个支持重画的Widget类:

class Widget {
public:
    ...
    void redraw() const;
    ...
};

当你重画一个list中的所有Widget对象时,可以用一个循环来完成:

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

也可以使用for_each算法来完成:

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

事实上,算法往往优先于循环,有三个理由:

1 效率。

2 正确性。

3 可维护性。

1 效率

(1)使用算法可以减少冗余的计算

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

每次循环,list::end()会被调用一次。但是我们并没有改变list,list::end()无需每次都调用一次。

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

for_each只调用了一次list::end(),其作为参数传递到for_each里了。

(2)算法内部做了优化,效率高于循环

2 正确性

当编写循环代码时,最关键的是要保证使用的迭代器是有效的。假设有一个数组,你想让每个数组元素加上41,然后把它插入到一个deque的前面。如果自己写循环,可能的实现如下:

// 函数中将会向这个数组写入数据,返回值是实际被写入的元素个数
size_t fillArray(double *pArray, size_t arraySize);
double data[maxNum];
deque<double> d;
...
size_t nums = fillArray(data, maxNum);
for (size_t i = 0; i < nums; ++i) {
    // 把数组元素每个加41,然后插入到d的前面
    d.insert(d.begin(), data[i] + 41);
}

上面代码每次插入的位置都是d.begin(),结果是最后插入的元素在deque的最前面,而我们期望的是最后插入的元素在deque的最后面。你也许想用以下方法对它进行修改:

deque<double>::iterator insertLocation = d.begin();
for (size_t i = 0; i < nums; ++i) {
    d.insert(insertLocation++, data[i] + 41);
}

上面代码会产生未定义的行为,每次deque::insert调用的时候,都会使deque中的所有迭代器失效,包括insertLocation。第一次调用insert后,insertLocation就已经失效了。正确的代码如下:

deque<double>::iterator insertLocation = d.begin();
for (size_t i = 0; i < nums; ++i) {
    // 每次insert被调用之后,更新insertLocation,以便保持迭代器有效,然后递增迭代器
    insertLocation = d.insert(insertLocation, data[i] + 41);
    ++insertLocation;
}

花了很长时间才写到正确的代码!来和使用transform算法的代码比较下:

// 将data中的元素复制到d的前部,每个元素加上41
transform(data, data + nums,
          inserter(d, d.begin()),
          bind2nd(plus<double>(), 41));

(1)指定源数据的起点和终点 (2)使用inserter作为目标区间的起始迭代器 (3)正确使用bind2nd(plus<double>(), 41)

推荐使用算法,把迭代器的问题交给算法。

3 可维护性

来看下代码可读性,清晰度。

当你看到一个算法调用的时候,它的名字会指示它的用途。如transform,你会意识到某个函数将被应用到一个区间中的每一个对象,而这些结果将被写到某一个地方;当你看到for,while时,你知道的只是一种循环将要出现。

总结

(1) 如果你要做的工作与一个算法所实现的功能很相近,那么使用算法调用更好。

(2) 如果你的循环很简单,若使用算法来实现的话,却要使用配接器或要求一个单独的函数子类,那么使用循环更好。

(3) 如果你在循环中要做的工作很多且复杂,最好使用算法调用。因为复杂的计算应当当道单独的函数里,那么总可以找到一种办法把这个函数传给一个算法(往往是for_each)。

おすすめ

転載: blog.csdn.net/u012906122/article/details/119989899