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.mzph.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++ 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 …

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中调用函数一…

C++ primer 第14章 操作重载与类型转换

文章目录基本概念直接调用一个重载的运算符函数某些运算符不应该被重载使用与内置类型一致的含义选择作为成员或者非成员输入和输出运算符重载输出运算符<<输出运算符尽量减少格式化操作输入输出运算符必须是非成员函数重载输入运算符>>算术和关系运算符相等运算符…

C语言 回调函数 produce(arr,len,getRand)

基本介绍 回调函数:形参中包含另一个函数的函数指针 用函数指针接收另一个函数 案例 代码解析 具体代码 #include<stdio.h> #include<stdlib.h> //回调函数--//函数原型 int getRand(); int *produce(int*arr,int len,int(*get)()); int main() {int arr[10…

C语言 动态内存分配机制(堆区) int*p=malloc(5*sizeof(4))

C程序内存分配图 栈区:局部变量 堆区:动态分配的数据 静态存储区/全局区:全局变量,静态数据 代码区:代码,指令 内存分配说明 内存动态分配的相关函数 堆区: #inlcude<stdlib.h> Malloc(size);//分配长度为size个字节的连续空间 Calloc(n,size);//分配size个长度为n…