《Effective STL》学习笔记(第三部分)
4、 迭代器
標準STL容器提供了四種不同的迭代器:iterator、const_iterator、reverse_iterator和const_reverse_iterator
為什么有四種迭代器?它們之間的關(guān)系是什么?它們可以互相轉(zhuǎn)化嗎?在調(diào)用算法和STL實用函數(shù)時不同類型可以混合使用嗎?這些類型是怎么關(guān)聯(lián)到容器和它們的成員函數(shù)的?本章回答了這些問題,同時介紹了一個比通常更值得注意的迭代器類型:istreambuf_iterator。
條款26:盡量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator
每個標準容器類都提供四種迭代器類型。對于container<T>而言,iterator的作用相當于T*,而const_iterator則相當于const T*;增加一個iterator或者const_iterator可以在一個從容器開頭趨向尾部的遍歷中讓你移動到容器的下一個元素。reverse_iterator與const_reverse_iterator同樣相當于對應(yīng)的T*和const T*,所不同的是,增加reverse_iterator或者const_reverse_iterator會在從尾到頭的遍歷中讓你移動到容器的下一個元素。
迭代器使用的一個重要指導方針是:盡量使用iterator代替其他三種迭代器,原因有:
(1) insert和erase的一些版本要求iterator。如果你需要調(diào)用這些函數(shù),你就必須產(chǎn)生iterator,而不能用const或reverse iterators。
(2)不可能把const_iterator隱式轉(zhuǎn)換成iterator。
條款27:用distance和advance把const_iterator轉(zhuǎn)化成iterator
如果你只有一個const_iterator,而你要在它所指向的容器位置上插入新元素呢?也就是如何把const_iterator轉(zhuǎn)化為iterator呢?前面提到:并不存在從const_iterator到iterator之間的隱式轉(zhuǎn)換,也就是說下面操作是不可以的:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 | typedef deque<int> IntDeque; // 方便的typedef typedef IntDeque::iterator Iter; typedef IntDeque::const_iterator ConstIter; ConstIter ci; // ci是const_iterator ... Iter i(ci); // 錯誤!沒有從const_iterator 到iterator隱式轉(zhuǎn)換的途徑 Iter i(const_cast<Iter>(ci)); // 仍是個錯誤!不能從const_iterator映射為iterator! |
正確方法如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | typedef deque<int> IntDeque; // 和以前一樣 typedef IntDeque::iterator Iter; typedef IntDeque::const_iterator ConstIter; IntDeque d; ConstIter ci; ... // 讓ci指向d Iter i(d.begin()); // 初始化i為d.begin() advance(i, distance <ConstIter> (i, ci)); // 把i移到指向ci位置 |
注:distance返回兩個指向同一個容器的iterator之間的距離;advance則用于將一個iterator移動指定的距離。如果i和ci指向同一個容器,那么表達式advance(i, distance(i, ci))會將i移動到與ci相同的位置上。
條款28:了解如何通過reverse_iterator的base得到iterator
條款29:XXXXXXXXXXXXXXXXXXXX(不常見)
5、算法
條款30:確保目標區(qū)間足夠大
“把transform的結(jié)果放入叫做results容器的結(jié)尾”的正確方法是調(diào)用back_inserter來產(chǎn)生指定目標區(qū)間起點的迭代器:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | vector<int> results; transform(values.begin(), values.end(), //把transmogrify應(yīng)用于values中的每個對象, back_inserter(results), // 在results的結(jié)尾 transmogrify); // 插入返回的values 同樣,將結(jié)果插到results容器的前面采用的方法是: ... // 同上 list<int> results; transform(values.begin(), values.end(), // results現(xiàn)在是list在results前端 front_inserter(results), // 以反序 transmogrify); // 插入transform的結(jié)果 |
更高效的方法是:
| 1 2 3 4 5 6 7 8 9 10 11 | vector<int> values; // 同上 vector<int> results; results.reserve(results.size() + values.size()); // 同上 transform(values.begin(), values.end(), // 把transmogrify的結(jié)果 back_inserter(results), // 寫入results的結(jié)尾, transmogrify); // 處理時避免了重新分配 |
條款31:了解你的排序選擇
(1) 如果你需要在vector、string、deque或數(shù)組上進行完全排序,你可以使用sort或stable_sort。
(2) 如果你有一個vector、string、deque或數(shù)組,你只需要排序前n個元素,應(yīng)該用partial_sort。
(3) 如果你有一個vector、string、deque或數(shù)組,你需要鑒別出第n個元素或你需要鑒別出最前的n個元素,而不用知道它們的順序,nth_element是你應(yīng)該注意和調(diào)用的。
(4) 如果你需要把標準序列容器的元素或數(shù)組分隔為滿足和不滿足某個標準,你大概就要找partition或stable_partition。
(5) 如果你的數(shù)據(jù)是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort來代替sort和stable_sort。如果你需要partial_sort或nth_element提供的效果,你就必須間接完成這個任務(wù),主要有三種方法:
[1] 把元素拷貝到一個支持隨機訪問迭代器的容器中,然后對它應(yīng)用需要的算法。
[2] 另一個方法是建立一個list::iterator的容器,對那個容器使用算法,然后通過迭代器訪問list元素。
[3]使用有序的迭代器容器的信息來迭代地把list的元素接合到你想讓它們所處的位置
條款32:如果你真的想刪除東西的話就在類似remove的算法后接上erase
先看一個錯誤的實例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | vector<int> v; // 建立一個vector<int> 用1-10填充它 v.reserve(10); for (int i = 1; i <= 10; ++i) { v.push_back(i); } cout << v.size(); // 打印10 v[3] = v[5] = v[9] = 99; // 設(shè)置3個元素為99 remove(v.begin(), v.end(), 99); // 刪除所有等于99的元素 cout << v.size(); // 仍然是10! |
正確的刪除元素的方法是:
| 1 2 3 4 5 | vector<int> v; // 正如從前 v.erase(remove(v.begin(), v.end(), 99), v.end()); // 真的刪除所有等于99的元素 cout << v.size(); // 現(xiàn)在返回7 |
需要注意的是remove和erase是親密聯(lián)盟,這兩個整合到list成員函數(shù)remove中。這是STL中唯一名叫remove又能從容器中除去元素的函數(shù):
| 1 2 3 4 5 6 7 | list<int> li; // 建立一個list // 放一些值進去 li.remove(99); // 除去所有等于99的元素:真的刪除元素,所以它的大小可能改變了 remove_if ,unique和remove類似,都需要跟erase連用才可以真正刪除數(shù)據(jù),同樣,對于list是個特殊。 |
條款33:提防在指針的容器上使用類似remove的算法
該條款是對條款32的補充,采用remove/erase方式刪除指針容器中的數(shù)據(jù)會造成內(nèi)存泄漏,如:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | class Widget{ public: ... bool isCertified() const; // 這個Widget是否通過檢驗 ... }; vector<Widget*> v; // 建立一個vector然后用動態(tài)分配的Widget v.push_back(new Widget); // 的指針填充 v.erase(remove_if(v.begin(), v.end(), // 刪除未通過檢驗的 not1(mem_fun(&Widget::isCertified))), // Widget指針 v.end()); |
Widget C和Widget B的內(nèi)存不會被釋放,造成內(nèi)存泄漏,正確的方法有兩種:
(1)在應(yīng)用erase-remove慣用法之前先刪除指針并設(shè)置它們?yōu)榭?#xff0c;然后除去容器中的所有空指針:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | void delAndNullifyUncertified(Widget*& pWidget) // 如果*pWidget是一個 { // 未通過檢驗Widget, if (!pWidget->isCertified()) { // 刪除指針 delete pWidget; // 并且設(shè)置它為空 pWidget = 0; } } for_each(v.begin(), v.end(), // 把所有指向未通過檢驗Widget的 delAndNullifyUncertified); // 指針刪除并且設(shè)置為空 v.erase(remove(v.begin(), v.end(), // 從v中除去空指針 static_cast<Widget*>(0)), // 0必須映射到一個指針, v.end()); // 讓C++可以正確地推出remove的第三個參數(shù)的類型 |
(2)采用智能指針,如boost庫中的shared_ptr和scoped_ptr
條款34:注意哪個算法需要有序區(qū)間
STL中只能操作有序數(shù)據(jù)(升序)的算法有:
(1) binary_search:二分查找
(2) lower_bound:下界
(3) upper_bound:上街
(4) equal_range:所有等于某個值的元素
(5) set_union:集合并集
(6) set_intersection:集合交集
(7) set_difference :集合差集
(8) set_symmetric_difference:包含在第一個集合但是不包含在第二個集合中的元素,包含在第2個集合但是不包含在第1個集合中的元素
(9) merge:合并兩個有序表
(10) inplace_merge:合并兩個有序表
(11) includes:檢測一個區(qū)間的所有對象是否在另一個區(qū)間中
另外,下面的算法一般用于有序區(qū)間,雖然它們不要求:
(12) unique:去重,相同的元素必須緊挨著,排序是個特例
(13) unique_copy:同上
條款35:通過mismatch或lexicographical比較實現(xiàn)簡單的忽略大小寫字符串比較
實現(xiàn)忽略大小寫字符串比較有兩種方法,一種是使用mismatch函數(shù):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | int ciStringCompareImpl(const string& s1,? const string& s2); int ciStringCompare(const string& s1, const string& s2) { if (s1.size() <= s2.size()) return ciStringCompareImpl(s1, s2); else return -ciStringCompareImpl(s2, s1); } int ciStringCompareImpl(const string& si, const strings s2) { typedef pair<string::const_iterator, // PSCI = “pair of string::const_iterator> PSCI; // string::const_iterator” PSCI p = mismatch( // 下文解釋了 s1.begin(), s1.end(), // 為什么我們 s2.begin(), // 需要not2;參見 not2(ptr_fun(ciCharCompare))); // 條款41解釋了為什么 // 我們需要ptr_fun if (p.first== s1.end()) { // 如果為真,s1等于 if (p.second == s2.end()) return 0; // s2或s1比s2短 else return -1; } return ciCharCompare(*p.first, *p.second); // 兩個字符串的關(guān)系 } // 和不匹配的字符一樣 |
第二種時使用lexicographical函數(shù):
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | bool ciCharLess(char c1, char c2) // 返回在忽略大小寫 { // 的情況下c1是否 // 在c2前面; tolower(static_cast<unsigned char>(c1)) < // 條款46解釋了為什么 tolower(static_cast<unsigned char>(c2)); // 一個函數(shù)對象可能 } // 比函數(shù)好 bool ciStringCompare(const string& s1, const string& s2) { return lexicographical_compare(s1.begin(), s1.end(), s2.begin(), s2.end(), ciCharLess); } |
條款36:了解copy_if的正確實現(xiàn)
STL有很多有趣的地方,其中一個是雖然有11個名字帶“copy”的算法:
(1) copy
(2) copy_backward
(3) replace_copy
(4) reverse_copy
(5) replace_copy_if
(6) unique_copy
(7) remove_copy
(8) rotate_copy
(9) remove_copy_if
(10) partial_sort_copy
(11) unintialized_copy
但沒有一個是copy_if,這需要自己實現(xiàn),非常經(jīng)典的一個實現(xiàn)是:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | template<typename InputIterator, // 一個copy_if的 typename OutputIterator, // 正確實現(xiàn) typename Predicate> OutputIterator copy_if(InputIterator begin, InputIterator end, OutputIterator destBegin, Predicate p) { while (begin != end) { if (p(*begin))*destBegin++ = *begin; ++begin; } return destBegin; } |
條款37:用accumulate或for_each來統(tǒng)計區(qū)間
本條款總結(jié)了用自定義的方式統(tǒng)計(summarize)區(qū)間的方法,主要有兩種(在頭文件<numeric>中):
(1) accumulate存在兩種形式:第一種是: 帶有一對迭代器和初始值的形式,它可以返回初始值加由迭代器劃分出的區(qū)間中值的和:
| 1 2 3 4 5 | list<double> ld; // 建立一個list ... // 放一些double進去 double sum = accumulate(ld.begin(), Id.end(), 0.0); // 計算它們的和,從0.0開始 |
注意初始值指定為0.0,0.0的類型是double,所以accumulate內(nèi)部使用了一個double類型的變量來存儲計算的和。如果這么寫這個調(diào)用:
| 1 | double sum = accumulate(ld.begin(), Id.end(), 0); // 計算它們的和,從0開始; |
初始值是int 0,所以accumulate內(nèi)部就會使用一個int來保存它計算的值,這是不正確的,因為每次加法后會將結(jié)果轉(zhuǎn)換為一個int,這造成小數(shù)點后面的數(shù)值丟失。
另一種形式帶有一個初始和值與一個任意的統(tǒng)計函數(shù),實例:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | string::size_type // string::size_type的內(nèi)容 stringLengthSum(string::size_type sumSoFar, // 請看下文 const string& s) { return sumSoFar + s.size(); } set<string> ss; // 建立字符串的容器, ... // 進行一些操作 string::size_type lengthSum = // 把lengthSum設(shè)為對 accumulate(ss.begin(), ss.end(), // ss中的每個元素調(diào)用 0, stringLengthSum); // stringLengthSum的結(jié)果,使用0作為初始統(tǒng)計值 |
(2) for_each,帶有一個區(qū)間和一個函數(shù)(一般是一個函數(shù)對象)來調(diào)用區(qū)間中的每個元
素,但傳給for_each的函數(shù)只接收一個實參(當前的區(qū)間元素),而且當完成時for_each返回它的函數(shù)。
原創(chuàng)文章,轉(zhuǎn)載請注明:?轉(zhuǎn)載自董的博客
本文鏈接地址:?http://dongxicheng.org/cpp/effective-stl-part3/
《新程序員》:云原生和全面數(shù)字化實踐50位技術(shù)專家共同創(chuàng)作,文字、視頻、音頻交互閱讀總結(jié)
以上是生活随笔為你收集整理的《Effective STL》学习笔记(第三部分)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 《Effective STL》学习笔记(
- 下一篇: 《Effective STL》学习笔记(