《C++ Primer》第十章 泛型算法

《C++ Primer》第十章 泛型算法

10.1 概述

大多数算法定义在头文件algorithm中,还有一些算法在numeric中。例如标准库算法find:

int val = 42;//即将查找的值
//如果在vec中找到想要的元素,则返回结果指向它,否则返回vec.cend()
auto result = find(vec.cbegin(), vec.end(), val);

string val = "a value";//我们要查找的值
//此调用在list中查找string元素
auto result=find(lst.cbegin(),lse.cend(), val);

//由于指针就像内置数组上的迭代器一样,因此可以用find在数组中查找值
int ia[] = {27, 210, 12 47, 109, 83};
int val = 83;
int* result = find(begin(ia), end(ia), val);
//在ia[1]、ia[2]和ia[3]中查找给定元素
auto result = find(ia+1,ia+4,val);

10.2 初识泛型算法

只读算法:find()、count()、accumulate()

accumulate(): 定义在numeric中,接受三个参数,前两个指出了需要求和的元素的范围,第三个参数是和的初值。例如:

//对vec中的元素求和,和的初值是0
int sum = accumulate(vec.cbegin(), vec.cend(), 0);

//通过调用accumulate将vector中所有string元素连接起来
string num = accumulate(v.cbegin(), v.cend(), string(""));
//将空串当做一个字符串字面值传递给第三个参数是不可以的,会导致一个编译错误
//错误:const char* 上没有定义+运算符
string sum = accumulate(v.cbegin(), v.cend(), "");

只读算法equal(): 用于确定两个序列是否保存相同的值。它将第一个序列中的每个元素和第二个序列中的对应元素进行比较。如果所有对应元素都相等,则返回true, 否则返回false。此算法接受三个迭代器:前两个表示第一个序列中的元素范围,第三个表示第二个序列的首元素。

//roster2中的元素数目应该至少与roster1一样多
equal(roster1.cbegin(), roster1.cend(), roster2.cbegin());

那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。

写容器元素的算法:fill接受一对迭代器表示一个范围,还接受一个值作为第三个参数。fill将给定的这个值赋予输入序列中的每个元素。

fill(vec.begin(), vec.end(), 0);//将每个元素重置为0
//将容器中的一个子序列设置为10
fill(vec.begin(), vec.begin() + vec.size()/2, 10);

算法不检查写操作:

fill_n接受一个单迭代器、一个计数器和一个值。它将给定值赋予迭代器指向的元素开始的指定个元素。

vector<int> vec;
//使用vec, 赋予不同值
fill_n(vec.begin(), vec.size(), 0);//将所有元素重置为0
fill_n(dest, n ,val)
fill_n假定dest指向一个元素,而从dest开始的序列至少包含n个元素

vector<int> vec;//容量
//灾难:修改vec中的10个(不存在)的元素
fill_n(vec.begin(), 10, 0);

back_inserter:接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。

vector<int> vec;//空向量
auto it = back_inserter(vec);//通过它赋值会将元素添加到vec中
*it = 42;//vec现在有一个元素,值为42
//使用back_inserter来创建一个迭代器,作为算法的目的位置来使用
//正确:back_inserter创建一个插入迭代器,可以用来向vec添加元素
fill_n(back_inserter(vec), 10 , 0);

拷贝算法:copy可实现内置数组的拷贝

int a1[] = {0,1,2,3,4,5,6,7,8,9};
int a2[sizeof(a1)/sizeof(*a1)];//a2和a1大小一样
//ret指向拷贝到a2的尾元素之后的位置
auto ret = copy(begin(a1), end(a1), a2);//把a1的内容拷贝给a2

算法replace:读入一个序列,并将其中所有给定值的元素都改为另一个值。接受4个参数:前两个是迭代器,表示输入序列,后两个一个是要搜索的值,另一个是新值。它将所有等于第一个值的元素替换为第二个值。

//将所有值为0的元素改为42
replace(ilist.begin(), ilist.end(), 0, 42);
//保持原序列不变,使用back_inserter按需要增长目标序列
replace_copy(ilst.cbegin(), ilist.cend(),
			back_inserter(ivec), 0, 42);

重排容器元素的算法:

消除重复单词:

