1. 程式人生 > >C++Primer讀書筆記十——泛型演算法.md

C++Primer讀書筆記十——泛型演算法.md

概述 在前一篇我們介紹了容器的基本概念以及使用其成員函式進行增刪改查,但有的時候我們還希望對容器進行更多的操作,比如:查詢特定元素,替換元素等。而標準庫並未給出此類成員函式, 此時需要引入algorithm標頭檔案,其中定義了一系列的操作演算法。

這些演算法不直接操作容器,而是遍歷有兩個迭代器指定的範圍。

演算法永遠不會執行容器的操作 泛型演算法本身不會執行容器的操作,他們只會運行於迭代器之上,執行迭代器的操作;因此,演算法永遠不會改變底層容器的大小,演算法可能改變容器中儲存的元素的值,也可能在容器內移動元素,但永遠不會新增或刪除元素。

只讀演算法

一些演算法只會讀取其輸入範圍內的元素,而從不更改元素,find,count就是如此 int sum = accumulate(vec.begin(),vec.end(),0); 對vec進行求和,和的初值為0; 演算法和元素型別

accmulate的第三個引數的型別決定了函式中使用哪種加法運算子,以及返回值型別; 同時也意味著將元素型別加到和的型別上的操作必須是可行的 由於string定義了+運算子,所以可以通過呼叫 string sum = accumulate(v.begin(),v.end(),string(""));//正確

但const char * 上沒有定義+運算子 string sum = accumulate(v.begin(),v.end(),"");//錯誤 操作兩個序列的演算法 另一個只讀演算法是equal,用於確定兩個序列是否儲存相同的值。接受三個迭代器,前兩個迭代器表示第一個序列的範圍,第三個表示第二個序列中的首元素 //v1應至少與v2中的元素一樣多 equal(v1.begin(),v1.end(),v2.begin()); 由於equal利用迭代器進行操作,所以可以用equal來比較兩個不同型別的容器中的元素,而且元素型別也不必相同,只要我們能用==來比較兩個元素型別即可。

寫容器元素的演算法 一些演算法將新值賦予序列中的元素,當我們使用這類元素時必須確保序列原大小不小於我們要求演算法寫入的數目,因為演算法不會執行容器的操作,因此他們自身可能不會改變容器的大小。 演算法fill接受一對迭代器表示一個範圍,還接受一個值作為第三個引數。fill將這個給定的值賦予輸入序列中的每個元素。 fill(v.begin(),v.end(),0);//將每個元素置0;

演算法不檢查寫操作 fill_n(dest,n,val); fill_n假定dest指向一個元素,而從dest開始的序列至少包含n個元素,否則將會出現未定義行為。 back_inserter back_inserter接受一個指向容器的引用,返回一個與該容器繫結的插入迭代器,當我們通過此迭代器賦值時,賦值運算子會呼叫push_back將一個具有給定值的元素新增容器中

vector<int> vec;//空向量
auto it = back_inserter(vec);//通過它將賦值會將元素新增到vec中
*it = 42;//vec中現有一個元素,值為42

常常使用back_inserter來建立一個迭代器,作為演算法的目的位置使用 vector vec; fill_n(back_inserter(vec),10,0); 每個元素的值都將為0;

拷貝演算法 auto ret = copy(begin(a1),end(a1),a2);//把a1內容拷貝給a2;

copy返回的是其目的位置迭代器的值。即,ret恰好指向拷貝到a2的尾元素之後的位置。

replace(v.begin(),v.end(),0,42);//將v中所有值為0的元素替換為42,如果希望原序列保持變可以呼叫replace_copy; replace_copy(ilst.cbegin(),ilst.cend(),back_inserter(ivec),0,42); 呼叫後ivec儲存了了ilst的拷貝,並且把其中值為0的元素替換為42; 消除重複元素 利用sort和earse來消除容器中的重複元素

sort(v.begin(),v.end());//對容器進行排序
//unique重排輸入範圍,使得每個單詞只出現一次
//排列在範圍的前部分,返回指向不重複區域之後的一個位置
auto end_unique = unique(v.begin(),v.end());
//使用earse刪除重複元素
v.earse(end_unique,words.end());

unique使得不重複元素出現在容器的前部分,返回迭代器指向最後一個不重複元素之後的位置,此部分依然存在,但我們不知道值是什麼 注意:unique作用於有序容器才有效!!!! 向演算法傳遞引數 可以向sort傳遞一個謂詞,自定義我們想要的排序方式

bool isShort(const string &s1,const string s2)
{
return s1.size()<s2.size();
}
sort(v.begin(),v.end(),isShort())

排序演算法 在我們將容器按大小排序時,我們還希望具有相同長度的元素進行字典排序。為了保持具有相同長度的元素按字典排序可以使用stable_sort演算法


elimDups(v);//進行字典排序
//按照長度排序長度相同的單詞維持字典序
stable(v.begin(),v.end(),isShort);

lambda表示式 一個lambda表示式表示一個可呼叫的程式碼單元,可以理解為一個未命名的行內函數。 表示式形式如下所示 [capture list](parameter list) ->return type{function body}; 可以忽略引數列表和返回型別但永遠包含捕獲列表和函式體 auto f = []{return 0;} 如果lambda的函式體包含任何單一return語句之外的內容,且未指定返回值型別,則返回void 向lambda傳遞引數 使用lambda來維持stable_sort:

