《C++primer》 第十章_泛型演算法 讀書筆記
《C++Primer 第五版》
——讀書隨筆集
第十章
順序容器只定義了很少的操作,在多數情況下,我們可以新增和刪除元素,訪問首尾元素,確定容器是否為空以及獲得只想首元素或尾元素之後位置的迭代器。
如果使用者還希望做其他更多有用的操作呢:比如查詢特定元素,替換或刪除一個特定值,重排元素順序等。
標註好難看並未給每個容器都定義成員函式來實現這些操作,而是定義了一組泛型演算法,稱它們為演算法是因為他們實現了一些經典演算法的公共介面,如排序和搜尋,稱它們是泛型的,是因為它們可以用於不同型別的元素和多種容器型別,不僅包括標準庫型別,如vector或list,還包括內建陣列型別。
10.1 概述
大多數演算法都定義在標頭檔案algorithm中,標準庫還在標頭檔案numeric中定義了一組數值泛型演算法。一般情況下,這些演算法不直接操作容器,而是遍歷由兩個迭代器指定的一個元素範圍來進行操作。通常情況下,演算法遍歷範圍,對其中每個元素進行一些處理。
- 迭代器演算法不依賴於容器,通過迭代器操作來實現。
- 但演算法依賴於元素型別的操作,大多數演算法提供了一種方法,允許我們使用自定義操作來代替預設的運算子。
演算法永遠不會執行容器的操作。泛型演算法本身永遠不會執行容器的操作,他們只會執行在迭代器之上,執行迭代器的操作。泛型演算法執行在迭代器之上而不會執行容器操作的特性帶來了一個令人驚訝但非常必要的程式設計假定:演算法永遠不會改變底層容器的大小。演算法可能改變容器中儲存的值,也可能在容器中移動元素,但永遠不會直接新增或者刪除元素。
後面我們將見識到一種特殊的迭代器,叫做插入器。與普通迭代器只能遍歷所繫結的容器相比,插入器能做更多的事。當給這類迭代器賦值時,他們會在底層容器上執行插入操作。因此演算法可以操控這類迭代器,從而完成向容器中新增元素的效果,但演算法自身不會執行這樣的操縱。
10.2 初識泛型演算法
除少數例外,標準庫演算法都對一個範圍內的元素進行操作。我們將此類元素範圍稱為“輸入範圍”。接受輸入範圍的演算法總是使用前兩個引數來表示此範圍,兩個引數分別是指向要處理的第一個元素和尾元素之後位置的迭代器。
雖然大多數演算法遍歷輸入範圍的方式相似,但它們使用範圍中元素的方式不同。理解演算法的最基本的方法就是了解它們是否讀取元素,改變元素或是重排元素。
10.2.1 只讀演算法
一些演算法只會讀取其輸入範圍的元素,而從不改變元素。find就是這樣一種演算法,我們之前使用的count函式也是如此。另一個只讀演算法是accumulate,它定義在標頭檔案numeric中。accumulate函式接受三個引數,前兩個指出需要求和的範圍,第三個引數就是和的初值。假定vec是一個整數序列,則:
int sum = accumulate(vec.cbegin(), vec.cend(), 0);
accumulate
的第三個引數的型別決定了函式中使用哪個加法運算子以及返回值的型別。
-
演算法和元素型別
accumulate作為第三個引數作為求和起點,這裡蘊含一個假定:將元素型別加到和的型別上的操作必須是可行的。即序列中的元素型別必須與第三個引數匹配,或者能夠轉化為第三個引數的型別。
//將v中的每個元素連線到一起 string num = accumulate(v.cbegin(), v.cend(), string("")); //錯誤,const char* 上沒有定義+運算子 string num = accumulate(v.cbegin(), v.cend(), "");
對於只讀演算法,最好只使用cbegin()和cend()。但是,如果你要通過使用演算法返回的迭代器來修改元素的值,則就需要使用begin()和end()的結果作為引數。
-
操作兩個序列的演算法
另外一個只讀演算法是equal(),用於確定兩個序列是否儲存相同的值。將第一個序列中的每個元素與第二個序列中的每個元素進行比較。如果每個對應元素都相等,則返回true,否則返回false。此演算法接受三個迭代器:前兩個為第一個序列的元素範圍,第三個是第二個序列的首元素。
//roster2中的元素數目應該至少與roster1一樣多 equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());
由於equal利用迭代器完成操作,所以我們可以呼叫equal來比較兩個不同型別的容器中的元素。而且,元素型別也不必一樣,只要我們能用==來比較兩個元素型別即可。
但是,equal有一個非常重要的假設:它假定第二序列至少與第一個序列一樣長。
那些只接受單一迭代器來表示第二個序列的演算法,都假定第二個序列至少與第一個序列一樣長。