Effective STL 精華版讀書筆記

容器

STL是建立在泛化之上的。

  • 數組泛化爲容器,參數化了所包含的對象的類型。
  • 函數泛化爲算法,參數化了所用的迭代器的類型。
  • 指針泛化爲迭代器,參數化了所指向的對象的類型。
    標準STL序列容器:vector、string、deque和list。
    標準STL關聯容器:set、multiset、map和multimap。
    非標準序列容器slist和rope。slist是一個單向鏈表,rope本質上是一個重型字符串。
    非標準關聯容器hash_set、hash_multiset、hash_map和hash_multimap
    vector可以作爲string的替代品。
    vector作爲標準關聯容器的替代品。
    幾種標準非STL容器,包括數組、bitset、valarray、stack、queue和priority_queue。

代碼規範

if (c.size() == 0)

這種寫法的時候,可以改成如下

if (c.empty())

對於list的分析
簡介:
list是一種序列式容器。list容器完成的功能實際上和數據結構中的雙向鏈表是極其相似的,list中的數據元素是通過鏈表指針串連成邏輯意義上的線性表,也就是list也具有鏈表的主要優點,即:在鏈表的任一位置進行元素的插入、刪除操作都是快速的。list的實現大概是這樣的:list的每個節點有三個域:前驅元素指針域、數據域和後繼元素指針域。前驅元素指針域保存了前驅元素的首地址;數據域則是本節點的數據;後繼元素指針域則保存了後繼元素的首地址。其實,list和循環鏈表也有相似的地方,即:頭節點的前驅元素指針域保存的是鏈表中尾元素的首地址,list的尾節點的後繼元素指針域則保存了頭節點的首地址,這樣,list實際上就構成了一個雙向循環鏈。由於list元素節點並不要求在一段連續的內存中,顯然在list中是不支持快速隨機存取的,因此對於迭代器,只能通過“++”或“–”操作將迭代器移動到後繼/前驅節點元素處。而不能對迭代器進行+n或-n的操作,這點,是與vector等不同的地方。

只有list提供了不用拷貝數據就能把元素從一個地方接合到另一個地方
的能力

vector/string

字符串值可能是或可能不是引用計數的。默認情況下,很多實現的確是用了引用計數,但它們通常提供了關閉的方法,一般是通過預處理
器宏。 條款13 給了一個你可能要關閉的特殊環境的例子,但你也可能因爲其他原因而要那麼做。比如,引用計數只對頻繁拷貝的字符串有
幫助,而有些程序不經常拷貝字符串,所以沒有那個開銷。
● string對象的大小可能從1到至少7倍char*指針的大小。
● 新字符串值的建立可能需要0、1或2次動態分配。
● string對象可能是或可能不共享字符串的大小和容量信息。
● string可能是或可能不支持每對象配置器。
● 不同實現對於最小化字符緩衝區的配置器有不同策略。

vector<int> v;
v.reserve(1000);
for (int i = 1; i <= 1000; ++i) v.push_back(i);
string s;
...
if (s.size() < s.capacity()) {
s.push_back('x');
}
void doSom ething(const int* pInts, size_t num Ints);
我們可以這麼做:使用STLAPI
if (!v.em pty()) {
doSom ething(& v[0], v.size());
}
void doSom ething(const char *pString);
doSom ething(s.c_str());

swap

string s;
... // 使s變大,然後刪除所有
// 它的字符
string(s).swap(s); // 在s上進行“收縮到合適”

vector<Contestant> v;
string s;
... // 使用v和s
vector<Contestant>().swap(v); // 清除v而且最小化它的容量
string().swap(s); // 清除s而且最小化它的容量

vector確實只有兩個問題。第一,它不是一個STL容器。第二,它並不容納bool。
deque和bitset是基本能滿足你對vector

關聯容器


