C++ primer 第10章 泛型算法

文章目錄

  • 概述
    • find
    • count
  • 初識泛型算法
    • 只讀算法
      • 只讀算法accumulate
      • 只讀算法equal
    • 寫容器元素的算法
      • 算法fill
      • 算法fill_n
      • back_inserter
      • 算法copy
      • 算法replace replace_copy
    • 重排容器元素的算法
      • sort
      • unique
      • unique_copy
  • 定制操作
    • 向算法傳遞函數
      • 謂詞
      • 算法stable_sort
      • 算法partition
    • lambda表達式
      • lambda介紹
      • 算法find_if
      • 算法for_each
    • lambda捕獲和返回
      • 值捕獲
      • 引用捕獲
      • 隱式捕獲
      • 可變lambda
      • 指定lambda返回類型
      • 算法count_if
    • 參數綁定
      • 標準庫bind函數
      • placeholders名字
      • bind的參數
      • bind重排參數順序
      • 綁定引用參數
  • 再探迭代器
    • 插入迭代器
      • back_insert示例代碼:
      • front_inserter示例代碼
      • inserter示例代碼
    • iostream迭代器
      • istream_iterator操作
      • 使用算法操作流迭代器
      • ostream_iterator操作
      • 使用流迭代器處理類類型
      • 使用流迭代器讀取一個文本文件,存入一個vector中的string里
      • 一個輸入文件,兩個輸出文件,讀取輸入文件,將奇數寫入輸出文件一,將偶數寫入輸出文件二
    • 反向迭代器
      • 反向迭代器轉換為普通迭代器
  • 泛型算法結構
    • 5類迭代器
    • 算法形參模式
      • 接受單個目標迭代器的算法
      • 接受第二個輸入序列的算法
    • 算法命名規范
  • 特定容器算法
    • list和forward_list成員函數版本的算法
      • merge()示例代碼
      • unique()示例代碼
    • splice成員
      • list示例代碼:
      • forward_list示例代碼:

概述

標準庫并未給每個容器添加大量功能,而是提供了一組算法,這些算法中的大多數都獨立于任何特定的容器,這些算法是通用的:它們可用于不同類型的容器和不同類型的元素。

大多數算法都定義在頭文件algorithm中。標準庫還在頭文件numeric中定義了一組數值泛型算法。

find

查找是否存在val,返回的是指向第一個出現val的迭代器,若不存在val,則返回container.end()

find(container.begin(),container.end(),val)

find_if算法可用來查找第一個具有特定大小的元素,接受一對迭代器,表示一個范圍。但與find不同的是,find_if的第三個參數是一個謂詞。

count

返回val出現的次數

count(container.begin(),container.end(),val)

初識泛型算法

只讀算法

一些算法只會讀取其輸入范圍內的元素,而從不改變元素。
對于只讀算法,通常最好使用cbegin()和cend(),但如果要使用算法返回的迭代器來改變元素的值,就需要使用begin()和end()作為參數。
只讀算法例如,find,count,accumulate

只讀算法accumulate

accumulate定義在頭文件numeric中,下示代碼表示對vec中的元素求和,和的初值是0

int sum = accumulate(vec.begin(),vec.end(),0);

accumulate第三個參數的類型決定了函數中使用哪個加法運算符以及返回值的類型。

string sum = accumulate(vec.begin(),vec.end(),string(""));

只讀算法equal

用于確定兩個序列是否保存相同的值。它將第一個序列中的每個元素與第二個序列中的對應元素進行比較,如果所有元素都對應相等,則返回true,否則返回false。equal可以用來比較兩個不同類型的容器中的元素,而且元素類型也不必一樣,只要能用==來比較兩個元素類型即可。 例如,vec可以是vector<string>,vec2可以是list<const char*>。

equal(vec.begin(),vec.end(),vec2.begin())

equal基于一個重要的假設:假定第二個序列至少與第一個序列一樣長,此算法要處理第一個序列中的每個元素,它假定每個元素在第二個序列中都有一個與之對應的元素。

vector<int>v1{1,3,5,7,9};
vector<int>v2{ 1,3,5,7,9,2,4,6 };
equal(v1.begin(),v1.end(),v2.begin());//truevector<int>v3{2,1,3,5,7,9,2,4,6 };
equal(v1.begin(),v1.end(),v3.begin());//false
equal(v1.begin(),v1.end(),v3.begin()+1);//true

寫容器元素的算法

一些算法將新值賦予序列中的元素。當我們使用這類算法時,必須注意確保序列原大小至少不小于我們要求算法寫入的元素數目。算法不會執行容器操作,因此它們自身不可能改變容器的大小。一些算法會自己向輸入范圍寫入元素,這些算法本質上并不危險,它們最多寫入與給定序列一樣多的元素。

算法fill

下示代碼表示將輸入范圍中的每一個元素都設置為10

fill(vec.begin(),vec.end(),10)

算法fill_n

從vec.begin()開始的n個元素設置為val,其中容器vec的大小至少為n

