泛型算法(lambda表达式、function类模板、bind函数适配器、迭代器类别、链表数据结构独有的算法)
文章目錄
- 概念
- find()函數(shù)
- 迭代器令算法不依賴(lài)于容器
- 但算法依賴(lài)于元素類(lèi)型的操作
- 算法永遠(yuǎn)不會(huì)執(zhí)行容器的操作
- 只讀算法
- accumulate()函數(shù)
- 從兩個(gè)序列中讀取元素(equal函數(shù)為例)
- 迭代器作為參數(shù)形成兩個(gè)序列
- equal()
- 寫(xiě)容器元素的算法
- 概念
- fill()
- fill_n()
- 插入迭代器back_inserter
- 插入迭代器是否與“標(biāo)準(zhǔn)庫(kù)算法不會(huì)改變它們所操作的容器的大小”相悖
- 拷貝(copy)算法
- replace算法
- 這里的容器大小指的是元素?cái)?shù)量
- 定制操作
- 謂詞
- stable
- 可調(diào)用對(duì)象
- lambda表達(dá)式
- 概念
- 原理
- 捕獲列表
- find_if
- for_each
- 捕獲方式
- 值捕獲
- mutable
- 引用捕獲
- 修改引用捕獲的變量
- 隱式捕獲
- function模板
- 概念
- function與重載函數(shù)
- bind函數(shù)
- 概念
- 使用placeholders名字
- 作用
- 削減參數(shù)數(shù)量
- 重排參數(shù)順序
- 綁定引用參數(shù)
- 泛型算法結(jié)構(gòu)——迭代器類(lèi)別
- 概念
- 不太重要的概念
- 迭代器類(lèi)別
- 輸入迭代器
- 輸出迭代器
- 前向迭代器
- 雙向迭代器
- 隨機(jī)訪問(wèn)迭代器
- 算法的命名規(guī)范
- _if版本的算法
- _copy版本的算法
- list和forward_list獨(dú)有的算法
- 概念
- 鏈表數(shù)據(jù)結(jié)構(gòu)特有的splice算法
- 多數(shù)鏈表特有的算法都與其通用版本很相似,但不完全相同。
概念
一般情況下,泛型算法本身不會(huì)執(zhí)行容器的操作,它們只會(huì)運(yùn)行于迭代器之上,執(zhí)行迭代器的操作。
泛型算法的一大優(yōu)點(diǎn)是 “泛型”,也就是一個(gè)算法可用于多種不同的數(shù)據(jù)類(lèi)型,算法與所操作的數(shù)據(jù)結(jié)構(gòu)分離。這對(duì)編程效率的提高是非常巨大的。
要做到算法與數(shù)據(jù)結(jié)構(gòu)分離,重要的技術(shù)手段就是使用迭代器作為兩者的橋梁。算法從不操作具體的容器,從而也就不存在與特定容器綁定,不適用于其他容器的問(wèn)題。算法只操作迭代器,由迭代器真正實(shí)現(xiàn)對(duì)容器的訪問(wèn)。不同容器實(shí)現(xiàn)自己特定的迭代器(但不同迭代器是相容的),算法操作不同迭代器就實(shí)現(xiàn)了對(duì)不同容器的訪問(wèn)。
因此,并不是算法應(yīng)該改變或不該改變?nèi)萜鞯膯?wèn)題。 為了實(shí)現(xiàn)與數(shù)據(jù)結(jié)構(gòu)的分離,為了實(shí)現(xiàn)通用性,算法根本就不該知道容器的存在。 算法訪問(wèn)數(shù)據(jù)的唯一通道是迭代器。是否改變?nèi)萜鞔笮?#xff0c;完全是迭代器的選擇和責(zé)任。
除了少數(shù)例外,標(biāo)準(zhǔn)庫(kù)算法都對(duì)一個(gè)范圍內(nèi)的元素進(jìn)行操作。我們將此元素范圍稱(chēng)為“輸入范圍”。接受輸入范圍的算法總是使用前兩個(gè)參數(shù)來(lái)表示此范圍,兩個(gè)參數(shù)分別是指向要處理的第一個(gè)元素和尾元素之后位置的迭代器。
find()函數(shù)
以find()函數(shù)為例來(lái)理解。傳遞給find的前兩個(gè)參數(shù)是表示元素范圍的迭代器,第三個(gè)參數(shù)是一個(gè)值。find將范圍中每個(gè)元素與給定值進(jìn)行比較。它返回指向第一個(gè)等于給定值的元素的迭代器。如果范圍中無(wú)匹配元素,則find返回第二個(gè)參數(shù)來(lái)表示搜索失敗。因此,我們可以通過(guò)比較返回值和第二個(gè)參數(shù)來(lái)判斷搜索是否成功。
由于指針就像內(nèi)置數(shù)組上的迭代器,因此我們也可以用find在數(shù)組中查找值:
迭代器令算法不依賴(lài)于容器
在find函數(shù)流程中,除了比較大小外,其他步驟都可以用迭代器操作來(lái)實(shí)現(xiàn):
但算法依賴(lài)于元素類(lèi)型的操作
雖然迭代器的使用令算法不依賴(lài)于容器類(lèi)型,但大多數(shù)算法都使用了一個(gè)(或多個(gè))元素類(lèi)型上的操作。 例如,find用元素類(lèi)型的==運(yùn)算符完成每個(gè)元素與給定值的比較。不過(guò),我們將會(huì)看到,大多數(shù)算法提供了一種方法,允許我們使用自定義的操作來(lái)代替默認(rèn)的運(yùn)算符。
算法永遠(yuǎn)不會(huì)執(zhí)行容器的操作
泛型算法運(yùn)行于迭代器之上而不會(huì)執(zhí)行容器操作的特性帶來(lái)了一個(gè)令人驚訝但非常必要的編程假定:算法永遠(yuǎn)不會(huì)改變底層容器的大小。算法可能改變?nèi)萜髦斜4娴脑氐闹?#xff0c;也可能在容器內(nèi)移動(dòng)元素,但永遠(yuǎn)不會(huì)直接添加或刪除元素。
只讀算法
一些算法只會(huì)讀取其輸入范圍內(nèi)的元素,但不改變?cè)亍?br /> 例如下面這三個(gè):
- find()
- count():接收一對(duì)迭代器和一個(gè)值作為參數(shù),返回給定值在序列中出現(xiàn)的次數(shù)。
accumulate()函數(shù)
定義在頭文件numeric中。接受三個(gè)參數(shù),前兩個(gè)指出需要求和的元素的范圍,第三個(gè)參數(shù)時(shí)和的初值,其類(lèi)型決定了函數(shù)中使用哪個(gè)加法運(yùn)算符以及返回值的類(lèi)型(這也就要求序列中元素的類(lèi)型必須與第三個(gè)參數(shù)匹配,或者能夠轉(zhuǎn)換為第三個(gè)參數(shù)的類(lèi)型)。
下面是另一個(gè)例子,由于string定義了+運(yùn)算符,所以我們可以通過(guò)調(diào)用accumulate來(lái)將vector中所有string元素連接起來(lái):
string sum = accumulate(v.cbegin(), v.cend(), string(""));此調(diào)用將v中每個(gè)元素連接到一個(gè)string上,該string初始時(shí)為空串。注意,我們通過(guò)第三個(gè)參數(shù)顯式地創(chuàng)建了一個(gè)string。如果單純將空串當(dāng)做一個(gè)字符串字面值傳遞給第三個(gè)參數(shù)是不可以的,會(huì)導(dǎo)致一個(gè)編譯錯(cuò)誤。
// error:const char*上沒(méi)有定義+運(yùn)算符 string sum = accumulate(v.cbegin(), v.cend(), "");原因在于,如果我們傳遞了一個(gè)字符串字面值,用于保存和的對(duì)象的類(lèi)型將是const char*。如前所述,此類(lèi)型決定了使用哪個(gè)+運(yùn)算符。由于const char*并沒(méi)有+運(yùn)算符,此調(diào)用將產(chǎn)生編譯錯(cuò)誤。
對(duì)于只讀取而不改變?cè)氐乃惴?#xff0c;通常最好使用cbegin()和cend()。但是,如果計(jì)劃使用算法返回的迭代器來(lái)改變?cè)氐闹?#xff0c;就需要使用begin()和end()的結(jié)果作為參數(shù)。
從兩個(gè)序列中讀取元素(equal函數(shù)為例)
迭代器作為參數(shù)形成兩個(gè)序列
一些算法從兩個(gè)序列中讀取元素。構(gòu)成這兩個(gè)序列的元素可以來(lái)自于不同類(lèi)型的容器。兩個(gè)序列中元素的類(lèi)型也不要求嚴(yán)格匹配。算法要求的只是能夠比較兩個(gè)序列中的元素。
操作兩個(gè)序列的算法之間的區(qū)別在于我們如何傳遞第二個(gè)序列。 一些算法接受三個(gè)迭代器:前兩個(gè)表示第一個(gè)序列的范圍,第三個(gè)表示第二個(gè)序列中的首元素。 其他算法接受四個(gè)迭代器:前兩個(gè)表示第一個(gè)序列的元素范圍,后兩個(gè)表示第二個(gè)序列的范圍。
用一個(gè)單一迭代器表示第二個(gè)序列的算法都假定第二個(gè)序列至少與第一個(gè)一樣長(zhǎng)。確保算法不會(huì)試圖訪問(wèn)第二個(gè)序列中不存在的元素是程序員的責(zé)任。
equal()
equal():用于確定兩個(gè)序列是否保存相同的值。它將第一個(gè)序列中的每個(gè)元素與第二個(gè)序列中的對(duì)應(yīng)元素進(jìn)行比較。如果所有對(duì)應(yīng)元素都相等,則返回true,否則返回false。 此算法接受三個(gè)迭代器:前兩個(gè)表示第一個(gè)序列中的元素范圍,第三個(gè)表示第二個(gè)序列的首元素(第二個(gè)序列中的元素?cái)?shù)目應(yīng)大于等于第一個(gè)序列)。
根據(jù)上面的概念,我們可以比較不同容器中不同的元素類(lèi)型,只要兩者能通過(guò)“ == ” 進(jìn)行比較即可,因此,兩個(gè)序列可以是vector和list<const char*>。
那些只接受一個(gè)單一迭代器來(lái)表示第二個(gè)序列的算法,都假定第二個(gè)序列至少與第一個(gè)序列一樣長(zhǎng)。
寫(xiě)容器元素的算法
概念
一些算法將新值賦予序列中的元素。 當(dāng)我們使用這類(lèi)算法時(shí),必須注意確保序列原大小不小于我們要求算法寫(xiě)入的元素?cái)?shù)目。 記住,算法不會(huì)執(zhí)行容器操作,因此它們自身不可能改變?nèi)萜鞯拇笮 ?/strong>
一些算法會(huì)自己向輸入范圍寫(xiě)入元素。這些算法本質(zhì)上并不危險(xiǎn),它們最多寫(xiě)入與給定序列一樣多的元素。
但是有一些算法接受一個(gè)迭代器和一個(gè)計(jì)數(shù)值來(lái)劃定范圍,然后寫(xiě)入元素。這種算法假定目的位置足夠大,能容納要寫(xiě)入的元素,也就是說(shuō)算法不檢查寫(xiě)操作。檢查目標(biāo)輸入序列是否為空是程序員的責(zé)任,對(duì)空容器調(diào)用這些算法得到的結(jié)果是未定義的(但是可以通過(guò)back_insert(),來(lái)實(shí)現(xiàn)對(duì)空容器的操作)。
分別以 fill() 和 fill_n() 為例來(lái)探究:
fill()
接受一對(duì)迭代器表示一個(gè)范圍,還接受一個(gè)值作為第三個(gè)參數(shù)。將給定值賦予序列中的每個(gè)元素。
給定范圍有效:
給定范圍越界:
給定容器為空:
fill_n()
fill_n接受一個(gè)單迭代器、一個(gè)計(jì)數(shù)值和一個(gè)給定值。他將給定值賦予迭代器指向的元素開(kāi)始的計(jì)數(shù)值個(gè)元素。
給定范圍有效:
給定范圍越界:
給定容器為空:
插入迭代器back_inserter
- 是定義在頭文件iterator中的一個(gè)函數(shù)。
- 是一種保證算法有足夠元素空間來(lái)容納輸出數(shù)據(jù)的方法
- 是一種向容器中添加元素的迭代器。
- 接受一個(gè)指向容器的引用,返回一個(gè)與該容器綁定的插入迭代器。
- 當(dāng)我們通過(guò)此迭代器賦值時(shí),賦值運(yùn)算符會(huì)調(diào)用push_back將一個(gè)具有給定值的元素添加到容器中。
實(shí)例:
也可以使用back_inserter來(lái)創(chuàng)建一個(gè)迭代器,作為算法的目的位置來(lái)使用:
這樣可以避免對(duì)空容器操作的未定義行為。具體實(shí)現(xiàn):
在每步迭代中,fill_n向給定序列的一個(gè)元素賦值。由于我們傳遞的參數(shù)是back_inserter返回的迭代器,因此每次賦值都會(huì)在vc上調(diào)用push_back(是向空容器添加元素,而不再是簡(jiǎn)簡(jiǎn)單單的對(duì)空容器的元素賦值這一未定義行為)。最終,這條fill_n調(diào)用語(yǔ)句向vec的末尾添加了10個(gè)元素,每個(gè)元素的值都是0。
當(dāng)我們向fill_n傳遞back_inserter時(shí),雖然最終效果是向容器添加了新的元素,但對(duì)fill_n來(lái)說(shuō),根本不知道這回事兒。它仍然像往常一樣(通過(guò)迭代器)向元素賦予新值,只不過(guò)這次是通過(guò)back_inserter來(lái)賦值,而back_inserter選擇將新值添加到了容器而已。
插入迭代器是否與“標(biāo)準(zhǔn)庫(kù)算法不會(huì)改變它們所操作的容器的大小”相悖
嚴(yán)格來(lái)說(shuō),標(biāo)準(zhǔn)庫(kù)算法根本不知道有“容器”這個(gè)東西。它們只接受迭代器參數(shù),運(yùn)行于這些迭代器之上,這些迭代器只能順序或隨機(jī)訪問(wèn)容器中的元素,造成的效果就是算法只能讀取元素、改變?cè)刂怠⒁苿?dòng)元素,但無(wú)法添加或刪除元素。
但當(dāng)我們傳遞給算法插入器,例如back_inserter時(shí),由于這類(lèi)迭代器能調(diào)用下層容器的操作(如push_back)來(lái)向容器插入元素,造成的算法執(zhí)行的效果就是向容器中添加了元素。
因此,關(guān)鍵要理解:標(biāo)準(zhǔn)庫(kù)算法從來(lái)不直接操作容器,它們只操作迭代器,從而間接訪問(wèn)容器。能不能插入和刪除元素,不在于算法,而在于傳遞給它們的迭代器是否具有這樣的能力。
拷貝(copy)算法
拷貝算法是另一個(gè)向目的位置迭代器指向的輸出序列中的元素寫(xiě)入數(shù)據(jù)的算法。
- 接受三個(gè)迭代器,前兩個(gè)迭代器設(shè)置范圍,第三個(gè)表示目標(biāo)序列的起始位置。
- 將前兩個(gè)迭代器劃定的輸入范圍中的元素拷貝到第三個(gè)迭代器指定的目標(biāo)序列中。
- 后面的序列必須能容納前面的元素
可以實(shí)現(xiàn)內(nèi)置數(shù)組的拷貝:
int a1[] = {0, 1, 2, 3, 4};int a2[sizeof(a1)/sizeof(*a1)]; // 與a1大小一樣auto ret = copy(begin(a1), end(a1), a2); // 把a(bǔ)1的內(nèi)容拷貝給a2// ret指向拷貝到a2的尾元素之后的位置copy返回的是其目的位置迭代器(遞增后)的值。即,ret恰好指向拷貝到a2的尾元素之后的位置。
也可用來(lái)實(shí)現(xiàn)不改變?cè)蛄兄档哪康?#xff1a;
多個(gè)算符都提供“拷貝”版本。計(jì)算新元素的值,但不會(huì)將它們放置在輸入序列的末尾,而是創(chuàng)建一個(gè)新序列保存這些結(jié)果。以replace算法為例:
replace算法
replace算法讀入一個(gè)序列,并將其中所有等于給定值的元素都改為另一個(gè)值。
此算法接受4個(gè)參數(shù):
- 前兩個(gè)是迭代器,表示輸入序列;
- 后兩個(gè)一個(gè)是要搜索的值;
- 另一個(gè)是新值。它將所有等于第一個(gè)值的元素替換為第二個(gè)值。
如果我們希望保留原序列不變,可調(diào)用replace_copy。該算法接受額外第三個(gè)迭代器參數(shù),指出調(diào)整后序列的保存位置:
replace_copy(vc.cbegin(), vc.cend(), back_inseret(ivec), 0, 2);調(diào)用后,vc未改變,ivec包含vc的一份拷貝,ivec中原vc對(duì)應(yīng)位置的0都變?yōu)?。
這里的容器大小指的是元素?cái)?shù)量
舉個(gè)例子:
即使我們用reserve分配了至少能容納10個(gè)int的內(nèi)存空間。但調(diào)用fill_n的行為仍然是未定義的。
這是因?yàn)榉盒退惴▽?duì)于容器的要求并不是有足夠的空間,而是足夠的元素?cái)?shù)量。
而reserve調(diào)整的是capacity的值,而非size的值(下面兩圖),換言之,此時(shí)vc依然為空,沒(méi)有任何元素。而算法又不具備向容器添加元素的能力, 因此fill_n仍然失敗。
定制操作
上面說(shuō)過(guò),sort算法默認(rèn)使用元素類(lèi)型的<運(yùn)算符。但我們可能遇見(jiàn)下列情況:
- 我們希望的排序順序與<所定義的順序不同
- 序列可能保存的是未定義<運(yùn)算符的元素類(lèi)型
這時(shí)就需要重載sort的默認(rèn)行為。
謂詞
謂詞是一個(gè)可調(diào)用的表達(dá)式,返回結(jié)果是一個(gè)能用做條件的值。
標(biāo)準(zhǔn)庫(kù)算法使用的謂詞分為兩類(lèi):一元謂詞(只接受單一參數(shù))和二元謂詞(有兩個(gè)參數(shù))。接受謂詞參數(shù)的算法對(duì)輸入序列中的元素調(diào)用謂詞。因此,元素類(lèi)型必須能轉(zhuǎn)換為謂詞的參數(shù)類(lèi)型。
實(shí)例:
按照string的<運(yùn)算符排序:
使單詞按照長(zhǎng)度排序,相同長(zhǎng)度的單詞按照字典序排列:
stable
帶有stable的函數(shù)可保證相等元素的原本相對(duì)次序在排序后保持不變。
實(shí)例,找出vector<string>內(nèi)長(zhǎng)度大于等于5的元素:
partition有時(shí)會(huì)打亂原有相對(duì)次序:
stable_partition會(huì)在排序的基礎(chǔ)上盡量維持原來(lái)的相對(duì)次序:
可調(diào)用對(duì)象
可以向一個(gè)算法傳遞任何類(lèi)別的可調(diào)用對(duì)象。
可調(diào)用:對(duì)于一個(gè)對(duì)象或一個(gè)表達(dá)式,如果可以對(duì)其使用調(diào)用運(yùn)算符——(),則稱(chēng)它為可調(diào)用的。
可調(diào)用對(duì)象有四種:
lambda表達(dá)式
概念
有時(shí)我們希望進(jìn)行的操作需要更多的參數(shù),以至于超出了算法對(duì)為此的限制(二元謂詞無(wú)法滿(mǎn)足需求)。此時(shí)就可以使用lambda表達(dá)式。
一個(gè)lambda表達(dá)式表示一個(gè)可調(diào)用的代碼單元。我們可以將其理解為一個(gè)未命名的內(nèi)聯(lián)函數(shù)。
與任何函數(shù)類(lèi)似,一個(gè)lambda具有一個(gè)返回類(lèi)型、一個(gè)參數(shù)列表和一個(gè)函數(shù)體。 但與函數(shù)不同,lambda可能定義在函數(shù)內(nèi)部。一個(gè)lambda表達(dá)式具有如下形式:
- capture list(捕獲列表)是一個(gè)lambda所在函數(shù)中定義的局部變量的列表(通常為空);
- return 表示返回類(lèi)型。
- parameter list表示參數(shù)列表。
- function body表示函數(shù)體。
- 與普通函數(shù)不同,lambda必須使用尾置返回來(lái)指定返回類(lèi)型。
- 我們可以忽略參數(shù)列表和返回類(lèi)型,但必須永遠(yuǎn)包含捕獲列表和函數(shù)體。
- lambda的調(diào)用方式與普通函數(shù)的調(diào)用方式相同,都是使用調(diào)用運(yùn)算符。
- lambda不能有默認(rèn)實(shí)參。換言之,調(diào)用的實(shí)參數(shù)目永遠(yuǎn)與形參數(shù)目相等。
- lambda表達(dá)式之間不能相互賦值,即使看起來(lái)類(lèi)型相同。
在lambda中忽略括號(hào)和參數(shù)列表等價(jià)于指定一個(gè)空參數(shù)列表。
關(guān)于返回類(lèi)型:
- 如果忽略返回類(lèi)型,lambda根據(jù)函數(shù)體中的代碼推斷出返回類(lèi)型。
- 如果函數(shù)體只是一個(gè)return語(yǔ)句,則返回類(lèi)型從返回的表達(dá)式的類(lèi)型推斷而來(lái)。
- 如果包含任何單一return語(yǔ)句之外的內(nèi)容,則返回類(lèi)型為void,此時(shí)lambda不能返回值。(C++11)
一個(gè)僅忽略返回類(lèi)型的實(shí)例:
[](const string& a, const string& b){return a.size() < b.size();}空捕獲列表表明此lambda不使用它所在函數(shù)中的任何局部變量。
一個(gè)lambda表達(dá)式表示一個(gè)可調(diào)用的代碼單元。我們可以將其看為一個(gè)未命名的內(nèi)聯(lián)函數(shù):
原理
當(dāng)定義了lambda表達(dá)式之后,編譯器按照仿函數(shù)的形式自動(dòng)生成一個(gè)lamber_uuid類(lèi)(本質(zhì)還是未命名的),并重載其中的operator(),當(dāng)用戶(hù)進(jìn)行調(diào)用的時(shí)候會(huì)自動(dòng)通過(guò)該類(lèi)的匿名對(duì)象來(lái)調(diào)用,使得其看起來(lái)和普通的函數(shù)一樣。
當(dāng)向一個(gè)函數(shù)傳遞一個(gè)lambda時(shí),同時(shí)定義了一個(gè)新類(lèi)型和該類(lèi)型的一個(gè)對(duì)象:傳遞的參數(shù)就是此編譯器生成的類(lèi)類(lèi)型的未命名對(duì)象。類(lèi)似的,當(dāng)使用auto定義一個(gè)用lambda初始化的變量時(shí),定義了一個(gè)從lambda生成的類(lèi)型的對(duì)象。
默認(rèn)情況下,從lambda生成的類(lèi)都包含一個(gè)對(duì)應(yīng)該lambda所捕獲的變量的數(shù)據(jù)成員。類(lèi)似任何普通類(lèi)的數(shù)據(jù)成員,lambda的數(shù)據(jù)成員也在lambda對(duì)象創(chuàng)建時(shí)被初始化。
捕獲列表
- 捕獲列表只用于局部非static變量,lambda可以直接使用局部static變量和在它所在函數(shù)之外聲明的名字。
捕獲列表允許的形式:
下面我們通過(guò)實(shí)例來(lái)詳細(xì)了解:
find_if
用find_if尋找vs中長(zhǎng)度大于5的元素。
find_if返回一個(gè)迭代器,指向第一個(gè)長(zhǎng)度不小于給定參數(shù)的元素。如果這樣的元素不存在,返回尾后迭代器的拷貝。
for_each
通過(guò)for_each算法來(lái)打印find_if找到的符合要求的元素。
接受一個(gè)可調(diào)用對(duì)象,并對(duì)輸入序列中每個(gè)元素調(diào)用此對(duì)象。
捕獲方式
值捕獲
采用值捕獲的前提是變量可以拷貝。與傳值參數(shù)不同的是,被捕獲的變量的值是在lambda創(chuàng)建時(shí)拷貝,而不是調(diào)用時(shí)拷貝:
由于被捕獲變量的值是在創(chuàng)建lambda時(shí)拷貝的,因此對(duì)其后續(xù)修改不會(huì)影響到lambda內(nèi)對(duì)應(yīng)的值。
mutable
默認(rèn)情況下,lambda函數(shù)總是一個(gè)const函數(shù),mutable可以取消其常量性。使用該修飾符時(shí),參數(shù)列表不可省略(即使參數(shù)為空)。
如果我們希望能改變一個(gè)被捕獲的變量的值,必須在參數(shù)列表首加上關(guān)鍵字mutable。 但是由于上面的被捕獲變量的值是在創(chuàng)建lambda時(shí)拷貝的,因此在lambda表達(dá)式內(nèi)修改變量值也不會(huì)影響值本身:
引用捕獲
在lambda函數(shù)體內(nèi)使用引用捕獲的變量時(shí),實(shí)際上使用的是引用所綁定的對(duì)象。
引用捕獲與返回引用有著相同的問(wèn)題:如果我們采用引用方式捕獲一個(gè)變量,就必須確保被引用的對(duì)象在lambda執(zhí)行的時(shí)候是存在的。lambda捕獲的都是局部非static變量,這些變量在函數(shù)結(jié)束后就不復(fù)存在了。如果lambda可能在函數(shù)結(jié)束后執(zhí)行,捕獲的引用指向的局部變量已經(jīng)消失。
修改引用捕獲的變量
引用捕獲是否可以修改依賴(lài)于引用指向的是否是一個(gè)const類(lèi)型。在lambda表達(dá)式內(nèi)修改引用捕獲同樣會(huì)導(dǎo)致變量改變:
隱式捕獲
隱式捕獲:通過(guò)在捕獲列表中寫(xiě)一個(gè)&或者=指示編譯器推斷捕獲列表,=表示采用值捕獲,&表示采用引用捕獲。
// st為隱式捕獲,方式為值捕獲 auto f = [=]{return st;};也可以混用隱式捕獲和顯示捕獲,但列表中第一個(gè)元素必須是一個(gè)&或者=,指定隱式捕獲方式為引用或值,且顯式捕獲的變量必須用與隱式捕獲不同的方式(顯式為引用捕獲隱式必須為值捕獲,反之亦然):
int st,c;auto f1 = [=, &c]{return st + c;}; // st隱式、值捕獲;c顯式、引用捕獲 auto f2 = [&, st]{return st + c;}; // c隱式、引用捕獲;st顯式、值捕獲function模板
概念
C++中可調(diào)用對(duì)象(如函數(shù)指針,仿函數(shù),lambda表達(dá)式等)的雖然都有一個(gè)比較統(tǒng)一的操作形式,但是定義方法五花八門(mén),這樣就導(dǎo)致使用統(tǒng)一的方式保存可調(diào)用對(duì)象或者傳遞可調(diào)用對(duì)象時(shí),會(huì)十分繁瑣。C++11中提供了std::function和std::bind統(tǒng)一了可調(diào)用對(duì)象的各種操作。
例如:
// 普通函數(shù) int add(int a, int b){return a+b;} // lambda表達(dá)式 auto mod = [](int a, int b){ return a % b;}// 仿函數(shù) struct divide{int operator()(int denominator, int divisor){return denominator/divisor;} };上面的幾種不同的可調(diào)用對(duì)象雖然類(lèi)型不同,但是根據(jù)參數(shù)和返回值可以共享同一種調(diào)用形式int(int ,int),通過(guò)function就可以統(tǒng)一其調(diào)用形式:
std::function<int(int ,int)> a = add; std::function<int(int ,int)> b = mod ; std::function<int(int ,int)> c = divide();定義格式:std::function<返回值(參數(shù)列表)> 名字
- std::function 是一個(gè)可調(diào)用對(duì)象包裝器,是一個(gè)類(lèi)模板,可以容納除了類(lèi)成員函數(shù)指針之外的所有可調(diào)用對(duì)象,它可以用統(tǒng)一的方式處理函數(shù)、函數(shù)對(duì)象、函數(shù)指針,并允許保存和延遲它們的執(zhí)行。
- std::function可以取代函數(shù)指針的作用,因?yàn)樗梢匝舆t函數(shù)的執(zhí)行,特別適合作為回調(diào)函數(shù)使用。它比普通函數(shù)指針更加的靈活和便利。
function與重載函數(shù)
在使用function的時(shí)候還有一個(gè)需要注意的點(diǎn),就是我們不能將重載過(guò)的函數(shù)直接放入function對(duì)象中,因?yàn)闀?huì)有二義性的問(wèn)題。
例如:
int add(int x, int y); double add(double x, double y);map<string, function<int(int, int)>> map; map.insert({"+", add});//此時(shí)無(wú)法判斷是哪個(gè)add//所以此時(shí)就不能直接使用函數(shù)名進(jìn)行插入 //可以通過(guò)使用函數(shù)指針來(lái)指向?qū)?yīng)函數(shù),再通過(guò)函數(shù)指針插入來(lái)消除二義性 int (*func)(int, int) = add; map.insert({"+", func});bind函數(shù)
概念
定義在functional中。相當(dāng)于一個(gè)通用的函數(shù)適配器。
調(diào)用bind的一般形式為:
auto newCallable = bind(callable, arg_list);- newCallable本身是一個(gè)可調(diào)用對(duì)象
- arg_list是一個(gè)逗號(hào)分隔的參數(shù)列表,對(duì)應(yīng)給定的callable的參數(shù)。即,當(dāng)我們調(diào)用newCallable時(shí),newCallable會(huì)調(diào)用callable,并傳遞給它arg_list中的參數(shù)。
arg_list中的參數(shù)可能包含形如_n的元素,其中n是一個(gè)整數(shù)。這些參數(shù)是 “占位符”,表示newCallable的參數(shù),它們占據(jù)了傳遞給newCallable的參數(shù)的“位置”。數(shù)值n表示生成的可調(diào)用對(duì)象中參數(shù)的位置: _1為newCallable的第一個(gè)參數(shù),_2為第二個(gè)參數(shù),依此類(lèi)推。
使用placeholders名字
名字_n都定義在一個(gè)名為placeholders的命名空間中,這個(gè)命名空間本身定義在std命名空間。
為了使用_1我們必須做出以下聲明:
using std::placeholders::_1說(shuō)明_1定義在命名空間placeholders中,而placeholders又定義在命名空間std中。
這樣寫(xiě)也有弊端:我們必須為每個(gè)_n提供單獨(dú)的using聲明,太過(guò)繁瑣。 因此我們可以使用另外一種形式:
using namespace namespace_name;這種形式說(shuō)明希望所有來(lái)自namespace_name的名字都可以在我們的程序中直接使用。
因此我們可以使用下面這兩種形式:
using namespace std::placeholders;using namespace std; using namespace placeholders;與bind函數(shù)一樣,placeholders命名空間也定義在functional頭文件中。
作用
削減參數(shù)數(shù)量
函數(shù)作為可調(diào)用對(duì)象,lambda表達(dá)式能出現(xiàn)的地方函數(shù)也可以。但問(wèn)題是算法要求一元謂詞,函數(shù)的形參大于一個(gè)怎么辦?lambda可以通過(guò)捕獲列表解決一元謂詞的限制。函數(shù)可以通過(guò)bind函數(shù)適配器削減參數(shù)數(shù)量。
還是以之前find_if函數(shù)為例:
我們可以通過(guò)形如這樣的函數(shù)起到lambda表達(dá)式的作用:
bool check_size(const string& s, string::size_type sz){return s.size() >= sz; }但是由于find_if接受一個(gè)一元謂詞,因此傳遞給find_if的可調(diào)用對(duì)象必須接受單一參數(shù)。check_size接受兩個(gè)參數(shù)顯然不符合要求。那么怎么解決呢?lambda將sz放入捕獲列表,check_size可以使用bind函數(shù),用一個(gè)定值作為其大小參數(shù)來(lái)調(diào)用check_size:
auto check5 = bind(check_size, _1, 5); // _1形式亦可寫(xiě)為placeholders::_1 // 前者需要添加語(yǔ)句 using namespace placeholders;此bind調(diào)用只有一個(gè)占位符,表示check5只接受單一參數(shù)。占位符出現(xiàn)在arg_list的第一個(gè)位置,表示check5的此參數(shù)對(duì)應(yīng)check_size的第一個(gè)參數(shù)。此參數(shù)是一個(gè)const string&。因此,調(diào)用check6必須傳遞給它一個(gè)string類(lèi)型的參數(shù),check6會(huì)將此參數(shù)傳遞給check_size。
將原來(lái)基于lambda的find_if調(diào)用替換為使用bind的版本:
bool check_size(const string& s, string::size_type sz){return s.size() >= sz; }int main(int argc, char const *argv[]) {vector<string> vs = {"daa","bbasdf","ccccc","dddd","aebbbb"};sort(vs.begin(), vs.end(), isshorter);auto check5 = bind(check_size, placeholders::_1, 5);auto flag = find_if(vs.begin(), vs.end(), check5);for_each(flag, vs.end(),[](const string& s){cout << s << " ";});cout << endl; }輸出結(jié)果:
重排參數(shù)順序
bool isshorter(const string& s1, const string& s2){return s1.size() < s2.size(); }按單詞長(zhǎng)度由短至長(zhǎng)排序:
sort(words.begin(), words.end(), isshorter);按單詞長(zhǎng)度由長(zhǎng)至短排序:
sort(words.begin(), words.end(), bind(isshorter, _2, _1));在第一個(gè)調(diào)用中,當(dāng)sort需要比較兩個(gè)元素A和B時(shí),它會(huì)調(diào)用isshorter(A,B)。
在第二個(gè)對(duì)sort的調(diào)用中,傳遞給isshorter的參數(shù)被交換過(guò)來(lái)了。因此,當(dāng)sort比較兩個(gè)元素時(shí),就好像調(diào)用isshorter(B,A)一樣。
綁定引用參數(shù)
默認(rèn)情況下,bind的那些不是占位符的參數(shù)被拷貝到bind返回的可調(diào)用對(duì)象中。 但是,與lambda類(lèi)似,有時(shí)對(duì)有些綁定的參數(shù)我們希望以引用方式傳遞,或是要綁定參數(shù)的類(lèi)型無(wú)法拷貝。
我們想用函數(shù)替換下面lambda表達(dá)式:
ostream& os = cout; for_each(flag, vs.end(),[&os](const string& s) {os << s << " ";});目標(biāo)功能函數(shù):
ostream &print(ostream & os, const string&s){return os << s; }如果要用它替換lambda就必須用到bind:
ostream &os(cout); for_each(flag, vs.end(), bind(print, os, _1));但是這樣做是錯(cuò)誤的,我們說(shuō),bind拷貝參數(shù)列表給返回的可調(diào)用對(duì)象,ostream是不能拷貝的。 如果希望傳遞給bind一個(gè)對(duì)象而又不拷貝它,就必須使用標(biāo)準(zhǔn)庫(kù)ref函數(shù):
for_each(flag, vs.end(), bind(print, ref(os), _1));值得一提的是, 在編譯器中對(duì)ostream的引用不加ref是不報(bào)錯(cuò)的,但是不能運(yùn)行。換言之,檢查某個(gè)參數(shù)bind能否拷貝是程序員的責(zé)任。
函數(shù)ref返回一個(gè)對(duì)象,包含給定的引用。返回的對(duì)象是可拷貝的。
類(lèi)似的還有cref函數(shù),生成一個(gè)保存const引用的類(lèi)。
與bind一樣,函數(shù)ref和cref也定義在頭文件functional中。
泛型算法結(jié)構(gòu)——迭代器類(lèi)別
概念
算法分類(lèi)方式可以如同一開(kāi)始所寫(xiě)那樣,分為:是否讀、寫(xiě)或者是重排序類(lèi)中的元素。
也可以從迭代器入手進(jìn)行分類(lèi):
不同的算法要求其迭代器提供的操作不同。某些算法,如find,只要求通過(guò)迭代器訪問(wèn)元素、遞增迭代器以及比較兩個(gè)迭代器是否相等這些能力。其他一些算法,如sort,還要求讀、寫(xiě)和隨機(jī)訪問(wèn)元素的能力。算法所要求的迭代器操作可以分為5個(gè)迭代器類(lèi)別(iterator category),每個(gè)算法都會(huì)對(duì)它的每個(gè)迭代器參數(shù)指明須提供哪類(lèi)迭代器。
不太重要的概念
類(lèi)似容器,迭代器也定義了一組公共操作。一些操作所有迭代器都支持,另外一些只有特定類(lèi)別的迭代器才支持。 例如,ostream_iterator只支持遞增、解引用和賦值。vector、string和deque的迭代器除了這些操作外,還支持遞減、關(guān)系和算術(shù)運(yùn)算。
迭代器是按它們所提供的操作來(lái)分類(lèi)的,而這種分類(lèi)形成了一種層次。除了輸出迭代器之外,一個(gè)高層類(lèi)別的迭代器支持低層類(lèi)別迭代器的所有操作。
C++標(biāo)準(zhǔn)指明了泛型和數(shù)值算法的每個(gè)迭代器參數(shù)的最小類(lèi)別。例如,find算法在一個(gè)序列上進(jìn)行一遍掃描,對(duì)元素進(jìn)行只讀操作,因此至少需要輸入迭代器。replace函數(shù)需要一對(duì)迭代器,至少是前向迭代器。類(lèi)似的,replace_copy的前兩個(gè)迭代器參數(shù)也要求至少是前向迭代器。其第三個(gè)迭代器表示目的位置,必須至少是輸出迭代器。其他的例子類(lèi)似。對(duì)每個(gè)迭代器參數(shù)來(lái)說(shuō),其能力必須與規(guī)定的最小類(lèi)別至少相當(dāng)。向算法傳遞一個(gè)能力更差的迭代器會(huì)產(chǎn)生錯(cuò)誤。
值得一提的是:對(duì)于向一個(gè)算法傳遞錯(cuò)誤類(lèi)別的迭代器的問(wèn)題,很多編譯器不會(huì)給出任何警告或提示。
迭代器類(lèi)別
輸入迭代器
輸入迭代器(input iterator):只讀,不寫(xiě);單遍掃描,只能遞增。 一個(gè)輸入迭代器必須支持:
- 用于比較兩個(gè)迭代器的相等和不相等運(yùn)算符(==、!=)
- 用于推進(jìn)迭代器的前置和后置遞增運(yùn)算(++)
- 用于讀取元素的解引用運(yùn)算符(*);解引用只會(huì)出現(xiàn)在賦值運(yùn)算符的右側(cè) (將已經(jīng)解引用的輸入迭代器的值賦予變量)
- 箭頭運(yùn)算符(->),等價(jià)于(*it).member,即,解引用迭代器,并提取對(duì)象的成員
輸入迭代器只用于順序訪問(wèn)。 對(duì)于一個(gè)輸入迭代器,*it++保證是有效的,但遞增它可能導(dǎo)致所有其他指向流的迭代器失效(私以為也就是值被讀取出來(lái)了,其他指向流的迭代器指向的元素沒(méi)有了,就會(huì)導(dǎo)致它們失效)。其結(jié)果就是,不能保證輸入迭代器的狀態(tài)可以保存下來(lái)并用來(lái)訪問(wèn)元素。因此,輸入迭代器只能用于單遍掃描算法。(私以為類(lèi)似于輸入流沒(méi)法被讀取第二次。) 算法find和accumulate要求輸入迭代器;而istream_iterator是一種輸入迭代器。
輸出迭代器
輸出迭代器(output iterator):只寫(xiě)而不讀元素;單遍掃描,只能遞增。 輸出迭代器必須支持:
- 用于推進(jìn)迭代器的前置和后置遞增運(yùn)算(++)
- 解引用運(yùn)算符(*),只出現(xiàn)在賦值運(yùn)算符的左側(cè)(向一個(gè)已經(jīng)解引用的輸出迭代器賦值,就是將值寫(xiě)入它所指向的元素)
我們只能向一個(gè)輸出迭代器賦值一次。 用作目的位置的迭代器通常都是輸出迭代器。例如,copy函數(shù)的第三個(gè)參數(shù)就是輸出迭代器。ostream_iterator類(lèi)型也是輸出迭代器。
前向迭代器
前向迭代器(forward iterator):可以讀寫(xiě)元素;多遍掃描,只能遞增。這類(lèi)迭代器只能在序列中沿一個(gè)方向移動(dòng)。
- 支持所有輸入和輸出迭代器的操作
- 可以多次讀寫(xiě)同一個(gè)元素
- 可以保存前向迭代器的狀態(tài)
算法replace要求前向迭代器。forward_list上的迭代器是前向迭代器。
雙向迭代器
雙向迭代器(bidirectional iterator):可以正向/反向讀寫(xiě)序列中的元素。
- 支持所有前向迭代器的操作
- 支持前置和后置遞減運(yùn)算符(–)
算法reverse要求雙向迭代器。除了forward_list之外,其他標(biāo)準(zhǔn)庫(kù)都提供符合雙向迭代器要求的迭代器。
隨機(jī)訪問(wèn)迭代器
隨機(jī)訪問(wèn)迭代器(random-access iterator):提供在常量時(shí)間內(nèi)訪問(wèn)序列中任意元素的能力。
- 支持雙向迭代器的所有功能
- 用于比較兩個(gè)迭代器相對(duì)位置的關(guān)系運(yùn)算符(<、<=、>和>=)
- 迭代器和一個(gè)整數(shù)值的加減運(yùn)算(+、+=、-和-=),計(jì)算結(jié)果是迭代器在序列中前進(jìn)(或后退)給定整數(shù)個(gè)元素后的位置
- 用于兩個(gè)迭代器上的減法運(yùn)算符(-),得到兩個(gè)迭代器的距離
- 下標(biāo)運(yùn)算符(iter[n]),與*(iter[n])等價(jià)
算法sort要求隨機(jī)訪問(wèn)迭代器。array、deque、string和vector的迭代器都是隨機(jī)訪問(wèn)迭代器,用于訪問(wèn)內(nèi)置數(shù)組元素的指針也是。
算法的命名規(guī)范
_if版本的算法
接受一個(gè)元素值的算法通常有另一個(gè)不同名的(不是重載的)版本,該版本接受一個(gè)謂詞代替元素值。 接受謂詞參數(shù)的算法都有附加的_if前綴:
這兩個(gè)算法都在輸入范圍中查找特定元素第一次出現(xiàn)的位置。算法find查找一個(gè)指定值;算法find_if查找使得pred返回非零值的元素。
這兩個(gè)算法提供了命名上差異的版本,而非重載版本,因?yàn)閮蓚€(gè)版本的算法都接受相同數(shù)目的參數(shù),因此可能產(chǎn)生重載歧義。 雖然很罕見(jiàn),但為了避免任何可能的歧義,標(biāo)準(zhǔn)庫(kù)選擇提供不同名字的版本而不是重載。
_copy版本的算法
默認(rèn)情況下,重排元素的算法將重排后的元素寫(xiě)回給定的輸入序列中。這些算法還提供另一個(gè)版本,將元素寫(xiě)到一個(gè)指定的輸出目的位置。如我們所見(jiàn),寫(xiě)到額外目的空間的算法都在名字后面附加一個(gè)_copy:
一些算法同時(shí)提供_copy和_if版本。這些版本接受一個(gè)目的位置迭代器和一個(gè)謂詞:
兩個(gè)算法都調(diào)用了lambda(參見(jiàn)10.3.2節(jié),第346頁(yè))來(lái)確定元素是否為奇數(shù)。在第一個(gè)調(diào)用中,我們從輸入序列中將奇數(shù)元素刪除。在第二個(gè)調(diào)用中,我們將非奇數(shù)(亦即偶數(shù))元素從輸入范圍拷貝到v2中。
list和forward_list獨(dú)有的算法
概念
與其他容器不同,鏈表類(lèi)型list和forward_list定義了幾個(gè)成員函數(shù)形式的算法。例如,它們定義了獨(dú)有的sort、merge、remove、reverse和unique。通用版本的sort要求隨機(jī)訪問(wèn)迭代器,因此不能用于list和forward_list,因?yàn)檫@兩個(gè)類(lèi)型分別提供雙向迭代器和前向迭代器。
鏈表類(lèi)型定義的獨(dú)有算法中,部分算法的通用版本可以用于鏈表。(顯然上面說(shuō)的sort不在“部分”中) 但代價(jià)太高。這些算法需要交換輸入序列中的元素。一個(gè)鏈表可以通過(guò)改變?cè)亻g的鏈接而不是真的交換它們的值來(lái)快速“交換”元素。 因此,這些鏈表版本的算法的性能比對(duì)應(yīng)的通用版本好得多。
鏈表數(shù)據(jù)結(jié)構(gòu)特有的splice算法
沒(méi)有通用版本。
多數(shù)鏈表特有的算法都與其通用版本很相似,但不完全相同。
鏈表特有版本與通用版本間的一個(gè)至關(guān)重要的區(qū)別是鏈表版本會(huì)改變底層的容器。例如,remove的鏈表版本會(huì)刪除指定的元素。unique的鏈表版本會(huì)刪除第二個(gè)和后繼的重復(fù)元素。
類(lèi)似的,merge和splice會(huì)銷(xiāo)毀其參數(shù)。
例如,
- 通用版本的merge將合并的序列寫(xiě)到一個(gè)給定的目的迭代器;兩個(gè)輸入序列是不變的。
- 而鏈表版本的merge函數(shù)會(huì)銷(xiāo)毀給定的鏈表——元素從參數(shù)指定的鏈表中刪除,被合并到調(diào)用merge的鏈表對(duì)象中。在merge之后,來(lái)自?xún)蓚€(gè)鏈表中的元素仍然存在,但它們都已在同一個(gè)鏈表中。
總結(jié)
以上是生活随笔為你收集整理的泛型算法(lambda表达式、function类模板、bind函数适配器、迭代器类别、链表数据结构独有的算法)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 为什么说信用卡提前还款是大忌?看完你就清
- 下一篇: leetcode124. 二叉树中的最大