void elimDups(vector<string> &words)
{
	//按字典序排序words,以便查找重复单词
	sort(words.begin(), words.end());
	//unique重排输入范围,使得每个单词只出现一次
	//排列在范围的前部, 返回指向不重复区域之后一个位置的迭代器
	auto end_unique = unique(words.begin(), words.end());
	//使用向量操作erase删除重复单词
	words.erase(end_unique, words.end());
}

10.3定制操作

谓词:返回结果是一个能用作条件的值。分为一元谓词和二元谓词。一元、二元代表着接受几个参数。示例如下:

//比较函数,用来按长度排序单词
bool isShorter(const string &s1, const string &s2)
{
	return s1.size()<s2.size();
}
//按长度由短至长排序words
sort(words.begin(), words.end(), isShorter);

lambda表达式:表示一个可调用的代码单元。可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。表达式形式如下:

[capture list] (parameter list) -> return type { function body }

其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空):return type、parameter list 和 function body 与任何普通函数一样,分别表示返回类型、参数列表和函数体。但是,lambda必须使用尾置返回。

isShorter函数的lambda:

[](const string &a, const string &b){
	return a.size()<b.size();
}
//使用lambda来调用stable_sort
stable_sort(words.begin(), words.end(),
            [](const string &a, const string &b)
            {return a.size() < b.size();});

使用捕获列表:

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

调用find_if

//获取一个迭代器,指向第一个满足size()>=sz的元素
auto wc = find_if(words.begin(),  words.end(),
	[sz](const string &a)
		{ return a.size() >= sz; });]
//计算满足size>=sz的元素的数目
auto count = words.end() - wc;
cout << count <<" "<<make_plural(count, "word", "s")
    << " of length "<< sz << " or longer"<<endl;

for_each算法: 接受一个可调用对象,并对输入序列中每个元素调用此对象。

//打印长度大于等于给定值的单词,每个单词后面接一个空格
for_each(wc, words.end(),
		[](const string &s){cout<<s<<" "});
cout<<endl;

lambda 捕获和返回:lambda的数据成员在lambda对象创建时被初始化

值捕获:类似参数传递,变量的捕获方式也可以是值或引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝:

void fcn1()
{
	size_t v1 = 42;//局部变量
	//将v1拷贝到名为f的可调用对象
	auto f = [v1]{ return v1; };
	v1 = 0;
	auto j = f();//j为42;f保存了我们创建它时的拷贝
}

引用捕获:

void fcn2()
{
	size_t v1 = 42;//局部变量
	//对象f2包含v1的引用
	auto f2 = [&v1]{ return v1; };
	v1 = 0;
	auto j = f2();//j为0;f2保存v1的引用,而非拷贝
}

希望biggies函数接受一个ostream的引用,用来输出数据,并接受一个字符作为分隔符

void biggies(vector<string> &words, vector<string>::size_type sz, ostream &os = cout, 
				char c = ' ')
{
	...
	//打印count的语句改为打印os
	for_each(words.begin(), words.end(),
			[&os, c](const string &s) { os<<s<<c; });
}

隐式捕获:为了指示编译器推断捕获列表,应在捕获列表中写一个&或=。&告诉编译器采用捕获引用方式, =则表示采用值捕获方式。

//sz为隐式捕获,值捕获方式
wc = find_if(words.begin(), words.end(),
			[=](const string &s)
				{ return s.size()>=sz; });

可以混合使用隐式捕获和显示捕获:

void biggies(vector<string> &words,	
				vector<string>:: size_type sz,
				ostream &os = cout, char c=' ')
{
	//os隐式捕获,引用捕获方式;c显示捕获,值捕获方式
	for_each(words.begin(), words.end(),
				[&, c](const string &s) { os<<s<<c; });
	for_each(words.begin(),words.end(),
				[=,&os](const string &s) { os<<s<<c; });
}

在这里插入图片描述

可变lambda:默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。可以在参数列表首加上mutable来改变被捕获的变量值。:

void fcn3()
{
	size_t v1 = 42;
	//f可以改变它所捕获的变量的值
	auto f = [v1] () mutable { return +v1; };
	v1 = 0;
	auto j = f();//j为43
}