fill_n(vec.begin(),n,val)

back_inserter

插入迭代器是一種向容器中添加元素的迭代器。
back_inserter定義在頭文件Iterator中,接受一個指向容器的引用,返回一個與該容器綁定的插入迭代器。通過此迭代器賦值時,賦值運算符會調用push_back將一個具有給定值的元素添加到容器中。

	vector<int>vec{1,3,5,7,9};auto it = back_inserter(vec);fill_n(it,10,5);for (auto a : vec) {cout << a << " ";}cout << endl;

輸出結果為:

1 3 5 7 9 5 5 5 5 5 5 5 5 5 5

由于傳入的參數是插入迭代器,因此每次賦值都會在vec上調用push_back,最終向vec的末尾添加了10個元素,每個元素的值都是5。

算法copy

將輸入范圍中的元素拷貝到v2中,v2至少要包含與輸入序列一樣多的元素。
copy返回的是拷貝到v2尾元素之后的位置。

auto iter=copy(v1.begin(),v1.end(),v2.begin());

示例代碼

	vector<int>vec{1,3,5,7,9};vector<int>vec2{ 1,2,3,4,5,6,7,8,9};auto it = copy(vec.begin(), vec.end(), vec2.begin());for (auto a : vec2) {cout << a << " ";}cout << endl;cout <<*it<< endl;

輸出結果:

1 3 5 7 9 6 7 8 9
6

算法replace replace_copy

將輸入序列中值為0的元素改為42

replace(vec.begin(), vec.end(),0,42);

如果希望保留原序列不變,可以調用 replace_copy

replace_copy(vec.begin(), vec.end(),back_inserter(vec2),0,42);

示例代碼一:

	vector<int>vec{1,3,5,7,9};vector<int>vec2;replace_copy(vec.begin(), vec.end(), back_inserter(vec2), 3, 42);for (auto a : vec2) {cout << a << " ";}cout << endl;
輸出結果:
1 42 5 7 9

示例代碼二:

	vector<int>vec{1,3,5,7,9};vector<int>vec2{ 1,2,3,4,5,6,5,8,9};replace(vec2.begin(), vec2.end(), 5,10);replace_copy(vec.begin(), vec.end(), back_inserter(vec2), 3, 42);for (auto a : vec2) {cout << a << " ";}cout << endl;輸出結果:此處插入迭代器調用push_back
1 2 3 4 10 6 10 8 9 1 42 5 7 9

重排容器元素的算法

某些算法會重排容器中元素的順序。

sort

sort是利用元素類型的<運算符來實現排序的。

sort(vec.begin(), vec.end());

sort還可接受第三個參數,此參數是一個謂詞,以此來重載sort的默認行為。

unique

要求源容器中,重復元素相鄰存放,返回最后一個不重復元素之后的位置

auto end_unique=unique(vec.begin(), vec.end());

示例代碼:

	vector<int>vec{1,3,5,7,9,3,5,2,7,4};sort(vec.begin(), vec.end());cout << "sort后:";for (auto a : vec) {cout << a << " ";}cout << endl;auto it = unique(vec.begin(), vec.end());cout << "unique后:";for (auto a : vec) {cout << a << " ";}cout << endl;cout << "unique返回的迭代器指向的元素:" << *it << endl;vec.erase(it,vec.end());cout << "erase后:";for (auto a : vec) {cout << a << " ";}cout << endl;
sort后:1 2 3 3 4 5 5 7 7 9
unique后:1 2 3 4 5 7 9 7 7 9
unique返回的迭代器指向的元素:7
erase后:1 2 3 4 5 7 9

從輸出結果可以看出,此例重復元素是3,5,7,unique后:1 2 3 4 5 7 9 7 7 9,而最后三個元素是7 7 9不是3 5 7,此處是由算法決定的。unique只是返回最后一個不重復元素之后的位置。

unique_copy

要求源容器中,重復元素相鄰存放

auto end_unique=unique_copy(vec.begin(), vec.end(),vec2.begin());

示例代碼:

	vector<int>vec{1,3,5,7,9,3,7,2,5};list<int>lst;sort(vec.begin(), vec.end());unique_copy(vec.begin(),vec.end(),back_inserter(lst));for (auto a : lst) {cout << a << " ";}

輸出結果:

1 2 3 5 7 9

定制操作

向算法傳遞函數

謂詞

謂詞是一個可調用的表達式,其返回結果是一個能用作條件的值。
一元謂詞:只接受單一參數。
二元謂詞:有兩個參數。
接受謂詞參數的算法對輸入序列中的元素調用謂詞,因此,元素類型必須能轉換為謂詞的參數類型。
示例:

bool isShorter(const string &s1,const string &s2){return s1.size()<s2.size();
}
//按長度由短至長排序words
sort(words.begin(),words.end(),isShorter);

算法stable_sort

穩定排序算法,維持相等元素的原有順序。

stable_sort(words.begin(),words.end(),isShorter);

算法partition

