022 參數(shù)綁定

對(duì)于那種只在一兩個(gè)地方使用的簡(jiǎn)單操作, lambda 表達(dá)式是最有用的。如果我們需要在很多地方使用相同的操作,通常應(yīng)該定義一個(gè)函數(shù),而不是多次編寫(xiě)相同的 lambda 表達(dá)式。類(lèi)似的,如果一個(gè)操作需要很多語(yǔ)句才能完成,通常使用函數(shù)更好。

如果 lambda 的捕獲列表為空,通??梢杂煤瘮?shù)來(lái)代替它。例如我們既可以用一個(gè) lambda,也可以用函數(shù)來(lái)實(shí)現(xiàn)將 vector 中的單詞按長(zhǎng)度排序。類(lèi)似的,對(duì)于打印 vector 內(nèi)容的 lambda,編寫(xiě)一個(gè)函數(shù)來(lái)替換它也是很容易的事情,這個(gè)函數(shù)只需接受一個(gè) string 并在標(biāo)準(zhǔn)輸出上打印它即可。

但是,對(duì)于捕獲局部變量的 lambda,用函數(shù)來(lái)替換它就不是那么容易了。例如,我們用在 find_if 調(diào)用中的 lambda 比較一個(gè) string 和一個(gè)給定大小。我們可以很容易地編寫(xiě)一個(gè)完成同樣工作的函數(shù):

bool check_size(const string &s,  string::size_type sz) {
    return s.size() >= sz;
}

但是,我們不能用這個(gè)函數(shù)作為 find_if 的一個(gè)參數(shù)。如前文所示,find_if 接受一個(gè)一元謂詞,因此傳遞給 find_if 的可調(diào)用對(duì)象必須接受單一參數(shù)。biggies 傳遞給 find_if 的 lambda 使用捕獲列表來(lái)保存sz。為了用 check_size 來(lái)代替此 lambda,必須解決如何向 sz 形參傳遞一個(gè)參數(shù)的問(wèn)題。

標(biāo)準(zhǔn)庫(kù) bind 函數(shù)

我們可以解決向 check_size 傳遞一個(gè)長(zhǎng)度參數(shù)的問(wèn)題,方法是使用一個(gè)新的名為 bind 的標(biāo)準(zhǔn)庫(kù)函數(shù),它定義在頭文件 functional 中??梢詫?bind 函數(shù)看作一個(gè)通用的函數(shù)適配器,它接受一個(gè)可調(diào)用對(duì)象,生成一個(gè)新的可調(diào)用對(duì)象來(lái)“適應(yīng)”原對(duì)象的參數(shù)列表。

調(diào)用bind的一般形式為:

auto  newCallable  = bind(callable,  args_list);

其中,newCallable 本身是一個(gè)可調(diào)用對(duì)象,args_list 是一個(gè)逗號(hào)分隔的參數(shù)列表,對(duì)應(yīng)給定的 callable 的參數(shù)。即,當(dāng)我們調(diào)用 newCallable 時(shí),newCallable 會(huì)調(diào)用 callable,并傳遞給它 args_list 中的參數(shù)。

args_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)推。

綁定 check_size 的 sz 參數(shù)

作為一個(gè)簡(jiǎn)單的例子,我們將使用 bind 生成一個(gè)調(diào)用 check_size 的對(duì)象,如下所示,它用一個(gè)定值作為其大小參數(shù)來(lái)調(diào)用 check_size:

// check6 是一個(gè)可調(diào)用對(duì)象,接受一個(gè) string 類(lèi)型的參數(shù)
// 并用此 string 和值 6 來(lái)調(diào)用 check
auto check6 = bind(check_size, _1, 6);