set,multiset,map,multimap都是有序的。less
相等是以operator==爲基礎的;等價是以operator<爲基礎的,"在已排序的區間中對象值的相對順序。
應優先使用成員函數(像std::find)而不是與之對應的非成員函數(像find)。
標準關聯容器總是保持排列順序的,所以每個容器必須有一個比較函數(默認爲less)來決定保持怎樣的順序。
等價的定義正是通過該比較函數而確定。

21.爲包含指針的關聯容器指定比較類型
在使用關聯容器時,如果存儲的是指針,又想按照指針指向的對象的某個字段順序存儲,
需要自己實現比較函數對象。作爲第二個參數傳入構造。

struct DereferenceLess {
 template <typename PtrType>
 bool operator()(PtrType pT1, // 參數是值傳遞的,
 PtrType pT2) const // 因爲我們希望它們
 { // 是(或行爲像)指針
 return *pT1 < *pT2;
 }
};
set<string*, DereferenceLess> ssp; // 行爲就像
set<string*, less<string*>, allocator<string*> > ssp;

struct StringPtrGreater: // 對關聯容器來說
public binary_function<const string*, // 這是有效的比較類型
const string*,
bool> {
 bool operator()(const string *ps1, const string *ps2) const
 {
 return *ps2 < *ps1; // 返回*ps2是否
 } // 大於*ps1(也就是
}; // 交換操作數的順序)

避免原地修改set和multiset的鍵

  1. 定位你想要改變的容器元素。
  2. 拷貝一份要被修改的元素。對map或multimap而言,確定不要把副本的第一個元素聲明爲const。畢竟,你想要改變它!
  3. 修改副本,使它有你想要在容器裏的值。
  4. 從容器裏刪除元素,通常通過調用erase
  5. 把新值插入容器。如果新元素在容器的排序順序中的位置正好相同或相鄰於刪除的元素,使用insert
    的“提示”形式把插入的效率從對數時間改進到分攤的常數時間。使用你從第一步獲得的迭代器作爲
    提示。

map<int, Widget> m;
m[1] = 1.50;
如果要更新一個已有的map,則優先選擇operator[];但如果要添加一個新的元素,那麼最好還是選擇insert。
Operator[]返回一個引用(m[k]=v),它指向與k相關聯的值對象。然後v被賦給該引用所指的對象。

迭代器

大概意思是每個容器有每個容器的特性,不要濫用。

  • 在一個序列容器上用一個迭代器作爲參數調用erase,會返回一個新迭代器,但在關聯容器上什麼都不返回。
  • 只有序列容器支持push_front或push_back
  • 你把一個對象插入一個序列容器中,它保留在你放置的位置。但如果你把一個對象插入到一個關聯容器中,容器會按照的排列順序把這個對象移到它應該在的位置
  • 只有關聯容器支持count和lower_bound

迭代器是一個“可遍歷STL容器內全部或部分元素”的對象。
迭代器指出容器中的一個特定位置。
迭代器就如同一個指針。
迭代器提供對一個容器中的對象的訪問方法,並且可以定義了容器中對象的範圍。

這裏大概介紹一下迭代器的類別。

  • 輸入迭代器:也有叫法稱之爲“只讀迭代器”,它從容器中讀取元素,只能一次讀入一個元素向前移動,只支持一遍算法,同一個輸入迭代器不能兩遍遍歷一個序列。

  • 輸出迭代器:也有叫法稱之爲“只寫迭代器”,它往容器中寫入元素,只能一次寫入一個元素向前移動,只支持一遍算法,同一個輸出迭代器不能兩遍遍歷一個序列。

  • 正向迭代器:組合輸入迭代器和輸出迭代器的功能,還可以多次解析一個迭代器指定的位置,可以對一個值進行多次讀/寫。

  • 雙向迭代器:組合正向迭代器的功能,還可以通過–操作符向後移動位置。
    隨機訪問迭代器:組合雙向迭代器的功能,還可以向前向後跳過任意個位置,可以直接訪問容器中任何位置的元素。