//按長度排序,長度相同的單詞維持字典序
stable_sort(v.begin(),v.end(),[](string a,string b)
{return a.size<b.size;}
)

使用捕獲列表

一個lambda通過將區域性變數包含在其捕獲列表中來指出將會使用那些變數。捕獲列表指引lambda在其內部包含訪問區域性變數所需的資訊。 [sz](const string &a) {return a.size() >= sz;}; 一個lambda只有在其捕獲列表中捕獲一個它所在函式的區域性變數,才能在函式體中使用該變數。 值捕獲 與傳值引數類似,採用值捕獲的前提是變數可以拷貝,被捕獲的值是在lambda建立時拷貝,而不是呼叫時拷貝

void func1()
{
  size_t v1 = 42;
  auto f = [v1]{return v1;}
  v1 = 0;
  auto j = f();//j為42
}

引用捕獲 一個與引用方式捕獲的變數與其他型別的引用行為類似,改變引用就會改變其值。

void func2()
{
size_t v1 = 42;
auto f = [&v1]{return 42;}
v1 = 0;
auto j = f();//j = 0;
}

如果lambda可能在函式結束後執行,捕獲的引用指向的區域性變數已經消失了; 隱式捕獲 為了指示編譯器推斷捕獲列表,應在列表中寫一個&或=

[=](const string &s)
{
return s.size()>=sz;
}

當混合使用預設捕獲和顯式捕獲時,捕獲列表中的第一個元素必須為一個&或=,第一個必須是隱式捕獲,且第二個元素的捕獲方式必須與第一個不同,如果第一個使用引用,則第二個必須使用值 可變lambda 如果我們希望能改變一個捕獲變數的值,就必須在引數列表首加上關鍵字mutable

void func3()
{
size_t v1 = 42;
auto f = [v1]()mutable {return v1++;}
v1 = 0;
auto j = f()//j = 43;
}

**stable_sort()**按長度排序,同時使得長度相同的元素具有字典序。 partition()對容器劃分,使得謂詞為真的元素排在前,返回最後一個使謂詞為真的元素後一個位置的迭代器 **stable_partition()首先按長度排序,將謂詞為真的元素排在前,返回最後一個使謂詞為真的元素的後一個位置的迭代器,**長度相同的元素符合字典序 find_if()返回一個迭代器,指向第一個使謂詞為真的元素,如果這樣的元素不存在返回end()的拷貝 cout_if()返回一個計數值,表示謂詞有多少次為真。 remove_if()將容器中所有使謂詞為真的元素放在尾端,返回一個迭代器指向第一個使謂詞為真的元素

標準庫bind函式實現引數繫結 如何使用函式來替換lambda,有的時候我們的謂詞只能接受一個引數,lambda幫我們解決了這個難題,其實當使用函式作為謂詞時,我們可以使用bind進行引數繫結,來接受多個引數,bind會對引數進行拷貝,定義在標頭檔案functional中。 bind的一般形式為: auto newCallable = bind(callable,arg_list); 其中newCallable是一個可呼叫的物件,arg_list是一個逗號分隔的引數列表,對應給定的callable引數。當我們呼叫newCallable時,會呼叫callable並傳遞給它arg_list中的引數

arg_list中,_1表示newCallable中的第一個引數,_2表示第二個,以此類推

bool check_size(const string &s,string::size_type sz)
{
return s.size()>=sz;
}
//check6是一個可呼叫物件,接受一個string型別的引數
//並用此string和值6來呼叫check_size()
auto check6 = bind(check_size,_1,6);
string s;
bool b1 = check6(s);//check(s)會呼叫check_size(s,6);

繫結引用引數

預設情況下,bind那些不是佔位符的引數被拷貝到bind返回的可呼叫函式中。但是,與lambda類似,有時對有些繫結的引數我們希望以引用方式傳遞,或是要繫結的引數的型別無法拷貝,如果我們希望傳遞給bind一個物件而不拷貝他,就必須使用標準庫函式ref函式。 bind(print,ref(os),_1);

使用佔位符_1,_2時要宣告名稱空間 using namespace std::placeholders;

再探迭代器

  • back_inserter 建立一個使用push_back的迭代器
  • front_inserter 建立一個使用push_front的迭代器
  • inserter 建立一個使用inserter的迭代器。此函式接受第二個引數,引數必須是指向容器的迭代器,元素將被插入到給定迭代器鎖所表示的元素之前,定義在標頭檔案iterator中

只有在容器支援相應的成員函式時才能使用以上迭代器。

iostream迭代器 istream_iterator in(is) in從輸入流is中讀取型別為T的值 istream_iterator end 表示尾後位置

istream_iterator<int> in(cin);
istream_iterator<int> end;
while(in!=end)
{
  v.push_back(*in++);
}
//等價於vector<int> v(in,end);

ostream_iterator out(os) out將型別為T的值寫到輸入流os中 ostream_iterator out(os,d) out將型別為T的值寫到輸入流中,每個值後面都加一個d,d指向一個空字串結尾的字元陣列;

ostream_iterator<int> out(cout," ");
*out++ = v[i];
cout<<endl;

對於list和forward_list應該優先使用成員函式版本的演算法而不是通用演算法。