日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

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

發布時間:2025/3/21 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 《Effective STL》学习笔记(第一部分) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
本書從STL應用出發,介紹了在項目中應該怎樣正確高效的使用STL。本書共有7個小節50個條款,分別為

(1) 容器:占12個條款,主要介紹了所有容器的共同指導法則

(2) vector和string:占6個條款,介紹了最常用的兩種容器的一些使用經驗

(3)?關聯容器:占7個條款,介紹了關聯容器(*map,*set)的使用經驗

(4) 迭代器:占12個條款,介紹了迭代器的一些使用技巧

(5) 算法:占8個條款,介紹STL算法的正確使用方法和提高效率的技巧

(6) 仿函數、仿函數類、函數等:占5個條款,介紹仿函數的使用經驗

(7) 使用STL編程:占8個條款,介紹怎樣在程序中使用由容器、迭代器、算法和函數對象組成的STL

在這些條款中,有的是一些編程經驗,即告訴你,如果想避免錯誤,應該這樣編寫程序,應該使用這個函數而不是另一個,還有一些是告訴你怎樣選擇最高效的函數以提高你程序的效率,總結如下:

{1} 介紹編程經驗:

條款1:仔細選擇你的容器

條款2:小心對“容器無關代碼”的幻想

條款6:警惕C++最令人惱怒的解析

條款7:當使用new得指針的容器時,記得在銷毀容器前delete那些指針

條款8:永不建立auto_ptr的容器

條款9:在刪除選項中仔細選擇

條款12:對STL容器線程安全性的期待現實一些

條款13:盡量使用vector和string來代替動態分配的數組

條款16: 如何將vector和string的數據傳給遺留的API

條款17:使用“交換技巧”來修整過剩容量

條款18:避免使用vector<bool>

條款19:了解相等和等價的區別

條款20:為指針的關聯容器指定比較類型

條款21: 永遠讓比較函數對“相等的值”返回false

條款22:避免原地修改set和multiset的鍵

條款26:盡量用iterator代替const_iterator,reverse_iterator和const_reverse_iterator

條款27:用distance和advance把const_iterator轉化成iterator

條款28:了解如何通過reverse_iterator的base得到iterator

條款30:確保目標區間足夠大

……

{2}提高程序效率:

條款3:使容器里對象的拷貝操作輕量而正確

條款4:用empty來代替檢查size()是否為0

條款5:盡量使用區間成員函數代替它們的單元素兄弟

條款14:使用reserve來避免不必要的重新分配

條款15:小心string實現的多樣性

條款23:考慮用有序vector代替關聯容器

條款24:當關乎效率時應該在map::operator[]和map-insert之間仔細選擇

條款25:熟悉非標準散列容器

條款29:需要一個一個字符輸入時考慮使用istreambuf_iterator

條款31:了解你的排序選擇

條款44:盡量用成員函數代替同名的算法

條款46:考慮使用函數對象代替函數作算法的參數

關于《Effective STL》學習筆記分為四部分,本文是第一部分(對應書中“容器”一節),第二、三、四部分分別為:《Effective STL》學習筆記(第二部分)(對應書中“vector和string”,“關聯容器”兩小節)Effective STL》學習筆記(第三部分)對應書中“迭代器”和“算法”兩小節)《Effective STL》學習筆記(第四部分)(對應書中“仿函數、仿函數類、函數等”和“使用STL”兩小節)

1、 容器

本章關注的是可以適用于所有STL容器的指導方針。后面的章節則專注于特殊的容器類型。

條款1:仔細選擇你的容器

C++提供了很多可供程序員使用的容器:

(1) 標準STL序列容器:vector,string,deque和list

(2) 標準STL關聯容器:set,multiset,map和multimap

(3) 非標準序列容器slist(單鏈表)和rope(重型字符串)

(4) 非標準關聯容器hash_set,hash_multiset,hash_map和hash_multimap

