C 知識 泛型演算法

2021-09-25 19:21:10 字數 3544 閱讀 1737

這一章主要介紹泛型演算法,關於常用的泛型演算法,需要自己在寫程式時用到才會更熟悉它的用法。在這一章還有乙個特別重要的概念:lamda表示式,是乙個可呼叫的物件。關於更多詳細的知識,建議自行檢視書籍。

最後,如果有什麼理解不對的地方,希望大家不吝賜教,謝謝!

【c++系列】【前一章:順序容器】【下一章:關聯容器】

順序容器只定義了很少的操作,但如果使用者可能還希望做其他很多有用的操作:查詢特定元素、替換或刪除乙個特定值、重排元素順序等。標準庫並未給每個容器都定義成員函式來實現這些操作,而是定義了一組泛型演算法:稱他們為「演算法」,是因為他們實現了一些經典演算法的公共介面,如排序和搜尋;稱他們是「泛型的」,是因為他們可以用於不同型別的元素和多種容器型別。

#include

#include一組數值泛型演算法

一般情況下,這些演算法並不會直接操作容器,而是遍歷由兩個迭代器指定的乙個元素範圍。

迭代器令演算法不依賴於容器,但演算法依賴於元素型別的操作

泛型演算法執行於迭代器之上而不會執行容器操作的特性帶來了乙個令人驚訝但非常必要的程式設計假定:演算法永遠不會改變底層容器的大小,永遠不會直接新增元素或刪除元素。 

雖然大多數演算法遍歷輸入範圍的方式相似,但他們使用範圍中元素的方式不同,理解演算法的最基本的方法就是了解他們是否讀取元素、改變元素或是重排元素順序。 

一些演算法只會讀取其輸入範圍內的元素,而從不改變元素。

那些只接受乙個單一迭代器來表示第二個序列的演算法,都假定第二個序列至少與第乙個序列一樣長。 

一些演算法將新賦予序列中的元素,當我們使用這類演算法時,必須注意確保序列原大小至少不小於我們要求演算法寫入的元素數目。

這些演算法會重排容器中元素的順序,乙個明顯的例子就是sort。呼叫sort會使輸入序列中的元素按照《運算子來實現排序。 

(1)向演算法傳遞函式

為了按長度重排vector,我們將使用sort的第二個版本,此版本是過載過的,它接受第三個引數,此引數是乙個謂詞。

謂詞分為兩類:一元謂詞和二元謂詞

接受謂詞引數的演算法對輸入序列中的元素呼叫謂詞,因此,元素型別必須能轉換為謂詞的引數型別。

//比較函式,用來按長度排序單詞

bool isshorter(const string &s1,const string &s2)

capture:是乙個lamda所在函式中定義的區域性變數的列表(通常為空)

如果lamda的函式體包含任何單一return語句之外的內容,且未指定返回型別,則返回void。

例如:我們的lamda會捕獲sz,並只有單一的string引數,其函式體會將string的大小與捕獲的sz的值進行比較:

[sz](const string &s)

;

lamda以一對開始,我們可以在其中提供乙個以逗號分隔的名字列表,這些名字都是它所在函式中定義的。由於lamda捕獲sz,因此lamda的函式體可以使用sz。

乙個lamda只有在其捕獲列表中捕獲乙個它所在函式體中的區域性變數,才能在函式體中使用該變數。

捕獲列表只用於區域性非static變數,lamda可以直接使用區域性static變數和它所在函式之外宣告的名字。

一般來說,我們應該儘量減少捕獲的資料量,來避免潛在的捕獲導致的問題。而且,如果可能的話,應該避免捕獲指標或引用。

讓編譯器根據lamda體中的的**來推斷我們要使用哪些變數,為了指示編譯器推斷捕獲列表,應在捕獲列表中寫乙個&或=。&告訴編譯器採用捕獲引用方式,=則表示採用值捕獲方式。當我們混合使用隱式捕獲和顯示捕獲時,捕獲列表中的第乙個元素必須是乙個&或=。此符號指定了預設捕獲方式為引用或值。[&,c]或[=,&c]

