C++Primer_Chap10_泛型算法_笔记

标准库未给每个容器都定义成员函数来实现诸如查找特定元素、替换和删除一个特定值、重排元素顺序等操作,而是定义了一组泛型算法

  • 称为“算法”,是因为其实现了一些经典算法的公共接口,如排序和搜索
  • 称为“泛型”,是因为他们可以用于不同类型的元素和多种容器类型
#include <algorithm>
#include <numeric>
auto result = find( lst.cbegin(), lst.cend(), val);

int sum = accumulate(vec.cbegin(), vec.cend(), 0);	//正确
string sum = accumulate(vec.cbegin(), vec.cend(), string(""));	//正确
string sum = accumulate(vec.cbegin(), vec.cend(), "");	//错误:const char * 没有定义+运算符

accumulate 定义在numeric头文件中,前两个参数指出求和元素范围,第三个参数是和的初始值 ,序列中的元素类型必须和第三个参数匹配 。第三个参数的类型决定函数中使用哪种加法运算以及返回值的类型

向算法传递函数

  谓词

  谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法使用的谓词分为两类:一元谓词(unary predicate,意味着他们只接收单一参数)和二元谓词(binary predicate,意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换成谓词的参数类型。

  接受一个二元谓词参数的sort版本用这个谓词代替<来比较函数:

 lambda表达式

void elimDups(vector<string> &words)
{
	sort(words.begin(), words.end());
	
	auto  end_unique = unique(words.begin(), words.end());
	
	words.erase( end_unique, words.end() );
}

void biggies(vector<string> &words, vector<string>::size_type sz)
{
	elimDups(words);	//将word按字典序排序,删除重复单词
	//按长度排序,长度相同的单词维持字典序
	stable_sort(sort(words.begin(), words.end(), 
		[](const string &a, const string  &b)
			{return a.size() < b.size();));
	//获取一个指向第一个满足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(wc, words.end(), 
		[](const string &s){cout << s << "";});
	cout << endl;
}

lambda捕获和返回

  当定义一个lambda时,编译器生成一个与lambda对于的新的(未命名)类类型。类似参数传递,变量的捕获也可以是值或引用。

值捕获

void fcn1()
{
	size_t v1 = 42;
	auto f2 = [v1]{return v1};
	v1 = 0;
	auto j = f2(); //j为42
}

引用捕获

void fcn1()
{
	size_t v1 = 42;
	auto f2 = [&v1]{return v1};
	v1 = 0;
	auto j = f2(); //j为0
}

  引用捕获和返回引用有着同样的问题和限制。如果采用引用方式捕获一个变量,就必须确保被引用对象在lambda执行的时候是存在的。

void biggies(vector<string> &words, 
		vector<string>::size_type sz,
		ostream &os = cout, char c = " ")
{
	elimDups(words);
	stable_sort(sort(words.begin(), words.end(), 
		[](const string &a, const string  &b)
			{return a.size() < b.size();));
	auto wc = find_if(words.begin(), words.end(),
			[sz](const string &a)
			{ return a.size() >= sz;});
	auto count = words.end() - wc;
	cout << count <<" " << make_plural(count, "word", "s")
		 << " of length" << sz << "or longer" << endl;
	for_each(wc, words.end(), 
		[&os,c](const string &s){ os << s << c;});
	cout << endl;
}

隐式捕获

  除了显式列出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体重的代码来推断我们要使用那些变量:在捕获列表中写一个&(引用捕获方式)或=(值捕获方式).

void biggies(vector<string> &words, 
		vector<string>::size_type sz,
		ostream &os = cout, char c = " ")
{
	elimDups(words);
	stable_sort(sort(words.begin(), words.end(), 
		[](const string &a, const string  &b)
			{return a.size() < b.size();));
	auto wc = find_if(words.begin(), words.end(),
			[sz](const string &a)
			{ return a.size() >= sz;});
	auto count = words.end() - wc;
	cout << count <<" " << make_plural(count, "word", "s")
		 << " of length" << sz << "or longer" << endl;

	for_each(wc, words.end(), 
		[&, c](const string &s){ os << s << c;});

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

	cout << endl;
}

当混合使用显示捕获和隐式捕获时,捕获列表的第一个元素必须是&或=中的一个,此符号指定默认捕获方式。且非默认捕获方式必须采用与默认捕获方式不同的方式,即隐式捕获为引用方式,则显示捕获命名变量必须采用值方式,因此不能在名字前使用&。反之,如果隐式捕获采用值方式,则显示捕获命名变量必须采用引用方式,即使用&

lambda捕获列表
[] 空捕获列表。lambda不能使用所在函数体重的变量。一个lambda只有捕获变量后才能使用它们
[names] names是一个逗号分隔的名字列表。这些名字都是lambda所在函数的局部变量。默认情况下,捕获列表中的变量都被拷贝。可以采用&修改为引用捕获方式
[&] 隐式捕获列表,采用引用捕获方法。lambda体中所使用的而来自所在函数的实体都采用引用方式使用
[=] 隐式捕获列表,采用值捕获方法。lambda体中所使用的而来自所在函数的实体的值
[&, identifier_list] identifier_list是一个逗号分隔的名字列表,包含0个或多个来自所在函数的变量,均采用值捕获,任何隐式捕获的变量都采用引用方式捕获。identifier_list中的名字前不能使用&
[=, identifier_list] identifier_list是一个逗号分隔的名字列表,包含0个或多个来自所在函数的变量,均采用引用捕获,任何隐式捕获的变量都采用值方式捕获。identifier_list中的名字不能包括this,且这些名字必须使用&

可变lambda

  默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果希望改变其值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表

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

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

void fcn1()
{
	size_t v1 = 42;
	auto f2 = [&v1]{return ++v1};
	v1 = 0;
	auto j = f2(); //j为1
}

指定lambda返回类型

  默认情况,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。

//正确
transform(vi.begin(), vi.end(), vi.begin(),
		[](int i){return i< 0? -i: i;});

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

当必须为一个lambda定义返回类型时,必须使用尾置返回类型

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

参数绑定

  对于需要在很多地方使用相同操作的情况,通常应该定义一个函数。对于只接收一个一元谓词的函数,可以使用bind函数来解决:

标准库bind函数

  bind函数可以看做一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象来“适应”原对象的参数列表,调用的一般形式为:

#include <functional>

auto newCallable = bind(callable, arg_list);

其中,newCallable本身是一个可调用对象,arg_list是一个逗号分隔的参数列表,对于给定callable的参数。即,当我们调用newCallable时,newCallable会调用callable,并传递给它arg_list中的参数。

  arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”,表示newCallable的参数,他们占据了传递给newCallable的参数的“位置”。数值n表示生成的可调用对象中参数的位置:_1是newCallable的第一个参数。

#include <functional>

using std::placeholders::_1;

auto check6 = bind(check_size, _1, 6);
string s = "hello";

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

auto wc = find_if(words.begin(), words.end(), 
            [sz](const string &a));
auto wc = find_if(words.begin(), words.end(), 
            bind(check_size, _1, sz));

使用placeholders名字

  名字_n都定义在一个名叫placeholders的命名空间中,而这个命名空间本身定义在std命名空间中。为了使用这些名字,两个命名空间都要写上:

using std::placeholders::_1;

对于每个占位符都需要提供一个单独的using申明太烦人,可以使用如下形式的using语句表明希望使用所有来自namespace_name的名字都可以在我们的程序中直接使用:

using namespace namesplace_name;

using namespace std::placeholders;

bind的参数

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

g(_1, _2)
f(a, b, _2, c, _1)

用bind重排参数序列

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

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

绑定引用参数

  默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中。但对于不能使用拷贝方式的需绑定参数的类型会出错(如ostream、istream)。如果希望传递的是一个对象而又不拷贝它,就必须使用标准库ref函数。

#include <functional>
ostream &print( ostream &os, const string &s, char c)
{
	return os << s <<c;
}

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

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

头文件iterator中的额外的几种迭代器
插入迭代器(insert iterator) 被绑定到一个容器上后,可用于向容器插入元素
流迭代器(stream iterator) 被绑定到输入或输出流上,可用来遍历所关联的IO流
反向迭代器(reverse iterator) 向后而不是向前移动。除了forward_list之外的标准库容器都有
移动迭代器(move iterator) 移动

插入迭代器

插入器类型
back_iterator 创建一个使用push_back的迭代器
front_iterator 创建一个使用push_front的迭代器
inserter 创建一个使用insert的迭代器。次函数接受第二个参数,这个参数必须是一个指向指定容器的迭代器。元素将被掺入到给定迭代器所表示的元素之前。

  对插入迭代器进行*it,++it,it++均不对it做任何事情,每个操作都返回it

it = inserter(c, iter);
*it = val;

it = c.inserter(it, val);
++it;

iostream迭代器

istream_iterator操作

  当创建一个流迭代器时,必须指定迭代器将要读取的对象类型。通过流迭代器,可以使用泛型算法从流对象读取数据以及向其写入数据。默认初始化流迭代器为尾后迭代器。

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);

  由于算法使用迭代器操作来处理数据,而流迭代器又至少支持某些迭代器操作,因此至少可以用某些算法来操作流迭代器。

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

ostream_iterator操作

ostream_iterator操作
ostream_iterator<T> out(os); out将类型为T的值写到输出流os中
ostream_iterator<T> out(os,d); out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组
out = val; 通过<<运算符将val写入到out所绑定的ostream中,val的类型必须与out可写的类型兼容
*out, ++out, out++ 运算符存在,但不对out做任何操作。每个运算符返回out

使用流迭代器处理类类型

  可以为任何定义了输入运算符(<<)的类型创建istream_iterator对象。类似,可以为任何定义了输出运算符(>>)的类型创建ostream_iterator对象。

istream_iterator<Sales_item> item_iter(cin), eof;
ostream_iterator<Sales_item> out_iter(cout, "\n");
Sales_item sum = *item_iter++;
while( item_iter != eof)
{
	if(item_iter->isbn() == sum.isbn())
		sum += *item_iter++;
	else
	{
		out_iter = sum;
		sum = *item_iter++;
	}
}
out_iter = sum;

反向迭代器

   反向迭代器是在容器中从尾元素向首元素反向移动的迭代器。递增(递减)操作的含义会颠倒过来(string容器的字符串也会反过来,可以通过base()函数)

猜你喜欢

转载自blog.csdn.net/accumulating_mocai/article/details/83151714
今日推荐