一个引用捕获的变量是否可以修改依赖于此引用指向的是一个const 类型还是一个非const类型。

void fcn4()
{
	size_t v1 = 42;
	//v1是一个非const变量的引用
    //可以通过f2的引用来改变它
	auto f2 = [&v1] { return ++v1; };
	v1 = 0;
	auto j = f2();//j为1
}

指定lambda返回类型:当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型

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

参数绑定:在find_if调用中的lambda比较一个string 和一个给定大小。但不能用这个函数作为find_if的一个参数。因为find_if接受一元谓词,因此传递给find_if的可调用对象必须接受单一参数。

bool check_size(const string &s, string::size_type sz)
{
	return s.size()>=sz;
}

标准库bind函数:将bind函数看作一个通用的函数适配器,它接受一个可调用对象。生成一个新的可调用对象来“适应”原对象的参数列表:

auto newCallable = bind(callable, arg_list);

绑定check_size的sz参数:

//check6是一个可调用对象,接受一个string类型的参数
//并用此string和值6来调用check_size
auto check6 = bind(check_size, _1, 6);

string s = "hello";
bool b1 = check6(s);//check6(s)会调用check_size(s,6);

//使用bind,可以将原来基于lambda的find_if调用
auto wc = find_if(words.begin(), words.end(), [sz](const string &a));

//替换为check_size版本
auto wc = find_if(words.begin(), words.end(),bind(check_size, _1, size));

使用placeholders名字:如_1对应的using声明为:

using std:: placeholders::1;

可以另一种不同形式的using语句,希望namespace_name的名字都可以可以直接使用:

using namespace namespace_name;

用bind重排参数顺序:

//按单词长度由短至长排序
sort(words.begin(), words.end(), isShorter);
//按单词长度由长至短排序
sort(words.begin(), words.end(), bind(isShorter, _2, _1));

绑定引用参数:与lambda相似,有时对有些绑定的参数希望以引用方式传递,或是要绑定参数的 类型无法拷贝。例如,为了替换一个引用方式捕获ostream的lambda:

//os是一个局部变量,引用是一个输出流
//c是一个局部变量,类型为char
for_each(words.begin(),words.end(),[&os, c](const string &s){ os<<s<<c; });
//可以很容易编写一个函数,完成相同的工作
ostream &print(ostream &os, const string &s, char c)
{
    return os<<s<<c;
}

但是不能直接用bind来代替对os的捕获:

//错误:不能拷贝os
for_each(words.begin(), words.end(), bind(print,os,_1,' '));

原因在于bind拷贝其参数,而我们不能拷贝一个ostream。如果希望传递给bind一个对象而又不拷贝它,就必须使用标准库ref函数:

for_each(words.begin(),words.end(),bind(print, ref(os), _1,' '));

函数ref返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个cref函数,生成一个保存const引用的类。

10.4 再探迭代器

在这里插入图片描述

在这里插入图片描述

插入迭代器:被绑定到一个容器上,用来向容器插入元素。

在这里插入图片描述

back_inserter: 调用push_back,总是插入到容器尾元素之后

front_inserter:调用push_front,总是插入到容器元素之前

inserter: 调用insert,总是插入到给定位置

list<int> lst = {1,2,3,4};
list<int> lst2,lst3;//空lst
//拷贝完成后,lst2包括4,3,2,1
copy(lst.cbegin(), lst.cend(), front_inserter(lst2));
//拷贝完成后,lst3包括1,2,3,4
copy(lst.cbegin(), lst.cend(),inserter(lst3, lst3.begin()));

流迭代器:被绑定到输入或输出流上,可用来遍历所有关联的IO流

iostream迭代器:istream_iterator读取输入流,ostream_iterator向一个输出流写数据。

在这里插入图片描述
在这里插入图片描述

反向迭代器:向后而不是向前移动。除了forward_list之外的标准库容器都有反向迭代器。

反向迭代器需要递减运算符。

移动迭代器:不是拷贝其中的元素,而是移动它们。

10.5 泛型算法结构

在这里插入图片描述

算法形式参数:

在这里插入图片描述

10.6 特定容器算法

在这里插入图片描述

在这里插入图片描述
splice成员:
在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

1100dp

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值