標籤:out 知識點 begin 容器 函數 param ++ 插入 排序
第十章:泛型演算法
筆記
1. 標準庫並未給每個容器添加大量功能,而是提供了一組演算法,這些演算法(通用的)中的大多數都獨立於任何特定的容器。
2. 大多數演算法都定義在標頭檔algorithm中,另外在標頭檔numeric中定義了一組數值泛型演算法。
3. 保證演算法有足夠的元素空間開容納輸出資料的方法是使用插入迭代器,back_inserter接受一個指向容器的引用,返回一個與該容器綁定的插入迭代器:
vector<int> vec; // 空向量 auto it = back_inserter(vec); // 通過它賦值會將元素添加到vec中 *it = 42; // vec中現在有一個元素,值為42 // 我們常常使用back_inserter來建立一個迭代器,作為演算法的目的位置來使用 fill_n(back_inserter(vec), 10, 0); // 添加10個元素到vec
4. 謂詞是一個可調用的運算式,其返回結果是一個能用作條件的值。標準庫演算法所使用得謂詞分為兩類:一元謂詞(意味著他們只接受單一參數)和二元謂詞(意味著它們有兩個參數)。接受謂詞參數的演算法對輸入序列中的元素調用謂詞。如:
// 比較函數,用來按長度排序單詞 bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); } // 按長度由短至長排序words sort(words.begin(), words.end(), isShorter);
5. 迭代器包括:插入迭代器、流迭代器、反向迭代器、移動迭代器。
6. 插入迭代器:back_inserter 建立一個使用push_back的迭代器。
front_inserter 建立一個使用push_back的迭代器。
inserter 建立一個使用insert的迭代器。此函數接受第二個參數,這個參數必須是一個指向給定容器的迭代器。元素將插入到給定迭代器所表示的元素之前。
7. 任何演算法的最基本特性是它要求其迭代器提供哪些操作。
重點知識點總結:
一、lambda運算式:
1.介紹lambda
我們可以向一個演算法傳遞任何類別的可調用對象(callable object)。對於一個對象或一個運算式,如果可以對其使用調用運算子(即圓括弧()),則稱它為可調用的。一個lambda表示式表示一個可調用的代碼單元。我們可以將其理解為一個未命名的內嵌函式。一個lambda運算式具有如下形式:
[capture list] (parameter list) -> return type { function body }
其中,capture list(捕獲列表)是一個lambda所在函數中定義的局部變數的列表(通常為空白);return type、paramete list和 function body與任何普通函數一樣,分別表示傳回型別、參數列表和函數體。但是,與普通函數不同,lambda運算式必須使用尾置返回。如:
auto f = [] { return 42; }; // 定義了一個可調用對象f,它不接受參數,返回42 // lambda調用方式與普通函數調用方式一樣,都是使用調用運算子 cout << f() << endl; // 列印42
如果lambda的函數體包含任何單一return語句之外的內容,且未指定傳回型別,則預設返回void。
2. 向lambda傳遞參數
// 按長度排序 stable_sort(words.begin(), words.end(), [](const string &s1, const string &s2) { return s1.size() < s2.size(); } // lambda不能有預設實參 // 空捕獲列表表明此lambda不使用它所在函數中的任何局部變數
3. 使用捕獲列表
雖然lambda可以出現在一個函數中,使用其局部變數,但它只能使用那些在捕獲列表中明確指明的變數。
[sz](const string &a) { return a.size() >= sz; };
4. find_if 和 for_each演算法中使用lambda
// 擷取一個迭代器,指向第一個滿足size() >= sz 的元素,sz是一個局部變數 auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; }); // 列印長度大於等於給定值的單詞,每個單詞後面接一個空格 for_each(wc, words.end(), [] (const string &s) { cout << s << " ";} ); cout << endl; // 一個lambda可以直接使用定義在當前函數之外的的名字。 // cout不是定義在此代碼所在局部範圍的中的局部名字,而是定義在標頭檔iostream中。 // 因此,只要包含了標頭檔iostream,就可以使用cout
5. lambda捕獲和返回
當定義一個lambda時,編譯器產生一個與lambda對應的新的(未命名)類類型。當向一個函數傳遞一個lambda時,同時定義了一個新類型和該類型的一個對象:傳遞的參數就是此編譯器產生的類類型的未命名物件。
類似與參數傳遞,變數的捕獲方式也可以是值或引用。
// 值捕獲方式,採用捕獲的前提是變數可以拷貝 void fcnl() { size_t v1 = 42; // 局部變數 // 將v1拷貝到名為f的可調用對象 auto f = [v1] { return v1; }; v1 = 0; auto j = f(); // j為42,;f儲存了我們建立它時v1的拷貝 } // 與參數不同,被捕獲的變數的值是在lamba建立時拷貝,而不是調用時拷貝 // 引用捕獲, 定義lambda時可以採用引用方式捕獲變數 void fcn2() { size_t v1 = 42; // 局部變數 // 對象f2包含v1的引用 auto f2 = [&v1] { return v1; }; v1 = 0; auto j = f2(); // j為0; f2儲存v1的引用,而非拷貝 }
當以引用方式捕獲一個變數時,必須保證在lambda執行時變數時存在的。盡量減少捕獲的資料量,來避免潛在的捕獲導致的問題。
6. 隱式捕獲
除了顯式累出我們希望使用的來自所在函數的變數之外,還可以讓編譯器根據lambda體中的代碼來推斷我們要使用哪些變數。為了指示編譯器推斷捕獲列表,應在捕獲列表中寫一個&或=,&告訴編譯器採用捕獲引用方式,=則表示採用值捕獲方式。如:
// sz 為隱式捕獲,值捕獲方式 wc = find_if(words.begin(), words.end(), [=](const string &s) { return s.size() >= sz; } ); // 如果我們系統對一部分變數採用值捕獲,對其他變數採用引用捕獲,可以混合使用隱式捕獲 // 和顯示捕獲 // os隱式捕獲,引用捕獲方式; c顯式捕獲,值捕獲方式 for_each(words.begin(), words.end(), [&, c](const string &s) { os << s << c; } ); // os顯式捕獲,引用捕獲方式; c隱式捕獲,值捕獲方式 for_each(words.begin(), words.end(), [=, &os](const string &s) { os << s << c; } ); // 注意!! 當我們混合使用隱式捕獲和顯式捕獲時,捕獲列表中的第一個元素必須是一個 // &或=。此符號指定了預設捕獲方式為引用或值。並且所有顯式捕獲方式必須 // 與預設捕獲方式不同,放在&或=後面。
7.指定lambda傳回型別
預設情況下,如果一個lambda體包含return之外的任何語句,則編譯器(如果未顯式指定傳回型別)假定此lambda返回void。
二、標準庫bind函數(待寫)
術語
二元謂詞(binary predicate)、可調用對象(callable object)、捕獲列表(capture list)、插入器(inserter)、
泛型演算法(generic algorithm)、謂詞(predicate)、lambda運算式、反向迭代器(reverse iterator)、
流迭代器(stream iterator)。
2016-11-11 22:44:46
c++ primer 5th 筆記:第十章