(5) vector<char>可以作為string的替代品

(6) vector作為標準關聯容器的替代品

(7) 幾種標準非STL容器,包括數組、bitset、valarray、stack、queue和priority_queue

不同容器有不同的優缺點,用戶需要根據實際應用的特點綜合決定使用哪種容器,如:vector是一種可以默認使用的序列類型,當很頻繁地對序列中部進行插入和刪除時應該用list,當大部分插入和刪除發生在序列的頭或尾時可以選擇deque這種數據結構。

條款2:小心對“容器無關代碼”的幻想

本條款要告誡程序員:編寫與容器無關的代碼是沒有必要的。

有人想編寫這樣的程序,剛開始時使用vector存儲,之后由于需求的變化,將vector改為deque或者list,其他代碼不變。實際上,這基本上是做不到的。這是因為:不同的序列容器所對應了不同的迭代器、指針和引用的失效規則,此外,不同的容器支持的操作也不相同,如:vector支持reserve()和capacity(),而deque和list不支持;即使是相同的操作,復雜度也不一樣(如:insert),這會讓你的系統產生意想不到的瓶頸。

此外,鼓勵程序員在聲明容器和迭代器的時候使用typedef進行重命名,這能夠對你的程序進行封轉,從而使用起來更簡單,如有下面一個map容器:

map<string,vectorWidget>::iterator,CIStringCompare>;

如要用const_iterator遍歷這個map,你需不止一次地寫下:

map<string, vectorWidget>::iterator, CIStringCompare>::const_iterator

如果使用typedef,會快速方便很多。

條款3:使容器里對象的拷貝操作輕量而正確

容器容納了對象,但不是你給它們的那個對象。當你向容器中插入一個對象時,你插入的是該對象的拷貝而不是它本身;當你從容器中獲取一個對象時,你獲取的是容器中對象的拷貝。

拷貝是STL的基本工作方式。當你刪除或者插入某個對象時,現有容器中的元素會移動(拷貝);當你使用了排序算法,remove、uniquer或者他們的同類,rotate或者reverse,對象會移動(拷貝)。

一個使拷貝更高效、正確的方式是建立指針的容器而不是對象的容器,即保存對象的指針而不是對象,然而,指針的容器有它們自己STL相關的頭疼問題,改進的方法是采用智能指針。

條款4:用empty來代替檢查size()是否為0

對于任意容器c,寫下

if (c.size() == 0)…

本質上等價于寫下

if (c.empty())…

但是為什么第一種方式比第二種優呢?理由很簡單:對于所有的標準容器,empty是一個常數時間的操作,但對于一些list實現,size花費線性時間。

這什么造成list這么麻煩?為什么不能也提供一個常數時間的size?如果size是一個常數時間操作,當進行增加/刪除操作時每個list成員函數必須更新list的大小,也包括了splice,這會造成splice的效率降低(現在的splice是常量級的),反之,如果splice不必修改list大小,那么它就是常量級地,而size則變為線性復雜度,因此,設計者需要權衡這兩個操作的算法:一個或者另一個可以是常數時間操作。

條款5:盡量使用區間成員函數代替單元素操作

給定兩個vector,v1和v2,怎樣使v1的內容和v2的后半部分一樣?

可行的解決方案有:

(1) 使用區間函數assign:

1 v1.assign(v2.begin() + v2.size() / 2, v2.end());

(2) 使用單元素操作:

1 2 3 4 5 6 7 vector<Widget>::const_iterator ci = v2.begin() + v2.size() / 2; ci != v2.end(); ++ci) v1.push_back(*ci);

(3) 使用copy區間函數

1 2 3 v1.clear(); copy(v2.begin() + v2.size() / 2, v2.end(), back_inserter(v1));

(4) 使用insert區間函數

1 v1.insert(v1.end(), v2.begin() + v2.size() / 2, v2.end());