預設情況下,對於乙個值被拷貝的變數,lamda不會改變其值。如果我們希望能改變乙個**獲的變數的值,就必須在引數列表加上關鍵字mutable。因此,可變lamda能省略引數列表。

void fcn3()

; v1=0;

auto j=f(); //j為43

}

乙個引用捕獲的變數是否可以修改依賴於此引用指向的是乙個const型別還是乙個非const型別:

void fcn4()

; v1=0;

auto j=f2(); //j為1

}

預設情況下,如果乙個lamda體包含return之外的任何語句,則編譯器假定此lamda返回void,與其他返回void的函式類似,被推斷返回void的lamda不能返回值。當我們需要為乙個lamda定義返回型別時,必須使用尾置返回型別:

transform(vi.begin(),vi.end,vi.begin(),(int i)->int);
對於那種只有在一兩個地方使用的簡單操作,lamda表示式是最有用的。如果我們需要在很多地方使用相同的操作,通常應該定義乙個函式,而不是多次編寫相同的lamda表示式。類似的,如果乙個操作需要很多語句才能完成,通常使用函式更好。

需要加標頭檔案#include,可以將bind函式看作乙個通用的函式介面卡,它接受乙個可呼叫物件,生成乙個新的可呼叫物件來「適應」原物件的引數列表。

auto newcallable=bind(callable,arg_list);

其中,newcallable本身是乙個可呼叫物件,arg_list是乙個逗號分隔的引數列表,對應給定的callable的引數,即當我們呼叫newcallable時,newcallable會呼叫callable,並傳遞給arg_list中的引數。arg_list中的引數可能包含形如_n的名字,_1為newcallable的第乙個引數,_2為第二個引數,一次類推。

auto wc=find_if(words.begin(),words.end(),[sz](const string &a));

//等價於

auto wc=find_if(words.begin(),words.end(),bind(check_size,_1,sz));

名字_n都定義在乙個名為placeholders的命名空間中,需要加上:using namespace std::placeholders;

//g是乙個有兩個引數的可呼叫物件

auto g=bind(f,a,b,_2,c,_1);

即g(_1,_2)會對映為f(a,b,_2,c,_1),則呼叫g(x,y)會呼叫f(a,b,y,c,x)

//按單詞長度由短至長排序

sort(words.begin(),words.end,isshorter);

//按單詞長度由長至短排序

sort(words.begin(),words.end(),bind(isshorter,_2,_1));

演算法所要求的迭代器操作可以分為5個迭代器類別。

C 泛型演算法

標準庫並未給每個容器都定義成員函式來實現這些操作,而是定義了一組泛型演算法,稱他們為演算法是因為他們實現了一些經典演算法的公共介面,如排序和搜尋 稱他們為排序的是因為它們可以用於不同型別的元素和多種容器型別。大多數演算法都定義在標頭檔案algorithm中。標準庫還在標頭檔案numeric中定義了一...

C 泛型演算法

1 泛型演算法初始 標準庫演算法都是對乙個範圍內的元素進行操作 除了少數例外 並將此範圍稱為 輸入範圍 而且總是使用前兩個引數來表示次範圍,這兩個引數分別代表,要處理的首元素和尾元素之後位置的迭代器。1.1 唯讀演算法 只會讀取輸入範圍內的元素,而從不改變元素 find int sum accumu...

C 泛型演算法

1 泛型演算法定義 泛型演算法 因為它們實現共同的操作,所以稱之為 演算法 而 泛型 指的是它們可以操作在多種容器型別上 不但可作用於 vector 或 list 這些標準 庫型別,還可用在內建陣列型別 甚至其他型別的序列上,這些我們將在本章的 後續內容中了解。自定義的容器型別只要與標準庫相容,同樣...