c.begin()  // 返回一個迭代器,它指向容器c的第一個元素
c.end() // 返回一個迭代器,它指向容器c的最後一個元素的下一個位置
c.rbegin()// 返回一個逆序迭代器,它指向容器c的最後一個元素
c.rend() //返回一個逆序迭代器,它指向容器c的第一個元素前面的位置

    int arry[] = {1,2,3,4,5};
    vector<int> v(arry,arry+sizeof(arry)/sizeof(arry[0]));
    //正向迭代器
    for(vector<int>::iterator it = v.begin();it != v.end();it++){
        cout<<*it<<" ";
    }
    //反向迭代器
    for(vector<int>::reverse_iterator it = v.rbegin();it != v.rend();it++){
        cout<<*it<<" ";
    }

輸入迭代器

#include<iostream>
#include<iterator>//必須
using namespace std;
int main() {
	istream_iterator<int> it(cin);//定義一個輸入int型數據的輸入迭代器,綁定cin爲輸入源,並立即開始輸入
	istream_iterator<int> eof;//定義一個輸入int型數據的“哨兵”
	while (it != eof) {
		cout << *(it++)<< endl;// 先將it給*運算符,再使it往前移動(即輸一個數據),然後輸出原it的內容,所以我們會看到:輸出的是上上次的內容。 
	}
	cout << "over" << endl;//當你輸入的不是int後會看到這裏的內容,包括按下Ctrl+D(for windows)時。
	system("pause");
	return 0;
}

輸出迭代器

#include<iostream>
#include<iterator>//必須
using namespace std;
int main() {
	ostream_iterator<int> it(cout);
	for (int i = 0;i < 5;i++) {
		*it = i;it++;
	}
	cout << endl;
	ostream_iterator<int> it2(cout,"\n");//有間隔內容的
	for (int i = 0;i < 5;i++) {
		*it2 = i;it2++;//當給it指向的內容附值時,即向cout輸出
	}
	ostream_iterator<int> it3(cout);
	for (int i = 0;i < 5;i++) {
		*it3 = i;//輸出後,it3會自動向前移動
	}
	system("pause");
	return 0;
}

雙向迭

代器與隨機訪問迭代器
雙向迭代器支持的操作:
it++, ++it, it–, --it,*it, itA = itB,
itA == itB,itA != itB
其中list,set,multiset,map,multimap支持雙向迭代器。
隨機訪問迭代器支持的操作:
在雙向迭代器的操作基礎上添加
it+=i, it-=i, it+i(或it=it+i),it[i],
itA<itB, itA<=itB, itA>itB, itA>=itB 的功能。
其中vector,deque支持隨機訪問迭代器。

去除一個容器中有特定值的所有對象:
如果容器是vector、string或deque,使用erase-rem ove慣用法。
如果容器是list,使用list::rem ove。
如果容器是標準關聯容器,使用它的erase成員函數。
● 去除一個容器中滿足一個特定判定式的所有對象:
如果容器是vector、string或deque,使用erase-rem ove_if慣用法。
如果容器是list,使用list::rem ove_if。
如果容器是標準關聯容器,使用rem ove_copy_if和swap,或寫一個循環來遍歷容器元素,當你把迭代
器傳給erase時記得後置遞增它。
● 在循環內做某些事情(除了刪除對象之外):
如果容器是標準序列容器,寫一個循環來遍歷容器元素,每當調用erase時記得都用它的返回值更新你
的迭代器。

算法

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

vector<int> results; // 把transm ogrify應用於
if (results.size() < values.size()){ // 確保results至少
results.resize(values.size()); // 和values一樣大,減少性能消耗
}
transform (values.begin(), values.end(), // values中的每個對象,
back_inserter(results), // 在results的結尾
transm ogrify); // 插入返回的values
transform (values.begin(), values.end(), // 在results前端
front_inserter(results), //  以反序
transm ogrify); // 插入transform 的結果

transform它不會給不存在的對象賦值

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