對容器內容按謂詞進行劃分,使得謂詞為true的值會排在容器的前半部分, 使得謂詞為false的值會排在容器的后半部分。算法返回一個迭代器,指向最后一個使謂詞為true的元素之后的位置。

auto it=partition(vec.begin(),vec.end(),謂詞);

測試代碼:

bool bigThan5(const string & s) {return s.size() >= 5;
}
void test1013() {vector<string>vec{"hellol","ha","hou","hello1w", "hi","hellob", "hellocd", };auto it=partition(vec.begin(),vec.end(),bigThan5);auto beg = vec.begin();while (beg!=it) {cout << *beg << " ";beg++;}cout << endl;cout << "vec:";for (auto a : vec) {cout << a << " ";}cout << endl;
}

輸出結果:

hellol hellocd hellob hello1w
vec:hellol hellocd hellob hello1w hi hou ha

lambda表達式

lambda介紹

一個lambda表達式表示一個可調用的代碼單元,我們可將其理解為一個未命名的內聯函數。與任何函數類似,一個lambda具有一個返回類型、一個參數列表和一個函數體。但與函數不同,lambda可能定義在函數內部,一個lambda表達式具有如下形式:
[捕獲列表](參數列表)->返回類型{函數體}

其中,捕獲列表是一個lambda所在函數中定義的局部變量的列表(通常為空)。
我們可以忽略參數列表和返回類型,但必須永遠包含捕獲列表和函數體。
示例中定義了一個可調用對象f,它不接受參數,返回42

auto f = [] {return 42;};

lambda的調用方式與普通函數的調用方式相同,都是使用調用運算符:

cout<<f()<<endl;//打印42

如果忽略返回類型,lambda根據函數體中的代碼推斷出返回類型。如果函數體只是一個return語句,則返回類型從返回的表達式的類型推斷而來。如果lambda 的函數體包含任何單一return語句之外的內容,且未指定返回類型,則返回void。

算法find_if

find_if算法對輸入序列中的每個元素調用給定的謂詞,返回第一個使謂詞返回非0值的元素,如果不存在這樣的元素,則返回尾后迭代器。
find_if接受一元謂詞——我們傳遞給它的任何函數都必須嚴格接受一個參數,以便能用來自輸入序列中的一個元素調用它。
如果我們想使用find_if算法來查找第一個具有特定大小的元素。則我們需要接受一個string和一個長度。因此,此處可以使用上述介紹的lambda表達式。

auto wc = find_if(words.begin(),words.end(),[sz](const string &a){return a.size()>=sz;});

這里對find_if的調用返回一個迭代器,指向第一個長度不小于給定參數sz的元素,如果這樣的元素不存在,則返回words.end()的一個拷貝。

算法for_each

對輸入序列中的元素進行打印

for_each(vec.begin(),vec.end(),[](const string &s){cout<<s<<" ";});

此處lambda 的捕獲列表為空,但卻使用了cout,是因為一個lambda可以直接使用定義在當前函數之外的名字。
捕獲列表只用于局部非static變量,lambda可以直接使用局部static變量和在它所在函數之外聲明的名字。

lambda捕獲和返回

類似參數傳遞,變量的捕獲方式也可以是值或引用。

值捕獲

與傳值參數類似,采用值捕獲的前提是變量可以拷貝。與參數不同,被捕獲的變量的值是在lambda創建時拷貝,而不是調用時拷貝:

int a=10;
auto f=[a]{return a;};
a=5;
auto j=f();//j=10

引用捕獲

int a=10;
auto f=[&a]{return a;};
a=5;
auto j=f();//j=5

當以引用方式捕獲一個變量時,必須保證在lambda執行時變量是存在的。
我們可以從一個函數返回lambda。函數可以直接返回一個可調用對象,或者返回一個類對象,該類含有可調用對象的數據成員。如果函數返回一個lambda,則與函數不能返回一個局部變量的引用類似,此lambda也不能包含引用捕獲。

隱式捕獲

sz為隱式捕獲,值捕獲方式

auto wc = find_if(words.begin(),words.end(),[=](const string &a){return a.size()>=sz;});

os為隱式捕獲,引用捕獲方式

for_each(vec.begin(),vec.end(),[&](const string &s){os<<s<<" ";});

混合使用隱式捕獲和顯式捕獲
當混合使用隱式捕獲和顯式捕獲時,顯式捕獲的變量必須使用與隱式捕獲不同的方式。即,如果隱式捕獲是引用方式(使用了&),則顯式捕獲命名變量必須采用值方式,因此不能在其名字前使用&。類似的,如果隱式捕獲是值方式(采用了=),則顯式捕獲命名變量必須采用引用方式,即在名字前使用& 。
示例:c隱式捕獲,值捕獲方式,os顯式捕獲,引用捕獲方式

for_each(vec.begin(),vec.end(),[=,&os](const string &s){os<<s<<c;});

在這里插入圖片描述

可變lambda

