C/C++基礎----泛型算法
算法不依賴與容器(使用叠代器),但大多數依賴於元素類型。如find需要==運算符,其他算法可能要求支持<運算符。
算法永遠不會執行容器的操作,永遠不會改變底層容器的大小(添加或刪除元素)。
accumulate(v.cbegin(), v.cend(), string(“ ”)) 算法累加運算符,第3個參數的類型決定了使用哪個+號運算符。
equal(v1.cbegin(), v1.cend(), v2.cbegin()),逐個比較兩個序列。第二個序列至少與第一個序列一樣長。
容器和元素類型都不必一樣,只要支持==符號比較兩個元素類型。
那些只接受單一叠代器來表示第二個序列的算法,都假定第二個序列至少與第一個等長。
確保算法不會訪問第二個序列中不存在的元素是程序員的責任。
算法不檢查寫操作面,向目的位置叠代器寫入數據的算法
auto it = back_inserter(vec);//返回插入叠代器,使用它賦值時會調用push_back將元素添加到容器。
fill_n(it,10,0); //添加10個元素到vec。
很多算法提供拷貝版本,返回的是一份拷貝,原序列並未改變。
sort(words.begin(), words.end());//排序
auto end_unique = unique(words.begin(), words.end());//去重,返回不重復區域後面一個位置
words.erase(end_unique,words.end());//算法不能直接增刪,調用erase刪除多余元素
即時words沒有重復元素,此操作也是安全的
謂詞(predicate)
可調用的表達式,返回結果是一個能用作條件的值。
用謂詞替代算法默認的操作,如定義二元isShorter函數,替代sort默認的<比較元素。
sort(words.begin(), words.end(), isShorter);
stable_sort(words.begin(), words.end(), isShorter);//維持相等元素的原有排序
partition將容器劃分成兩部分,true的在前。partition_stable 維持原有元素的順序
可調用對象
函數、函數指針、函數對象,lambda表達式(可理解為一個未命名的內聯函數)
[capture list] (parameter list) -> return type {function body}
其中參數列表和返回類型可以省略,但是捕獲列表和函數體必須永遠包含。
忽略參數列表等價於空參數列表
忽略返回類型,如果函數體只有一個return語句則從表達式的類型判斷;如包含其他任意語句,則返回void。
lambda
不能有默認參數
lambda只有在其捕獲列表中的捕獲一個它所在函數的局部變量,才能再函數體中使用該變量。僅限局部非static變量,局部static和所在函數外聲明的變量可以直接使用。
make_plural(count, “word”, “s”) //區分單復數,count為1則單數
find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >=sz;});
for_each(wc,words.end(),[](const string &s){cout<<s<<” ”;});
排序-去重-刪除多余-按長度排序-找到長度大於sz的起點,打印
值捕獲
前提是變量可拷貝,被捕獲的變量的值在lambda創建時拷貝,而不是調用時。
引用捕獲
需要保證捕獲的局部變量的有效性,也可以從函數返回lambda,不能包含局部變量的引用捕獲。如果可能,盡量避免捕獲指針或引用。
lambda捕獲列表 |
|
[] |
空捕獲列表 |
[names] |
名字列表,默認都是被拷貝 |
[&] |
隱式捕獲列表,都采用引用捕獲 |
[=] |
隱式捕獲列表,都采用值捕獲 |
[&,identifier_list] |
除個別值捕獲 |
[=, identifier_list] |
除個別引用捕獲 |
可變lambda
默認不會改變被捕獲的變量的值,如希望改變,使用mutable。
對於只在一兩個地方使用的簡單操作,lambda表達式是最有用的。
對於在很多地方使用的相同操作,或者一個操作需要很多語句才能完成,通常定義一個函數更好。
捕獲列表為空的lambda,通常可以用函數替代;
而對於有捕獲變量的,就不那麽容易了。會面臨謂詞參數個數不一致的問題。
bind函數(函數適配器)
接受一個可調用對象,生成一個新的可調用對象來適應原對象的參數列表。
auto newCallable = bind(callable, arg_list);
arg_list對應給callable的參數。其中可能包含_n形式的名字,表示占據了傳遞給newCallable參數的第n個位置。
bool check_size(const string& s, string::size_type sz) {return s.size() >= sz;}
auto check6 = bind(check_size, _1, 6);//
bind調用只有一個占位符,便是check6只接收一個參數,占位符出現在第一個arg_list的第一個位置,表示check6此參數對應check_size的第一個參數const string&。
auto g =bind(f, a, b, _2, c, _1);//f有5個參數,其中_1和_2分別對應g的第1個和第2個參數
g(_1,_2)映射為f(a, b, _2, c, _1)
需要包含命名空間using std::placeholders::_1;
using namespace std::placeholders;
對於不是占位符的參數,默認是拷貝到bind返回的可調用對象中的,有時候需要用引用方式傳遞ref(os),cref()生成const引用。
bind ref cref都在頭文件functinal裏。
註:bind1st和bind2nd,分別只能綁定第一個或第二個參數,由於局限性強已經在新標準中棄用。binder1st和binder2nd也類似,只不過他們是模板類需要指定op的類型
bind1st(op, value)(x) -> op(value,x)
bind2nd(op,value)(x) -> op(x,value)
其他叠代器
插入叠代器:綁定到容器上,可以向容器插入元素
流叠代器:綁定到輸入輸出流,可以遍歷關聯的IO流,只要定義了<<或>>運算符的對象
反向叠代器:向後移動,forward_list沒有
移動叠代器:不是拷貝其中的元素,而是移動move它們。
插入叠代器
*it, ++it, it++ 不會對it做任何事情,返回it
back_inserter push_back
front_inserter push_front
inserter insert
如inserter生成的叠代器做如下賦值操作 *it = val;
效果相當於以下代碼
it = c.insert(it, val);//it指向新加入的首元素
++it;
使用不同的插入叠代器,最後生成的序列順序會不同。
流叠代器
創建時必須制定將要讀寫的對象類型,可以在創建時將其綁定到一個流上,也可以默認初始化叠代器當尾後叠代器使用。
istream_iterator<int> in_iter(cin);
istream_iterator<int> eof;
while(in_iter!=eof)
vec.push_back(*in_iter++);
可以改成成如下形式
istream_iterator<int> in_iter(cin), eof;
vector<int> vec(in_iter, eof); //從叠代器範圍構造vec
從流中讀取的數據來構造vec
與某些算法結合,來處理流數據。輸入流叠代器可以作為源叠代器,輸入作為目標叠代器
如cout<<accumulate(in, eof, 0)<<endl;
istream_iterator允許懶惰求值,推遲中流中讀取數據。標準庫保證在第一次解引用前,從流中讀取數據的操作已經完成。
如果叠代器可能銷毀,或者兩個對象同步使用一個流,那麽何時讀取就很重要了。
ostream_iterator允許第二個參數,是C風格的字符串,在輸出每個元素之後都會打印此字符串。必須綁定到一個流。
*out, ++out, out++提供形式支持,不會做任何事情
ostream_iterator<int> out_iter (cout, “ “);
for (auto e : vec)
*out_iter++ = e;
cout<<endl;
其中*和++都可以忽略,但是推薦這種寫法,可以保持與其他叠代器的使用保持一致,修改容易,而且對讀者來說也更為清晰。
反向叠代器
使我們可以使用算法透明地向前或向後處理容器。
需要既支持++也支持--,forward_list和流叠代器不能創建。
當使用反向叠代器,在打印的時候會反向,需要使用base成員來轉換。
[line.crbegin(), rcomma)和[rcomma.base(), line.end())指向line中相同的元素範圍。
另外反向叠代器表示的範圍是不對稱的,當從普通叠代器初始化反向叠代器或是給其賦值的時候,其指向並不是相同的。
5種叠代器類型
叠代器類別 |
|
輸入叠代器 |
只讀,不寫;單遍掃描,只能遞增 |
輸出叠代器 |
只寫,不讀;單遍掃描,只能遞增 |
前向叠代器 |
可讀寫;多遍掃描,只能遞增 |
雙向叠代器 |
可讀寫;多遍掃描,可遞增遞減 |
隨機訪問叠代器 |
可讀寫;多遍掃描,支持全部叠代器運算 |
算法指明了叠代器參數的最小類別,如傳遞的叠代器參數基本更低會產生錯誤。
輸入叠代器:
至少支持==, !=, 前後置++, 解引用*(賦值的右側), ->
*it++保證是有效的,但是遞增可能導致其他叠代器失效,不能保證輸入叠代器的狀態可以保存下來並用來訪問元素。因此只能用於單遍掃描算法。
如find和accumulate支持istream_iterator
輸出叠代器:(可看做輸入叠代器的補集)
至少支持前後置++, 解引用*(賦值的左側),只能向一個輸出叠代器賦值一次,單遍掃描。
如copy第3個參數,ostream_iterator
前向叠代器:
replace, forward_list
雙向叠代器:(--)
reverse
隨機訪問叠代器:
< <= > >= + += - -= 兩個叠代器相減 下標運算符
sort array deque string vector 內置數組元素的指針
常見算法形參類型
alg(beg, end, other args);
alg(beg, end, dest, other args);
alg(beg, end, beg2, other args);
alg(beg, end, beg2, end2, other args);
向輸出叠代器接入數據的算法都是假定目標空間足夠。
單個beg2的算法假定從beg2開始的序列至少與序列1一樣大。
算法命名規範
重載傳遞一個謂詞
_if版本 將接受一個元素值版本改成接受一個謂詞版本,使用不同的函數名避免可能的重載歧義。
copy版本
默認重排元素的算法寫回輸入序列,copy版本寫到一個指定的輸出目的位置。
鏈表類型特有算法
其他通用算法也可以用於鏈表,但是代價過高,需要交換元素。鏈表可以通過改變鏈接來完成交換,而不用真正交換元素。所以優先使用成員函數版本。
list forward_list成員函數版本算法(都返回void) |
|
lst.merge(lst2) |
合並,兩個都必須是有序的,合並之後lst2為空 |
lst.merge(lst2,comp) |
同上,使用謂詞給定的比較操作 |
lst.remove(val) |
erase刪除元素 |
lst.remove_if(pred) |
同上 |
lst.reverse() |
反轉序列 |
lst.sort() |
使用<或給定比較操作排序 |
lst.sort(comp) |
|
lst.unique() |
刪除同一個值得連續拷貝 |
lst.unique(pred) |
同上,使用二元謂詞 |
splice成員
lst.splice(args)或flst.splice_after(args)
(p, lst2) 將lst2中所有元素移動到lst中p之前或flst中p之後,元素將從lst2刪除,不能為同一個鏈表
(p, lst2, p2) 將p2指向的元素移動到lst中,可以是相同的鏈表
(p, lst2, b, e) 將b和e之間指向的元素移動到lst中,可以是相同的鏈表,可以是同鏈表但不能包含p。
鏈表特有版本一個重要的區別是會改變底層的容器。
C/C++基礎----泛型算法