此 bind 調(diào)用只有一個(gè)占位符,表示 check6 只接受單一參數(shù)。占位符出現(xiàn)在 args_list 的第一個(gè)位置,表示 check6 的此參數(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。

string s = "hello";
bool b1 = check6(s); // check6(s) 會(huì)調(diào)用 check_size(s, 6)

使用 bind,我們可以將原來(lái)基于 lambda 的 find_if 調(diào)用:

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

替換為如下使用 check_size 的版本:

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

此 bind 調(diào)用生成一個(gè)可調(diào)用對(duì)象,將 check_size 的第二個(gè)參數(shù)綁定到 sz 的值。當(dāng) find_if 對(duì) words 中的 string 調(diào)用這個(gè)對(duì)象時(shí),這些對(duì)象會(huì)調(diào)用 check_size,將給定的 string 和 sz 傳遞給它。因此,find_if 可以有效地對(duì)輸入序列中每個(gè) string 調(diào)用 check_size,實(shí)現(xiàn) string 的大小與 sz 的比較。

使用 placeholders 名字

名字 _n 都定義在一個(gè)名為 placeholders 的命名空間中,而這個(gè)命名空間本身定義在 std 命名空間中。為了使用這些名字,兩個(gè)命名空間都要寫(xiě)上。與我們的其他例子類(lèi)似,對(duì) bind 的調(diào)用代碼假定之前己經(jīng)恰當(dāng)?shù)厥褂昧?using 聲明。例如,_1 對(duì)應(yīng)的 using 聲明為:

using std::placeholders::_1;

此聲明說(shuō)明我們要使用的名字 _1 定義在命名空間 placeholders 中,而此命名空間又定義在命名空間 std 中。

對(duì)每個(gè)占位符名字,我們都必須提供一個(gè)單獨(dú)的 using 聲明。編寫(xiě)這樣的聲明很煩人,也很容易出錯(cuò)。可以使用另外一種不同形式的 using 語(yǔ)句,而不是分別聲明每個(gè)占位符,如下所示:

using namespace namespace_name;

這種形式說(shuō)明希望所有來(lái)自 namespace_name 的名字都可以在我們的程序中直接使用。例如:

using  namespace  std::placeholders;

使得由 placeholders 定義的所有名字都可用。與 bind 函數(shù)一樣,placeholders 命名空間也定義在 functional 頭文件中。

bind 的參數(shù)

如前文所述,我們可以用 bind 修正參數(shù)的值。更一般的,可以用 bind 綁定給定可調(diào)用對(duì)象中的參數(shù)或重新安排其順序。例如,假定 f 是一個(gè)可調(diào)用對(duì)象,它有 5 個(gè)參數(shù)則下面對(duì)bind的調(diào)用:

// g 是一個(gè)有兩個(gè)參數(shù)的可調(diào)用對(duì)象
auto g = bind(f,  a,  b, _2, c, _1);

生成一個(gè)新的可調(diào)用對(duì)象,它有兩個(gè)參數(shù),分別用占位符 _2 和 _1 表示。這個(gè)新的可調(diào)用對(duì)象將它自己的參數(shù)作為第三個(gè)和第五個(gè)參數(shù)傳遞給 f。f 的第一個(gè)、第二個(gè)和第四個(gè)參數(shù)分別被綁定到給定的值 a、b 和 c 上。

傳遞給 g 的參數(shù)按位置綁定到占位符。即,第一個(gè)參數(shù)綁定到 _1,第二個(gè)參數(shù)綁定到 _2。因此,當(dāng)我們調(diào)用 g 時(shí),其第一個(gè)參數(shù)將被傳遞給f作為最后一個(gè)參數(shù),第二個(gè)參數(shù)將被傳遞給f作為第三個(gè)參數(shù)。實(shí)際上,這個(gè) bind 調(diào)用會(huì)將

g(_1, _2)

映射為

f(a,  b, _2, c, _1);

即,對(duì) g 的調(diào)用會(huì)調(diào)用 f,用 g 的參數(shù)代替占位符,再加上綁定的參數(shù) a、b 和 c。例如調(diào)用 g(X, Y) 會(huì)調(diào)用 f(a, b, Y, c, X)

用 bind 重排參數(shù)順序

下面是用 bind 重排參數(shù)順序的一個(gè)具體例子,我們可以用 bind 顛倒 isShroter 的含義:

