首页 > 代码库 > c++ primer 5th 笔记:第十章
c++ primer 5th 笔记:第十章
第十章:泛型算法
笔记
1. 标准库并未给每个容器添加大量功能,而是提供了一组算法,这些算法(通用的)中的大多数都独立于任何特定的容器。
2. 大多数算法都定义在头文件algorithm中,另外在头文件numeric中定义了一组数值泛型算法。
3. 保证算法有足够的元素空间开容纳输出数据的方法是使用插入迭代器,back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器:
vector<int> vec; // 空向量 auto it = back_inserter(vec); // 通过它赋值会将元素添加到vec中 *it = 42; // vec中现在有一个元素,值为42 // 我们常常使用back_inserter来创建一个迭代器,作为算法的目的位置来使用 fill_n(back_inserter(vec), 10, 0); // 添加10个元素到vec
4. 谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用得谓词分为两类:一元谓词(意味着他们只接受单一参数)和二元谓词(意味着它们有两个参数)。接受谓词参数的算法对输入序列中的元素调用谓词。如:
// 比较函数,用来按长度排序单词 bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); } // 按长度由短至长排序words sort(words.begin(), words.end(), isShorter);
5. 迭代器包括:插入迭代器、流迭代器、反向迭代器、移动迭代器。
6. 插入迭代器:back_inserter 创建一个使用push_back的迭代器。
front_inserter 创建一个使用push_back的迭代器。
inserter 创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将插入到给定迭代器所表示的元素之前。
7. 任何算法的最基本特性是它要求其迭代器提供哪些操作。
重点知识点总结:
一、lambda表达式:
1.介绍lambda
我们可以向一个算法传递任何类别的可调用对象(callable object)。对于一个对象或一个表达式,如果可以对其使用调用运算符(即圆括号()),则称它为可调用的。一个lambda表示式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。一个lambda表达式具有如下形式:
[capture list] (parameter list) -> return type { function body }
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、paramete list和 function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。但是,与普通函数不同,lambda表达式必须使用尾置返回。如:
auto f = [] { return 42; }; // 定义了一个可调用对象f,它不接受参数,返回42 // lambda调用方式与普通函数调用方式一样,都是使用调用运算符 cout << f() << endl; // 打印42
如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则默认返回void。
2. 向lambda传递参数
// 按长度排序 stable_sort(words.begin(), words.end(), [](const string &s1, const string &s2) { return s1.size() < s2.size(); } // lambda不能有默认实参 // 空捕获列表表明此lambda不使用它所在函数中的任何局部变量
3. 使用捕获列表
虽然lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些在捕获列表中明确指明的变量。
[sz](const string &a) { return a.size() >= sz; };
4. find_if 和 for_each算法中使用lambda
// 获取一个迭代器,指向第一个满足size() >= sz 的元素,sz是一个局部变量 auto wc = find_if(words.begin(), words.end(), [sz](const string &a) { return a.size() >= sz; }); // 打印长度大于等于给定值的单词,每个单词后面接一个空格 for_each(wc, words.end(), [] (const string &s) { cout << s << " ";} ); cout << endl; // 一个lambda可以直接使用定义在当前函数之外的的名字。 // cout不是定义在此代码所在局部作用域的中的局部名字,而是定义在头文件iostream中。 // 因此,只要包含了头文件iostream,就可以使用cout
5. lambda捕获和返回
当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名)类类型。当向一个函数传递一个lambda时,同时定义了一个新类型和该类型的一个对象:传递的参数就是此编译器生成的类类型的未命名对象。
类似与参数传递,变量的捕获方式也可以是值或引用。
// 值捕获方式,采用捕获的前提是变量可以拷贝 void fcnl() { size_t v1 = 42; // 局部变量 // 将v1拷贝到名为f的可调用对象 auto f = [v1] { return v1; }; v1 = 0; auto j = f(); // j为42,;f保存了我们创建它时v1的拷贝 } // 与参数不同,被捕获的变量的值是在lamba创建时拷贝,而不是调用时拷贝 // 引用捕获, 定义lambda时可以采用引用方式捕获变量 void fcn2() { size_t v1 = 42; // 局部变量 // 对象f2包含v1的引用 auto f2 = [&v1] { return v1; }; v1 = 0; auto j = f2(); // j为0; f2保存v1的引用,而非拷贝 }
当以引用方式捕获一个变量时,必须保证在lambda执行时变量时存在的。尽量减少捕获的数据量,来避免潜在的捕获导致的问题。
6. 隐式捕获
除了显式累出我们希望使用的来自所在函数的变量之外,还可以让编译器根据lambda体中的代码来推断我们要使用哪些变量。为了指示编译器推断捕获列表,应在捕获列表中写一个&或=,&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。如:
// sz 为隐式捕获,值捕获方式 wc = find_if(words.begin(), words.end(), [=](const string &s) { return s.size() >= sz; } ); // 如果我们系统对一部分变量采用值捕获,对其他变量采用引用捕获,可以混合使用隐式捕获 // 和显示捕获 // os隐式捕获,引用捕获方式; c显式捕获,值捕获方式 for_each(words.begin(), words.end(), [&, c](const string &s) { os << s << c; } ); // os显式捕获,引用捕获方式; c隐式捕获,值捕获方式 for_each(words.begin(), words.end(), [=, &os](const string &s) { os << s << c; } ); // 注意!! 当我们混合使用隐式捕获和显式捕获时,捕获列表中的第一个元素必须是一个 // &或=。此符号指定了默认捕获方式为引用或值。并且所有显式捕获方式必须 // 与默认捕获方式不同,放在&或=后面。
7.指定lambda返回类型
默认情况下,如果一个lambda体包含return之外的任何语句,则编译器(如果未显式指定返回类型)假定此lambda返回void。
二、标准库bind函数(待写)
术语
二元谓词(binary predicate)、可调用对象(callable object)、捕获列表(capture list)、插入器(inserter)、
泛型算法(generic algorithm)、谓词(predicate)、lambda表达式、反向迭代器(reverse iterator)、
流迭代器(stream iterator)。
2016-11-11 22:44:46
c++ primer 5th 笔记:第十章