最優的方案是assign方案,理由如下:

首先,使用區間函數的好處是:

● 一般來說使用區間成員函數可以輸入更少的代碼。

● 區間成員函數會導致代碼更清晰更直接了當。

使用copy區間函數存在的問題是:

【1】 需要編寫更多的代碼,比如:v1.clear(),這個與insert區間函數類似

【2】 copy沒有表現出循環,但是在copy中的確存在一個循環,這會降低性能

使用insert單元素版本的代碼對你征收了三種不同的性能稅,分別為:

【1】 沒有必要的函數調用;

【2】 無效率地把v中的現有元素移動到它們最終插入后的位置的開銷;

【3】 重復使用單元素插入而不是一個區間插入必須處理內存分配。

下面進行總結:

說明:參數類型iterator表示容器的迭代器類型,也就是container::iterator,參數類型InputIterator表示可以接受任何輸入迭代器。

【1】 區間構造

所有標準容器都提供這種形式的構造函數:

1 2 3 container::container(InputIterator begin, // 區間的起點 InputIterator end); // 區間的終點

【2】 區間插入

所有標準序列容器都提供這種形式的insert:

1 2 3 4 5 void container::insert(iterator position, // 區間插入的位置 InputIterator begin, // 插入區間的起點 InputIterator end); // 插入區間的終點

關聯容器使用它們的比較函數來決定元素要放在哪里,所以它們了省略position參數。

.

1 void container::insert(lnputIterator begin, InputIterator end);

【3】 區間刪除

每個標準容器都提供了一個區間形式的erase,但是序列和關聯容器的返回類型不同。序列容器提供了這個:

1 iterator container::erase(iterator begin, iterator end);

而關聯容器提供這個:

1 void container::erase(iterator begin, iterator end);

為什么不同?解釋是如果erase的關聯容器版本返回一個迭代器(被刪除的那個元素的下一個)會招致一個無法接受的性能下降.

【4】 區間賦值

所有標準列容器都提供了區間形式的assign:

1 void container::assign(InputIterator begin, InputIterator end);

條款6:警惕C++最令人惱怒的解析

假設你有一個int的文件,你想要把那些int拷貝到一個list中。這看起來像是一個合理的方式:

1 2 3 4 5 ifstream dataFile("ints.dat"); list<int> data(istream_iterator<int>(dataFile), // 警告!這完成的并不 istream_iterator<int>()); // 是像你想象的那樣

這里的想法是傳一對istream_iterator給list的區間構造函數(參見條款5),因此把int從文件拷貝到list中。

實際上,這段代碼可以編譯通過,但運行時不會產生任何結果。仔細分析后,會發現,你這段代碼實際上是聲明了一個data函數,它的返回值是list<int>,兩個參數均為istream_iterator<int>類型。

解決辦法是在數據聲明中從時髦地使用匿名istream_iterator對象后退一步,僅僅給那些迭代器名字。以下代碼到哪里都能工作:

1 2 3 4 5 6 7 ifstream dataFile("ints.dat"); istream_iterator<int> dataBegin(dataFile); istream_iterator<int> dataEnd; list<int> data(dataBegin, dataEnd);

條款7:當使用new得指針的容器時,記得在銷毀容器前delete那些指針

條款8:永不建立auto_ptr的容器

條款9:在刪除選項中仔細選擇

(1)假定你有一個標準STL容器,c,容納int,

1 Container<int> c;

而你想把c中所有值為1963的對象都去,則不同的容器類型采用的方法不同:沒有一種是通用的.

[1] 如果采用連續內存容器(vector、queue和string),最好的方法是erase-remove慣用法:

1 2 3 c.erase(remove(c.begin(), c.end(), 1963), // 當c是vector、string c.end()); // 或deque時,erase-remove慣用法是去除特定值的元素的最佳方法

[2] 對于list,最有效的方法是直接使用remove函數:

1 c.remove(1963);

[3] 對于關聯容器,解決問題的適當方法是調用erase:

1 c.erase(1963); // 當c是標準關聯容器時,erase成員函數是去除特定值的元素的最佳方法

(2)讓我們換一下問題:不是從c中除去每個有特定值的元素,而是消除下面判斷式返回真的每個對象:

1 bool badValue(int x); // 返回x是否是“bad”

[1] 對于序列容器(vector、list、deque和string),只需要將remove換成remove_if即可:

1 2 3 4 5 c.erase(remove_if(c.begin(), c.end(), badValue), // 當c是vector、string c.end()); // 或deque時這是去掉badValue返回真的對象的最佳方法 c.remove_if(badValue); // 當c是list時這是去掉badValue返回真的對象的最佳方法

[2] 對于關聯容器,有兩種方法處理該問題,一個更容易編碼,另一個更高效。“更容

易但效率較低”的解決方案用remove_copy_if把我們需要的值拷貝到一個新容器中,然后把原容器的內容和新的交換:

1 2 3 4 5 6 7 8 9 10 11 12 13 AssocContainer<int> c; // c現在是一種標準關聯容器 AssocContainer<int> goodValues; // 用于容納不刪除的值的臨時容器 remove_copy_if(c.begin(), c.end(), // 從c拷貝不刪除 inserter(goodValues, // 的值到 goodValues.end()), // goodValues badValue); c.swap(goodValues); // 交換c和goodValues的內容

“更高效”的解決方案是直接從原容器刪除元素。不過,因為關聯容器沒有提供類似remove_if的成員函數,所以我們必須寫一個循環來迭代c中的元素,和原來一樣刪除元素:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 AssocContainer<int> c; ... for (AssocContainer<int>::iterator i = c.begin(); // for循環的第三部分 i != c.end(); // 是空的;i現在在下面 /*nothing*/ ){ // 自增 if (badValue(*i)) c.erase(i++); // 對于壞的值,把當前的 else ++i; // i傳給erase,然后 } // 作為副作用增加i;對于好的值,只增加i

(3)進一步豐富該問題:不僅刪除badValue返回真的每個元素,而且每當一個元素被刪掉時,我們也想把一條消息寫到日志文件中。

[1] 對于關聯容器,只需要對我們剛才開發的循環做一個微不足道的修改就行了:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ofstream logFile; // 要寫入的日志文件 AssocContainer<int> c; ... for (AssocContainer<int>::iterator i = c.begin(); // 循環條件和前面一樣 i !=c.end();){ if (badValue(*i)){ logFile << "Erasing " << *i <<'\n'; // 寫日志文件 c.erase(i++); // 刪除元素 } else ++i; }

[2] 對于vector、string、list和deque,必須利用erase的返回值。那個返回值正是我們需要的:一旦刪除完成,它就是指向緊接在被刪元素之后的元素的有效迭代器。換句話說,我們這么寫:

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 for (SeqContainer<int>::iterator i = c.begin(); i != c.end();){ if (badValue(*i)){ logFile << "Erasing " << *i << '\n'; i = c.erase(i); // 通過把erase的返回值賦給i來保持i有效 } else ++i; }

條款10:注意分配器的協定和約束

條款11:理解自定義分配器的正確用法

條款12:對STL容器線程安全性的期待現實一些

當涉及到線程安全和STL容器時,你可以確定庫實現了“允許在一個容器上的多讀者”(在讀取時不能 有任何寫入者操作這個容器。)和“不同容器上的多個寫者”(多線程可以同時寫不同的容器。)。你不能希望庫消除對手工并行控制的需要,且你完全不能依賴于任何線程支持。

原創文章,轉載請注明:?轉載自董的博客

本文鏈接地址:?http://dongxicheng.org/cpp/effective-stl-part1/

總結

以上是生活随笔為你收集整理的《Effective STL》学习笔记(第一部分)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。