默認情況下,對于一個值被拷貝的變量,lambda不會改變其值,如果我們希望能改變一個被捕獲的變量的值,就必須在參數列表后加上關鍵字mutable。因此,可變lambda能省略參數列表。
若不加關鍵字mutable,在下述代碼中, ++a會報錯。

	int a = 10;auto f = [a]()mutable {return ++a; };auto j = f();cout << "a:" << a << endl;  //a=10cout << "j:" << j << endl;  //j=11

示例:

void test() {int num = 5;auto f = [num]()mutable->bool {if (num == 0)return true; num--; cout <<" f()的num:"<< num <<" "; return false; };for (int i = 0; i < 6;i++) {cout << "test中的num:"<<num<<" ";cout << f() << endl;}}

輸出結果:

test中的num:5  f()的num:4 0
test中的num:5  f()的num:3 0
test中的num:5  f()的num:2 0
test中的num:5  f()的num:1 0
test中的num:5  f()的num:0 0
test中的num:5 1

指定lambda返回類型

當我們需要為一個lambda定義返回類型時,必須使用尾置返回類型。
示例代碼:

transform(vi.begin(),vi.end(),vi.begin(),
[](int i)->int
{if(i<0)return -i;
else return i;});

算法count_if

接受一對迭代器表示一個輸入范圍,還接受一個謂詞,對輸入范圍中每個元素執行,count_if返回一個計數值,表示謂詞有多少次為真。

參數綁定

如果lambda的捕獲列表為空,通常可以用函數來代替它。

標準庫bind函數

bind函數定義在頭文件functional中。bind函數可看做一個通用的函數適配器,它接受一個可調用對象,生成一個新的可調用對象來“適應”原對象的參數列表。
調用bind 的一般形式:

auto newCallable=bind(callable,arg_list)

其中,newCallable本身是一個可調用對象,arg_list是一個逗號分隔的參數列表,對應給定的callable的參數,即,當我們調用newCallable時,newCallable會調用callable,并傳遞給它arg_list中的參數。
arg_list中的參數可能包含形如_n的名字,其中n是一個整數。這些參數是“占位符”,表示newCallable的參數,它們占據了傳遞給newCallable的參數的“位置”。數值n表示生成的可調用對象中參數的位置:_1為newCallable的第一個參數,_2為newCallable的第二個參數,以此類推。
示例代碼:

auto check6=bind(check_size,_1,6);
string s="hello";
check6(s);//會調用 check_size(s,6);

將原來基于lambda 的find_if版本:

auto wc = find_if(words.begin(),words.end(),[sz](const string &a){return a.size()>=sz;});

替換為如下bind版本:

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

當find_if對words中的string調用這個對象時,這些對象會調用check_size,將給定的string和sz傳遞給它。因此,find_if可以有效地對輸入序列中每個string調用check_size,實現string的大小與sz的比較。

placeholders名字

名字_n都定義在一個名為placeholders的命名空間中,而這個命名空間本身定義在std命名空間中,例如,_1對應的using聲明為:

using std::placeholders::_1
由placeholders定義的所有名字都可用的聲明:
using namespace std::placeholders
與bind函數一樣,placeholders命名空間也定義在functional頭文件中。

bind的參數

auto g=bind(f,a,b,_2,c,_1);

調用g(x,y)會調用f(a,b,y,c,x)

bind重排參數順序

按單詞長度由短至長排序
sort(words.begin(),words.end(),isShorter);

按單詞長度由長至短排序
sort(words.begin(),words.end(),bind(isShorter,_2,_1));
在第一個調用中,當sort需要比較兩個元素A和B時,它會調用isShorter(A,B)。在第二個對sort的調用中,傳遞給isShorter的參數被交換,因此比較兩個元素時,就好像調用isShorter(B,A)一樣。

綁定引用參數

默認情況下,bind的那些不是占位符的參數被拷貝到bind返回的可調用對象中,但是有時對有些綁定的參數我們希望以引用方式傳遞,或是要綁定參數的類型無法拷貝,我們就必須使用標準庫ref函數
錯誤用法:for_each(words.begin(),words.end(),bind(print,os,_1,' '));
正確用法:for_each(words.begin(),words.end(),bind(print,ref(os),_1,' '));

函數ref返回一個對象,包含給定的引用,此對象是可以拷貝的,標準庫中還有一個cref函數,生成一個保存const引用的類。與bind一樣,函數ref和cref也定義在頭文件functional中。

再探迭代器

  • 插入迭代器:這些迭代器被綁定到一個容器上,可用來向容器插入元素。
  • 流迭代器:這些迭代器被綁定到輸入或輸出流上,可用來遍歷所關聯的IO流。
  • 反向迭代器:這些迭代器向后而不是向前移動。除了forward_list之外的標準庫容器都有反向迭代器。
  • 移動迭代器:這些專用的迭代器不是拷貝其中的元素,而是移動它們。此迭代器第13章進行介紹。

插入迭代器

插入器是一種迭代器適配器,它接受一個容器,生成一個迭代器,能實現向給定容器添加元素。當我們通過一個插入迭代器進行賦值時,該迭代器調用容器操作來向給定容器的指定位置插入一個元素。
在這里插入圖片描述

