第10章 泛型算法

1、算法永远不会执行容器操作。

  • 泛型算法本省不会执行容器的操作,它们只会运行与迭代器之上,执行迭代器的操作。
  • 算法永远不会改变底层容器的大小。算法可能癌变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
  • 标准库定义了一类特殊的迭代器,插入器(inserter)。与普通迭代器只能遍历所绑定的容器相比,插入器能做更多的事情。它们会在底层的容器上执行插入操作。当一个算法操作一个这样的迭代器时,迭代器可以完成向容器添加元素的效果,但算法自身永远不会做这样的操作。

2、只读算法:一些算法只会读取其输入范围内的元素,而从不改变元素。

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

4、写容器元素算法

  • 一些算法将新值赋予序列中的元素,当我们使用这类算法时,必须注意确保序列原大小至少不小于我们要求算法写入的元素数目。算法不会执行容器操作,因此他们自身不能改变容器的大小。
  • 确保算法不会试图访问第二个序列中不存在的元素是程序员的责任。
  • 向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。

5、一种确保算法有足够元素空间来容纳输出数据的方法是使用插入迭代器(insert iterator) 。

vector<int> vec;
auto it = back_inserter(vec);  //通过他赋值会将元素添加到vec中
*it = 42;

常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用。

vector<int> vec;
fill_n(back_inserter(vec), 10, 0);  //back_inserter创建一个插入迭代器,可用来向vec中添加元素

6、重排容器元素的算法。

eg: 消除重复单词

void elimDups(vector<string> &word) {
	sort(word.begin(), word.end());  //先排序
	auto end_unique = unique(words.begin(), words.end());  //消除重复的单词
	words.erase(end_unique, words.end());  //删除
}

7、向算法传递函数。

  • 谓词(predicate)是一个可调用的表达式,其返回结果是一个能用作条件的值(返回值为boolean)。
  • 标准库算法使用的谓词分两类:一元谓词(unary predicate),二元谓词(binary predicate)。
//比较函数,用来按长度排序单词
bool isShorter(const string &s1, const string &s2){
	return s1.size() < s2.size();
}
//按长度由短至长排序words
sort(words.begin(), word.end(), isShorter);

8、Lambda表达式: [capture list] (para list) -> return type {functon body}

  • STL算法只接受一元谓词或二元谓词,而有时我们希望进行的操作需要更多的参数,超出了算法对谓词的限制。
  • capture list是一个lambda所在函数中定义的局部变量的列表,return type、para list和function body与任何普通函数一样。lambda必须使用尾置返回。
  • lambda不能有默认参数。
//same as function isShorter()
[] (const string &s1, const string &s2)  {return s1.size() < s2.size();}
  • 使用捕获列表:解决find_if()算法接受一元谓词,但需要操作两个参数,找到大于长度大于sz的元素。
[sz] (const string &s1) {return s1.size() >= sz;} 
//获取一个迭代器,指向第一个满足size() >= sz的元素
auto wc = find_if(words.begin(), words.end(), [sz] (const string &a) {return a.size() >= sz;});
  • 捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。

9、lambda捕获和返回

[] 空捕获列表,lambda不能使用所在函数中的变量
[names] names是一个逗号分割的名字列表,这些名字都是lambda所在函数的局部变量
[&] 隐式捕获列表,采用引用捕获方式
[=] 隐式捕获列表,采用值捕获方式
[&, identifier_list] identifier_list是一个逗号分割的列表,包含0个或多个来自所在函数的变量,这些变量采用值捕获方式,而任何隐式捕获的变量都采用引用方式捕获
[=, identifier_list] identifier_list是一个逗号分割的列表,包含0个或多个来自所在函数的变量,这些变量采用引用捕获方式,而任何隐式捕获的变量都采用值方式捕获
  • 默认情况下,对于一个值捕获的变量,lambda不会改变其值。如果我们希望能改变一个值捕获的变量,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表:
auto f = [v1] () mutable {return ++v1};
  • 默认情况下,如果一个lambda体包含return之外的任何语句,则编译器假定此lambda返回void。被推断返回void的lambda不能返回值。
[] (int i) {if (i < 0) return -i; else return i;}    //错误,编译器推断返回void,实际返回int

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

[] (int i) -> int {if (i < 0) return -i; else return i;}

10、参数绑定

  • 对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的,如果我们需要在很多地方使用相同的操作,通常应该定义一个函数。如果一个操作需要很多语句才能完成,通常使用函数更好。
  • 如果lambda的捕获列表为空,通常可以用函数来代替它。但是,对于捕获局部变量的lambda,用函数来替换它就不是那么容易了。
  • 用在find_if调用中的lambda比较一个string和一个给定大小。可以很容易的编写一个完成同样工作的函数,但find_if的可调用对象必须接受单一参数。
bool check_size(const string &s, string::size_type sz) {
	return s.size() >= sz;
}
  • 函数适配器bind:auto newCallable = bind(callable, arg_list)。arg_list中的参数可能包含形如_n的名字,其中n是一个整数。这些参数是“占位符”,_1为newCallable的第一个参数,_2为第二个参数。
auto wc = find_if(words.begin(), words.end(), bind(check_size, _1, sz));
  • 默认情况下,bind的那些不是占位符的参数被拷贝到bind返回的可调用对象中,但是,与lambda类似,有时对绑定的参数我们希望以引用的方式传递,或是要绑定参数的类型无法拷贝。
    eg:为了替换一个引用方式捕获ostream的lambda
[&os, c] (const string &s) {os << s << c;};

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

//错误,不能拷贝os
bind(print, os, _1, ' ');

//正确
bind(print, ref(os), _1, ' ');

猜你喜欢

转载自blog.csdn.net/weixin_42205011/article/details/87620685