bool qualityCom pare(const W idget& lhs, const W idget& rhs)
{
// 返回lhs的質量是不是比rhs的質量好
}
...
partial_sort(widgets.begin(), // 把最好的20個元素
widgets.begin() + 20, // (按順序)放在widgets的前端
widgets.end(),
qualityCom pare);
... // 使用widgets...
nth_elem ent(widgets.begin(), // 把最好的20個元素
widgets.begin() + 19, // 放在widgets前端,
widgets.end(), // 但不用擔心
qualityCom pare); // 它們的順序

stable_sort是穩定的,會保持相等的時候順序不變
ist::sort提供了穩定排序
如果你需要在vector、string、deque或數組上進行完全排序,你可以使用sort或stable_sort。
● 如果你有一個vector、string、deque或數組,你只需要排序前n個元素,應該用partial_sort。
● 如果你有一個vector、string、deque或數組,你需要鑑別出第n個元素或你需要鑑別出最前的n個元素,
而不用知道它們的順序,nth_elem ent是你應該注意和調用的。
● 如果你需要把標準序列容器的元素或數組分隔爲滿足和不滿足某個標準,你大概就要找partition或
stable_partition。
● 如果你的數據是在list中,你可以直接使用partition和stable_partition,你可以使用list的sort來代替sort和
stable_sort。如果你需要partial_sort或nth_elem ent提供的效果,你就必須間接完成這個任務,但正如我
在上面勾畫的,會有很多選擇。
性能排名

  1. partition 4. partial_sort
  2. stable_partition 5. sort
  3. nth_elem ent 6. stable_sort

條款32:如果你真的想刪除東西的話就在類似rem ove的算法後接上erase

rem ove並不“真的”刪除東西,因爲它做不到。
“類似rem ove”的算法:rem ove_if和unique
value相同值的元素都會被覆蓋,而其他的元素都會依次前移。最後remove返回"指向最後一個’有用’
元素的iterator",但是在remove算法過程中,並沒有修改原容器的size,以及end()。
v.erase(rem ove(v.begin(), v.end(), 99), v.end()); // 真的 刪除所有

條款33:提防在指針的容器上使用類似rem ove的算法

void delAndNullifyUncertified(W idget*& pW idget) // 如果*pW idget是一個
{ // 未通過檢驗W idget,
if (!pW idget->isCertified()) { // 刪除指針
delete pW idget; // 並且設置它爲空
pW idget = 0;
}
}
for_each(v.begin(), v.end(), // 把所有指向未通過檢驗W idget的
delAndNullifyUncertified); // 指針刪除並且設置爲空
v.erase(remove(v.begin(), v.end(), // 從v中除去空指針
static_cast<W idget*>(0)), // 0必須映射到一個指針,
v.end()); // 讓C++可以
// 正確地推出rem ove的
// 第三個參數的類型

條款34:注意哪個算法需要有序區間

binary_search、lower_bound、upper_bound和equal_range二分法查找來搜索值
set_union、set_intersection、set_difference和set_sym m etric_difference提供了線性時間設置它們名字
m erge和inplace_m erge執行了有效的單遍合併排序算法
includes。它用來檢測是否一個區間的所有對象也在另一個區間中。

vector<int> v; // 建立一個vector,
... // 把一些數據放進去
sort(v.begin(), v.end(), greater<int>()); // 降序排列
... // 使用這個vector
// (沒有改變它)
bool a5Exists = // 搜索5
binary_search(v.begin(), v.end(), 5. greater<int>()); // 把greater作爲
// 比較函數

條款35:通過mismatch或lexicographical比較實現簡單的忽略大小寫字符串比較

mismatch並行比較兩個序列, 指出第一個不匹配的位置, 返回一對 iterator, 標誌第一個不匹配元素位置。
如果都匹配, 返回每個容器的 last。
exicographical_compare是一個泛化版本,可以比較任何類型的值的區間

條款36:瞭解copy_if的正確實現