back_insert示例代碼:

	list<int>lst = { 1,2,3,4 };auto it = back_inserter(lst);for (int i = 5; i < 10;i++) {*it = i;}for (auto a:lst) {cout << a << " ";}

輸出結果:

1 2 3 4 5 6 7 8 9

調用push_back

front_inserter示例代碼

	list<int>lst = { 1,2,3,4 };auto it = front_inserter(lst);for (int i = 5; i < 10;i++) {*it = i;}for (auto a:lst) {cout << a << " ";}

輸出結果:

9 8 7 6 5 1 2 3 4

調用push_front

inserter示例代碼

	list<int>lst = { 1,2,3,4 };auto it = inserter(lst,lst.begin());for (int i = 5; i < 10;i++) {*it = i;}for (auto a:lst) {cout << a << " ";}

輸出結果

5 6 7 8 9 1 2 3 4

示例代碼中,
*it = i;相當于 it=lst.insert(it,i);++it;
即一直是在原來元素的位置之前加入新的元素。

iostream迭代器

雖然iostream類型不是容器,但標準庫定義了可以用于這些IO類型對象的迭代器。istream_iterator讀取輸入流,ostream_iterator向一個輸出流寫數據。這些迭代器將它們對應的流當做一個特定類型的元素序列來處理。通過使用流迭代器,我們可以用泛型算法從流對象讀取數據以及向其寫入數據。

istream_iterator操作

當創建一個流迭代器時,必須指定迭代器將要讀寫的對象類型。一個istream_iterator使用>>來讀取流。因此,istream_iterator要讀取的類型必須定義了輸入運算符。
當創建一個istream_iterator時,我們可以將它綁定到一個流:
istream_iterator<int>int_it(cin); //從cin讀取int

我們還可以默認初始化迭代器,這樣就創建了一個可以當作尾后值使用的迭代器:
istream_iterator<int>int_eof; //尾后迭代器

下面是一個用istream_iterator從標準輸入讀取數據,存入一個vector的例子:

istream_iterator<int>in_iter(cin);//從cin讀取int
istream_iterator<int>int_eof;  //尾后迭代器
while(in_iter!=int_eof)vec.push_back(*in_iter++);

該程序可重寫為如下形式:

istream_iterator<int>in_iter(cin);//從cin讀取int
istream_iterator<int>int_eof;  //尾后迭代器
vector<int>vec(in_iter,int_eof);//從迭代器范圍構造vec

我們可以用一對表示元素范圍的迭代器來構造vec。這兩個迭代器是istream_iterator,這意味著元素范圍是通過從關聯的流中讀取數據獲得的。這個構造函數從cin中讀取數據,直至遇到文件尾或者遇到一個不是int的數據為止。從流中讀取的數據被用來構造vec。
在這里插入圖片描述

使用算法操作流迭代器

由于算法使用迭代器操作來處理數據,而流迭代器又至少支持某些迭代器操作,因此我們至少可以用某些算法來操作流迭代器。
示例,我們可以用一對istream_iterator來調用accumulate:

istream_iterator<int>in(cin), eof;
cout << accumulate(in, eof, 0) << endl;

若輸入為1 3 5 7 9
則輸出為25,此調用會計算出從標準輸入讀取的值的和。

ostream_iterator操作

我們可以對任何具有輸出運算符(<<運算符)的類型定義ostream_iterator。當創建一個ostream_iterator時,我們可以提供(可選的)第二參數,它是一個字符串,在輸出每個元素后都會打印此字符串。此字符串必須是一個C風格字符串(即,一個字符串常量或者一個指向以空字符結尾的字符數組的指針)。必須將ostream_iterator綁定到一個指定的流,不允許空的或表示尾后位置的ostream_iterator。
在這里插入圖片描述
示例代碼:

	ostream_iterator<int>out_iter(cout," ");vector<int>vec{1,3,5,7,9};for (auto a:vec) {*out_iter++ = a;}cout << endl;

輸出:

1 3 5 7 9

此處* 和++實際上對ostream_iterator對象不做任何事情,可以寫作out_iter = a;但仍然推薦*out_iter++ = a;這種寫法, 因為這種寫法,流迭代器的使用與其他迭代器的使用保持一致。
可以通過調用copy來打印vec中的元素,這比編寫循環更簡單:

ostream_iterator<int>out_iter(cout," ");
vector<int>vec{1,3,5,7,9};
copy(vec.begin(),vec.end(),out_iter);
cout<<endl;

輸出:

1 3 5 7 9

使用流迭代器處理類類型

我們可以為任何定義了輸入運算符>>的類型創建istream_iterator對象,類似的,只要類型有輸出運算符<<,我們就可以為其定義ostream_iterator。

使用流迭代器讀取一個文本文件,存入一個vector中的string里

	ifstream in("test.txt");istream_iterator<string>in_iter(in),eof;ostream_iterator<string>out_iter(cout," ");vector<string>vec(in_iter,eof);copy(vec.begin(), vec.end(), out_iter);cout << endl;

