《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/
總結
以上是生活随笔為你收集整理的《Effective STL》学习笔记(第四部分)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Effective STL》学习笔记(
- 下一篇: 《Effective C++》读书笔记(