// 按單詞長(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ú)法拷貝。

例如,為了替換一個(gè)引用方式捕獲 ostream 的 lambda:

// os 是一個(gè)局部變量,引用一個(gè)輸出流
// c 是一個(gè)局部變量,類(lèi)型為 char
for_each(words.begin(), words.end(), [&os, c](const string &s) { os << s << c; });

可以很容易地編寫(xiě)一個(gè)函數(shù),完成相同的工作:

ostream  &print(ostream &os, const string &s, char c) {
    return os << s << c;
}

但是,不能直接用 bind 來(lái)代替對(duì) os 的捕獲

// 錯(cuò)誤:不能拷貝os
for_each(words.begin(), words.end(), bind(print, os, _1, ' '));

原因在于 bind 拷貝其參數(shù),而我們不能拷貝一個(gè) ostream。如果我們希望傳遞給 bind 一個(gè)對(duì)象而又不拷貝它,就必須使用標(biāo)準(zhǔn)庫(kù)\color{RED}{ref} 函數(shù):

for_each(words.begin(), words.end(), bind(print, ref(os), _1, ' '));

函數(shù) ref 返回一個(gè)對(duì)象,包含給定的引用,此對(duì)象是可以拷貝的。標(biāo)準(zhǔn)庫(kù)中還有一個(gè) \color{RED}{cref} 函數(shù),生成一個(gè)保存 const 引用的類(lèi)。與 bind 一樣,函數(shù) ref 和 cref 也定義在頭文件 functional 中。

向后兼容:參數(shù)綁定

舊版本 C++ 提供的綁定函數(shù)參數(shù)的語(yǔ)言特性限制更多,也更復(fù)雜。標(biāo)準(zhǔn)庫(kù)定義了兩個(gè)分
別名為 bind1st 和 bind2nd 的函數(shù)。類(lèi)似 bind,這兩個(gè)函數(shù)接受一個(gè)函數(shù)作為參數(shù),
生成一個(gè)新的可調(diào)用對(duì)象,該對(duì)象調(diào)用給定函數(shù),并將綁定的參數(shù)傳遞給它。但是,這
些函數(shù)分別只能綁定第一個(gè)或第二個(gè)參數(shù)。由于這些函數(shù)局限太強(qiáng),在新標(biāo)準(zhǔn)中已被棄
用(deprecated)。所謂被棄用的特性就是在新版本中不再支持的特性。新的 C++ 程序應(yīng)
該使用 bind。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • //Clojure入門(mén)教程: Clojure – Functional Programming for the J...
    葡萄喃喃囈語(yǔ)閱讀 4,070評(píng)論 0 7
  • 這是16年5月份編輯的一份比較雜亂適合自己觀看的學(xué)習(xí)記錄文檔,今天18年5月份再次想寫(xiě)文章,發(fā)現(xiàn)簡(jiǎn)書(shū)還為我保存起的...
    Jenaral閱讀 3,174評(píng)論 2 9
  • 函數(shù)和對(duì)象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對(duì)于任何一門(mén)語(yǔ)言來(lái)說(shuō)都是核心的概念。通過(guò)函數(shù)可以封裝任意多條語(yǔ)句,而且...
    道無(wú)虛閱讀 4,968評(píng)論 0 5
  • 函數(shù)只定義一次,但可能被執(zhí)行或調(diào)用任意次。JS函數(shù)是參數(shù)化的,函數(shù)的定義會(huì)包括一個(gè)稱(chēng)為形參的標(biāo)識(shí)符列表,這些參數(shù)在...
    PySong閱讀 685評(píng)論 0 0
  • 函數(shù)只定義一次,但可能被執(zhí)行或調(diào)用任意次。JS函數(shù)是參數(shù)化的,函數(shù)的定義會(huì)包括一個(gè)稱(chēng)為形參的標(biāo)識(shí)符列表,這些參數(shù)在...
    PySong閱讀 912評(píng)論 0 0

友情鏈接更多精彩內(nèi)容