一個輸入文件,兩個輸出文件,讀取輸入文件,將奇數寫入輸出文件一,將偶數寫入輸出文件二

void test1033(string &s1, string &s2, string &s3) {ifstream in(s1);ofstream ofs1(s2);ofstream ofs2(s3);istream_iterator<int>in_iter(in), eof;ostream_iterator<int>out1(ofs1, " ");ostream_iterator<int>out2(ofs2, " ");while (in_iter!=eof) {if (*in_iter % 2 == 1) {*out1++ = *in_iter;}else {*out2++ = *in_iter;}in_iter++;}
}

反向迭代器

反向迭代器就是在容器中從尾元素向首元素反向移動的迭代器。對于反向迭代器,遞增(以及遞減)操作的含義會顛倒過來。遞增一個反向迭代器會移動到前一個元素,遞減會移動到下一個元素。除了forward_list之外,其他容器都支持反向迭代器。通過調用rbegin、rend、crbegin、crend成員函數來獲得反向迭代器。
我們只能從既支持++也支持–的迭代器來定義反向迭代器,因為反向迭代器的目的是在序列中反向移動。
在這里插入圖片描述
示例代碼:

	vector<int>vec = {1,3,5,7,2,4,6,8};for (auto it = vec.crbegin(); it != vec.crend();++it) {cout << *it << " ";}

輸出結果:

8 6 4 2 7 5 3 1

通過向sort傳遞一對反向迭代器來將vector整理為遞減序:

	vector<int>vec = {1,3,5,7,2,4,6,8};sort(vec.begin(),vec.end());for (auto a : vec) {cout << a << " ";}cout << endl;sort(vec.rbegin(), vec.rend());for (auto a : vec) {cout << a << " ";}

輸出結果:

1 2 3 4 5 6 7 8
8 7 6 5 4 3 2 1

反向迭代器轉換為普通迭代器

調用base成員函數即可
在這里插入圖片描述
反向迭代器的目的是表示元素范圍,而這些范圍是不對稱的,這導致一個重要的結果:當我們從一個普通迭代器初始化一個反向迭代器,或是給一個反向迭代器賦值時,結果迭代器與原來迭代器指向的并不是相同的元素。示例代碼如下:

	string s = "hello,world,last";auto it = s.rbegin();it++;  //此時it指向last中的s*it = 'A';cout << s << endl;//輸出結果為hello,world,laAt
	string s = "hello,world,last";auto it = s.rbegin();it++;auto it2 = it.base();//此時it2指向last中的t*it2 = 'A';cout << s << endl;//輸出結果為hello,world,lasA

例如,查找某以逗號分隔的string中的最后一個單詞的示例代碼如下:

	string s = "hello,world,last";auto it1 = find(s.begin(),s.end(),',');cout << "第一個單詞:"<<string(s.begin(),it1) <<endl;auto it2 = find(s.rbegin(), s.rend(), ',');cout << "最后一個單詞錯誤的方式:" << string(s.rbegin(), it2) << endl;cout << "最后一個單詞正確的方式:" << string(it2.base(),s.end()) << endl;

輸出結果:

第一個單詞:hello
最后一個單詞錯誤的方式:tsal
最后一個單詞正確的方式:last

泛型算法結構

5類迭代器

在這里插入圖片描述
在這里插入圖片描述
在這里插入圖片描述

算法形參模式

接受單個目標迭代器的算法

dest參數是一個表示算法可以寫入的目的位置的迭代器,算法假定:按其需要寫入數據,不管寫入多少個元素都是安全的。
如果dest是一個直接指向容器的迭代器,那么算法將輸出數據寫到容器中已存在的元素內,更常見的情況,dest被綁定到一個插入迭代器或是一個ostream_iterator。

接受第二個輸入序列的算法

在這里插入圖片描述

算法命名規范

  • 一些算法使用重載形式傳遞一個謂詞
  • _if版本的算法可接受謂詞參數
  • 區分拷貝元素的版本和不拷貝的版本
    • 默認情況下,重排元素的算法將重排后的元素寫回給定的輸入序列中。這些算法還提供另一個版本,將元素寫到一個指定的輸出目的位置。寫到額外目的空間的算法都在名字后面附加一個_copy。
    • 一些算法同時提供_copy和_if版本,這些版本接受一個目的位置迭代器和一個謂詞。

示例代碼如下:

reverse(beg,end) 	反轉輸入范圍中元素的順序
reverse_copy(beg,end,dest)	將元素按逆序拷貝到dest
remove_if(vec.begin(),vec.end(),[](int i){return i%2;}) 	
reverse_copy_if(vec.begin(),vec.end(),back_inserter(vec2),[](int i){return i%2;})	

特定容器算法

list和forward_list成員函數版本的算法

在這里插入圖片描述
在這里插入圖片描述

merge()示例代碼

lst和lst2必須是有序的,經merge后,lst2為空,lst為1 2 3 4 5 6 7 8 9 10

	list<int>lst{1,3,5,7,10};list<int>lst2{ 2,4,6,8,9 };lst.merge(lst2);