STL 中有11個包含了copy的算法:
copy:複製整個序列到一個新位置
copy_backward:與Copy相同,不過元素是以相反的順序拷貝
replace_copy_if:將指定範圍內所有操作結果爲 true 的元素用新值代替輸出到另一個容器
reverse_copy:將指定範圍內元素重新反序排序輸出到另一個容器
unique_copy:將序列中重複元素輸出到另一個容器
remove_copy:移除值爲value的元素,並將處理後在容器複製到另一容器裏.
rotate_copy:將指定範圍內元素移到容器末尾
remove_copy_if:將所有不匹配的元素拷貝到一個指定容器,原容器的值不變。
partial_sort_copy:對序列做部分排序,輸出到另一個容器。


template<typename InputIterator,typename OutputIterator,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:用accum ulate或for_each來統計區間

struct Point {...); // 同上
class PointAverage:
public unary_function<Point, void> { // 參見 條款40
public:
PointAverage(): xSum (0), ySum (0), num Points(0) {}
void operator()(const Point& p)
{
++num Points;
xSum += p.x;
ySum += p.y;
}
Point result() const
{
return Point(xSum /num Points, ySum /num Points);
}
private:
size_t num Points;
double xSum ;
double ySum ;
};
list<Point> Ip;
...
Point avg = for_each(lp.begin(), lp.end(), PointAverage()).result;
string::size_type 
stringLengthSum(string::size_type sumSoFar, const string& s)
{
   return sumSoFar + s.size();
}
set<string> ss; // 建立字符串的容器,
... // 加入字符串
string::size_type lengthSum = accumulate(ss.begin(), ss.end(), static_cast<string::size_type>(0), stringLengthSum);

仿函數

條款42:確定less表示operator<

此條比較重要

應該儘量避免修改less的行爲,因爲這樣做很有可能誤導其他的程序員。如果使用了less,無論是顯式還是隱式,你都需要確保她與operator<有相同的意義。若希望以一種特殊的方式來排序對象,那麼最好創建一個特殊的函數子類,它的名字不能是less,這樣做其實是很簡單的。

使用STL編程

優先使用成員函數
迭代器並沒有劃分一個有序區間,你就只能用線性時間的算法count、count_if、find和find_if
有序區間binary_search、lower_bound、upper_bound和equal_range,是對數時間

你想知道的 無序區間 在有序區間 set/map multiset/multimap
期望值是否存在? find binary_search count find
期望值是否存在?如果有,第一個等於這個值的對象在哪裏? find equal_range find find lower_bound
第一個不在期望值之前的對象在哪裏? find_if lower_bound lower_bound lower_bound
第一個在期望值之後的對象在哪裏? find_if upper_bound upper_bound upper_bound
有多少對象等於期望值? count equal_range,然後distance count count
等於期望值的所有對象在哪裏? find(迭代) equal_range equal_range equal_range

less<
less_equal <=
if (find(lw.begin(), lw.end(), w) != lw.end()) {
… // 找到了
} else { // 沒找到
}

vector<double> v;//排序double數組
sort(v.begin(), v.end(), greater<double>());
typedef vector<int>::iterator VecIntIter;
// 把rangeBegin初始化爲指向最後一個 
// 出現一個大於等於y的值後面的元素。 
// 如果沒有那樣的值, 
// 把rangeBegin初始化爲v.begin()。如果那個值的 
// 最後一次出現是v中的最後一個元素, 
// 就把rangeBegin初始化爲v.end()。
VecIntIter rangeBegin = find_if(v.rbegin(), v.rend(),
 bind2nd(greater_equal<int>(), y)).base();
// 從rangeBegin到v.end(),刪除所有小於x的值
v.erase(remove_if(rangeBegin, v.end(), bind2nd(less<int>(), x)), v.end());

set聲明瞭set/multiset/,map同理map/multimap
中聲明瞭算法,除了在inner_product/adjacent_differencd和partial_sum
特殊的迭代器,包括istream_iterators和istreambuf_iterators,在中聲明。
標準仿函數(比如less)和仿函數適配器(比如not1、bind2nd)在中聲明

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章