unique()示例代碼

list中重復元素相鄰,調用unique()即可刪除同一個值的連續拷貝,如下代碼,調用unique()后lst為1 2 3 4

	list<int>lst{1,2,2,3,3,3,4};lst.unique();

splice成員

在這里插入圖片描述

list示例代碼:

	list<int>lst{1,3,5,7,10 };list<int>lst2{ 2,4,6,8,9 };lst.splice(lst.begin(),lst2);//lst=2 4 6 8 9 1 3 5 7 10
	list<int>lst{1,3,5,7,10 };list<int>lst2{ 2,4,6,8,9 };lst.splice(lst.begin(),lst2,lst2.begin());//lst=2 1 3 5 7 10
	list<int>lst{1,3,5,7,10 };list<int>lst2{ 2,4,6,8,9 };lst.splice(lst.begin(),lst2,lst2.begin(), lst2.end());//lst=2 4 6 8 9 1 3 5 7 10

forward_list示例代碼:

	forward_list<int>flst{1,3,5,7,10 };forward_list<int>flst2{ 2,4,6,8,9 };flst.splice_after(flst.begin(),flst2);//flst=1 2 4 6 8 9 3 5 7 10

以下代碼,it指向元素6,將6之后的元素8移動到flst中:

	forward_list<int>flst{1,3,5,7,10 };forward_list<int>flst2{ 2,4,6,8,9 };auto it=flst2.begin();it++;it++;flst.splice_after(flst.begin(),flst2, it);//flst: 1 8 3 5 7 10//flst2: 2 4 6 9 
	forward_list<int>flst{1,3,5,7,10 };forward_list<int>flst2{ 2,4,6,8,9 };flst.splice_after(flst.begin(),flst2, flst2.begin(), flst2.end());//flst: 1 4 6 8 9 3 5 7 10  flst2: 2

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/446557.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/446557.shtml
英文地址,請注明出處:http://en.pswp.cn/news/446557.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C語言常用字符串函數

概括 代碼 #include<stdlib.h> #include<stdio.h> #include<string.h> int main() {//常用字符串函數char a[]"abcSDFbnm";char b[]"SD";printf("a的字符串長度:%d\n",strlen(a));printf("b的字符串長度:%d\n",str…

C++ primer 第11章 關聯容器

文章目錄使用關聯容器map示例關聯容器概述定義關聯容器關聯容器值初始化multimap和multiset關鍵字類型的要求pair類型pair上的操作關聯容器操作關聯容器額外的類型別名關聯容器迭代器map迭代器set迭代器關聯容器和算法添加元素向map添加元素檢測insert的返回值使用insert代替下…

C++ primer 第12章 動態內存

文章目錄前言動態內存與智能指針shared_ptr類shared_ptr和unique_ptr都支持的操作shared_ptr獨有的操作make_shared 函數shared_ptr的拷貝和賦值shared_ptr自動銷毀所管理的對象shared_ptr還會自動釋放相關聯的內存程序使用動態內存出于以下原因直接管理內存使用new動態分配和初…

C語言順序查找二分查找

介紹 順序查找 按照順序一個個查找 #include<stdio.h> //順序查找 int search(int arr[],int len,int aim) {int i;for(i0;i<len;i){if(arr[i]aim){return i;//返回下標 }}return -1;//表示未查詢到} int main() {int arr[]{13,355,256,65,234,-1,35,-6,-3,-4,0};…

C++ primer 第12章 12.3 使用標準庫:文本查詢程序

文章目錄使用標準庫&#xff1a;文本查詢程序文本查詢程序設計數據結構在類之間共享數據自己的文本查詢程序書中的文本查詢程序使用標準庫&#xff1a;文本查詢程序 我們將實現一個簡單的文本查詢程序&#xff0c;作為標準庫相關內容學習的總結。 我們的程序允許用戶在一個給…

C語言二維數組 int arr[2][3]

基礎使用 先遍歷行再遍歷列 #include<stdio.h> //二維數組的基本使用 int main() {//二維數組的初始化int arr1[2][2]{{2,2},{0,0}};int arr2[2][3]{2,2,2,8,8,8};int arr3[6][9];int i,j;for(i0;i<6;i){for(j0;j<9;j){arr3[i][j]1;}}arr3[2][5]0;//打印printf(&…

牛客網C++面經 類和數據抽象

請你來說一下C中struct和class的區別 在C中&#xff0c;可以用struct和class定義類&#xff0c;都可以繼承。區別在于&#xff1a;structural的默認繼承權限和默認訪問權限是public&#xff0c;而class的默認繼承權限和默認訪問權限是private。另外&#xff0c;class還可以定義…

C++ primer 第13章 拷貝控制

文章目錄前言拷貝、賦值與銷毀拷貝構造函數合成拷貝構造函數拷貝初始化和直接初始化拷貝初始化的發生&#xff1a;參數和返回值拷貝初始化的限制拷貝賦值運算符重載賦值運算符合成拷貝賦值運算符析構函數析構函數完成的工作什么時候會調用析構函數合成析構函數代碼片段調用幾次…

C語言 指針自增自減加減運算 p++ p+i

介紹 自增自減代碼 #include<stdio.h> #include<string.h> //指針自增--short void increase(short *arr,int len) {int i;arr&arr[0];for(i0;i<len;i){printf("arr[%d]%d,address%p\n",i,*arr,arr);arr;} }//指針自減--char void decrease(char…

C++ 編譯與底層

原文鏈接 編譯與底層請你來說一下一個C源文件從文本到可執行文件經歷的過程&#xff1f; 對于C源文件&#xff0c;從文本到可執行文件一般需要四個過程&#xff1a;預處理階段&#xff1a;對源代碼文件中文件包含關系&#xff08;頭文件&#xff09;、預編譯語句&#xff08;…

C語言 指針數組-字符指針數組整型指針數組 char*s[3] int*a[5] 數組指針int(*p)[4]

基本介紹 1.指針數組:由n個指向整型元素的指針而組成,里面存放指針 Int *ptr[3]; 2.地址: ptr[i]:元素地址 &ptr[i]:指針地址 圖示 代碼: 內存布局: 代碼 #include<stdio.h> #include<string.h> //指針數組--int void pointer(int *arr,int len) {int …

uninitialized_copy測試代碼示例

原測試代碼如下&#xff1a; int main() {vector<int>v1{1,3,5,7,9,2,4,6,8};allocator<int>alloc;auto data alloc.allocate(9);uninitialized_copy(v1.begin(),v1.end(), data);auto end data 9;while(data!end) {cout << *data <<" "…

C語言的地址 內存

取地址在CPU的寄存器產生&#xff0c;不占據內存地址由計算器總線&#xff0c;地址作為常量不消耗內存指針 存儲不同的地址&#xff0c;間接賦值空類型指針 void* 類型指針 不可以取數據 或者修改數據 需要進行強制類型轉換int num 10;void *p &num;std::cout << …

C語言 多重指針--整型字符字符串 int**pp

介紹 多重指針:一個指針指向另一個指針 離值越近的指針級別越大:一級 內存布局 代碼 圖示: 多重指針–整型 #include<stdio.h> #include<string.h> //多重指針--整型//二級指針 void two() {printf("二級指針:\n");int a896;int *p&a,**pp&…

C++ primer 第13章 拷貝控制

文章目錄前言拷貝、賦值與銷毀拷貝構造函數合成拷貝構造函數拷貝初始化和直接初始化拷貝初始化的發生&#xff1a;參數和返回值拷貝初始化的限制拷貝賦值運算符重載賦值運算符合成拷貝賦值運算符析構函數析構函數完成的工作什么時候會調用析構函數合成析構函數代碼片段調用幾次…

牛客網C++面經 C++11

請問C11有哪些新特性&#xff1f; auto關鍵字&#xff1a;編譯器可以根據初始值自動推導出類型。但是不能用于函數傳參以及數組類型的推導nullptr關鍵字&#xff1a;nullptr是一種特殊類型的字面值&#xff0c;它可以被轉換成任意其它的指針類型&#xff1b;而NULL一般被宏定義…

C語言 返回指針的函數--指針函數 int* max(int a)

定義 strlong示例代碼 代碼1: #include<stdio.h> #include<string.h> //返回指針的函數//比較兩個字符串,返回更長的字符串 char *strlong(char* a,char* b) {char *p1&a[0];char *p2&b[0];while(true){if(*p1\0){return b;}else if(*p2\0){return a;}p1…

第2、3講 圖像的存儲格式

本圖像處理系列筆記是基于B站楊淑瑩老師的課程進行學習整理的。 文章目錄黑白圖像8位灰度索引圖像8位偽彩色索引圖像24位真彩色圖像圖像文件格式BMP文件存儲格式BMP文件頭位圖信息頭顏色表位圖信息——BITMAPINFO結構BMP位圖文件匯總按照顏色深度分類&#xff0c;常用圖像文件&…

Ubuntu18.04.4 環境下對屬性加密算法CP-ABE環境搭建

注意事項 cpabe依賴pbc&#xff0c;pbc依賴gmp&#xff0c;gmp依賴M4、bison、flex如果權限不夠 &#xff0c;命令的前面加上sudo &#xff0c;不要直接使用root用戶進行操作&#xff0c;其帶來的隱患有很多 第一步 配置簡單的環境 簡單環境 包括gcc、g、make、cmake、openss…

C語言 函數指針 int(*ptr)(int,int)

基本介紹 函數指針:指向函數的指針 與數組類似 定義 Int(*pmax)(int ,int)max; Int(*pmax)(int x,int y)max;//形參名稱不重要 函數返回類型(*指針)(形參類型)函數名稱; 具體案例 代碼: *pmax取到函數本身 調用函數指針方式: (*pmax)(x,y); pmax(x,y);//與java中調用函數一…