本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:cpp_primer:10_algorithms [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:10_algorithms [2024/01/14 13:47] (当前版本) – ↷ 链接因页面移动而自动修正 codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | | ||
+ | C++ Primer 笔记 第十章\\ | ||
+ | ---- | ||
+ | **泛型算法**(// | ||
+ | * 不限定类型:可以应用到不同容器甚至 build-in 类型上 | ||
+ | * 主要使用迭代器操作 | ||
+ | * 实现的主要是算法的内容,比如排序、寻找等等 | ||
+ | ====概述==== | ||
+ | 泛型算法主要定义在两个头文件中: | ||
+ | * ''< | ||
+ | * ''< | ||
+ | 泛型算法并不直接操作目标,而是主要通过维护**两个迭代器**(一首一尾)来进行对目标的遍历。比如使用 //find// 搜寻某个 //vector// 中的指定元素: | ||
+ | <code cpp> | ||
+ | int val = 42; // value we'll look for | ||
+ | // result will denote the element we want if it's in vec, or vec.cend() if not | ||
+ | auto result = find(vec.cbegin(), | ||
+ | </ | ||
+ | //find// 通过两个迭代器确定搜寻的范围,再通过 '' | ||
+ | <code cpp> | ||
+ | string val = "a value"; | ||
+ | // this call to find looks through string elements in a list | ||
+ | auto result = find(lst.cbegin(), | ||
+ | </ | ||
+ | 由于在 //build-in array// 中也可以通过 //begin()// 和 //end()// 函数实现类似于迭代器的功能,因此 find 也可以应用于 //build-in array//: | ||
+ | <code cpp> | ||
+ | int ia[] = {27, 210, 12, 47, 109, 83}; | ||
+ | int val = 83; | ||
+ | int* result = find(begin(ia), | ||
+ | </ | ||
+ | ==泛型算法如何工作== | ||
+ | 泛型算法通过**迭代器**进行操作。比如之前的 //find// 的一个简单流程: | ||
+ | - 访问第一个元素 | ||
+ | - 与目标值比较,如果匹配返回值说明找到,否则接着进行寻找 | ||
+ | - 如最终未能找到,返回指定值说明没有找到 | ||
+ | 我们发现这整个过程都没有任何与容器相关的操作。因此,只要是能使用迭代器访问元素的地方,// | ||
+ | ==算法、容器和迭代器的关系== | ||
+ | |||
+ | 尽管泛型算法不依赖于容器,但是依赖于**容器元素类型**的操作。比如刚才 //find// 中比较元素的步骤,就必须根据元素指定的比较法则来进行。\\ \\ | ||
+ | 这意味着绝大多数的算法并没有对容器的直接操作;换句话说,泛型算法可能会改变容器元素的值,或者交换元素的位置,但**不会更改容器本身的大小**(不会**直接**做添删类操作)。 | ||
+ | |||
+ | ====初识泛型算法==== | ||
+ | C++ 标准库中至少包含了 100 多种泛型算法。从体系上来看,绝大多数算法都是对一个“范围” 内的元素进行操作,而该范围一定是使用迭代器来表示。而真正体现算法差异性的是算法的内容。根据内容的不同,泛型算法可以分为三类: | ||
+ | * 只读算法 | ||
+ | * 读/ | ||
+ | * 元素重排序算法 | ||
+ | ===只读算法=== | ||
+ | |||
+ | |||
+ | **只读算法**(// | ||
+ | ==只读算法:accumulate== | ||
+ | // | ||
+ | <code cpp> | ||
+ | accumulate(vec.cbegin(), | ||
+ | </ | ||
+ | 迭代器表示范围,第三个参数表示累加的**起始值**。需要注意的是,由于第三个参数参与了累加,因此隐含了两个条件: | ||
+ | * 返回内的元素需要与第三个参数的类型必须相同,或是可以转化 | ||
+ | * 用于累积的元素定义了加法(可以相加) | ||
+ | 基于这样的条件来判断,带有 //string// 的容器也能使用 // | ||
+ | <code cpp> | ||
+ | string sum = accumulate(v.cbegin(), | ||
+ | </ | ||
+ | 算法严格遵循这样的一致性。一个反例就是上例不能使用 literial 空字符串作为起始值,因为 '' | ||
+ | <code cpp> | ||
+ | string sum = accumulate(v.cbegin(), | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 除了需要使用算法返回的迭代器改变元素值的情况,只读算法都应该使用 // | ||
+ | </ | ||
+ | |||
+ | ==只读算法:equal== | ||
+ | //equal// 是一种应用于两个序列的算法,用与比较连个序列是否相等。// | ||
+ | <code cpp> | ||
+ | // roster2 should have at least as many elements as roster1 | ||
+ | equal(roster1.cbegin(), | ||
+ | </ | ||
+ | //equal// 通过对两个序列每个位置的对应元素进行比较来得出结果。其返回值为 '' | ||
+ | * //equal// 可以比较不同类型的元素序列 | ||
+ | * //equal// 可以比较不同长度的元素序列 | ||
+ | 需要注意的是,由于 //equal// 算法从设计上来说需要查看第一个序列的所有元素,因此 //equal// 假定第二个序列的长度**不小于**第一个序列的长度。 | ||
+ | ===写算法=== | ||
+ | 写算法是 STL 中提供的,用于修改元素内容的算法。该类算法有个非常重要的使用前提:**确保被修改的容器足够大到装下算法需要修改的元素个数。**\\ \\ | ||
+ | 具体的来说,写算法大概分为三种: | ||
+ | * 修改自身容器,即只接受一对迭代器。这种操作是安全的,因为可写的元素总是在范围内。 | ||
+ | * 使用一个序列修改另外一个序列:这种情况又可以细分为两种: | ||
+ | * 接收**三个迭代器**的情况。头两个表示第一个序列的范围,第三个表示第二个序列的开头位置。这种情况下,算法会假定**第二个序列的长度大于等于第一个序列**;也就是我们必须确定第二序列足够的容量供修改。 | ||
+ | * 接收四个迭代器的情况:两个表示第一序列的范围,两个表示第二序列的范围。 | ||
+ | ==写算法:fill & fill_n== | ||
+ | fill 是一种常见的写算法,其所用是将范围类的元素全替换为指定值。它接收三个参数,前两个确定元素的范围,第三个数为指定值: | ||
+ | <code cpp> | ||
+ | fill(vec.begin(), | ||
+ | </ | ||
+ | //fill// 属于之前提到的修改当前序列本身的算法,因此操作是确认安全的。不过即便如此,我们也应该谨记**算法不会检查写操作**这个事实。 //fiil// 算法的另外一个变种 // | ||
+ | <code cpp> | ||
+ | fill_n(vec.begin(), | ||
+ | </ | ||
+ | '' | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | // disaster: attempts to write to ten (nonexistent) elements in vec | ||
+ | fill_n(vec.begin(), | ||
+ | </ | ||
+ | <WRAP center round important 100%> | ||
+ | 总之需要谨记的是,算法不会检查容器本身,它只会假定容器具有写入的资格(足够大,并且可以容纳指定类型的元素) | ||
+ | </ | ||
+ | |||
+ | ==确保被写容器拥有足够空间的手段:back_inserter== | ||
+ | |||
+ | 我们可以用 // | ||
+ | * 将迭代器绑定至容器中需要添加元素的位置 | ||
+ | * 向该迭代器指向的位置进行赋值 | ||
+ | // | ||
+ | * 获取容器的引用 | ||
+ | * 返回一个绑定到该容器的迭代器 | ||
+ | * 调用 // | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | auto it = back_inserter(vec); | ||
+ | *it = 42; | ||
+ | </ | ||
+ | 该种类型的迭代器可以与写算法一起连用,达到循环新建容器元素并写入的效果: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | fill_n(back_inserter(vec), | ||
+ | </ | ||
+ | ==写算法:copy & replace== | ||
+ | //copy// 是另一种常见的写算法,其功能是将源序列的内容复制到目标序列中。其接收三个迭代器(源序列范围 + 目标序列起始点),因此需要确保目标序列的容量足够大: | ||
+ | <code cpp> | ||
+ | int a1[] = {0, | ||
+ | int a2[sizeof(a1)/ | ||
+ | // ret points just past the last element copied into a2 | ||
+ | auto ret = copy(begin(a1), | ||
+ | </ | ||
+ | //copy// 会返回一个迭代器,该迭代器指向**复制**结果的 // | ||
+ | 除此之外,其他某些算法也提供类似 copy 的功能,比如 // | ||
+ | <code cpp> | ||
+ | // replace any element with the value 0 with 42 | ||
+ | replace(ilst.begin(), | ||
+ | </ | ||
+ | 如果不想修改源序列的值,可以使用另外一个版本 // | ||
+ | <code cpp> | ||
+ | // use back_inserter to grow destination as needed | ||
+ | replace_copy(ilst.cbegin(), | ||
+ | </ | ||
+ | 这里 使用了 // | ||
+ | ===排序算法=== | ||
+ | 排序算法指改变容器内元素顺序的算法。 | ||
+ | ==排序算法:sort() & unique()== | ||
+ | 常用的对容器元素排序的算法有:// | ||
+ | |||
+ | < | ||
+ | //sort()// 接收两个表示范围的迭代器,对范围内的元素按照 ''<'' | ||
+ | <code cpp> | ||
+ | sort(words.begin(), | ||
+ | </ | ||
+ | < | ||
+ | // | ||
+ | <code cpp> | ||
+ | unique(words.begin(), | ||
+ | </ | ||
+ | 其机制稍微有些复杂: | ||
+ | * 首先,其目的是**移除重复的元素**,创造一个不重复元素的序列 | ||
+ | * 其次,由于泛型算法的限制,// | ||
+ | * 再次,// | ||
+ | 可以看出 // | ||
+ | * 首先通过排序,重复的元素会变得相邻。 | ||
+ | * 其次通过 // | ||
+ | * 最后通过 //earse()// 真正删除上述范围的元素。 | ||
+ | < | ||
+ | 写成代码就是: | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | sort(words.begin(), | ||
+ | end_unique = unique(words, | ||
+ | words.erase(end_unique, | ||
+ | </ | ||
+ | 这种情况下,即使序列中一开始就没有重复元素(即 // | ||
+ | |||
+ | ====为算法定制比较策略==== | ||
+ | 某些比较算法采用默认的策略对元素进行比较,比如 '' | ||
+ | |||
+ | ===方式1:传递函数给算法=== | ||
+ | 这种特殊的版本以**传递函数的方式** 将定制的比较策略应用到算法中,其中函数即为我们制定的策略。以 //sort()// 为例。通常情况下, | ||
+ | 谓词如下的特性使其在这里实际上作为了定制策略的载体: | ||
+ | * 它是一个**表达式**(可以是函数) | ||
+ | * 它可以被算法调用 | ||
+ | * 它接收**元素**作为 argument(算法调用它对元素进行比较) | ||
+ | 因此整个流程可以描述为: | ||
+ | - 谓词装载定制的函数 | ||
+ | - 该函数接收元素作为 argument | ||
+ | - 算法通过调用该函数完成定制策略下元素的比较: | ||
+ | < | ||
+ | 比如 // | ||
+ | <code cpp> | ||
+ | bool isShorter(const string &s1, const string &s2) { | ||
+ | return s1.size() < s2.size(); | ||
+ | } | ||
+ | </ | ||
+ | 接着直接通过带谓词的算法版本调用则可使用该规则完成排序: | ||
+ | <code cpp> | ||
+ | sort(words.begin(), | ||
+ | </ | ||
+ | 这里有两点需要注意: | ||
+ | * 谓词可以接收的 parameter 是 1-2 个,parameter 的数量被称为**元**,即接收单个 parameter 的谓词被称为**一元谓词**(// | ||
+ | * 由于谓词接收元素作为 argument,因此**元素的类型与谓词中函数 parameter 的类型需要匹配**。 | ||
+ | ==实例:谓词配合 stable_sort() 排序== | ||
+ | 上例中,通过比较 //string// 长度得出来的结果很可能看起来不那么直观,因为规则只比较 //string// 的长度,因此长度相同的 //string// 会被无序的放在一起。如果希望结果看起来更直观,可以使用 // | ||
+ | <code cpp> | ||
+ | stable_sort(words.begin(), | ||
+ | </ | ||
+ | ===方式2:Lambda 表达式=== | ||
+ | |||
+ | 根据书中的实例:需要实现一个 '' | ||
+ | <code cpp> | ||
+ | void biggies(vector< | ||
+ | | ||
+ | { | ||
+ | elimDups(words); | ||
+ | // resort by length, maintaining alphabetical order among words of the same length | ||
+ | stable_sort(words.begin(), | ||
+ | // get an iterator to the first element whose size() is >= sz | ||
+ | // compute the number of elements with size >= sz | ||
+ | // print words of the given size or longer, each one followed by a space | ||
+ | } | ||
+ | </ | ||
+ | 例子中的另外一个要求是希望该函数可以作为自定义的策略提供给 // | ||
+ | ==find_if 简介== | ||
+ | // | ||
+ | <code cpp> | ||
+ | find_if (InputIterator first, InputIterator last, UnaryPredicate pred) | ||
+ | </ | ||
+ | ===Lambda 的基本概念=== | ||
+ | ==Lambda 表达式以及相关背景== | ||
+ | 由于 // | ||
+ | \\ \\ <color # | ||
+ | 总的来说,如果一个对象被称作**可调用对象**(// | ||
+ | \\ \\ <color # | ||
+ | 可调用对象是指可以对其使用调用运算符 '' | ||
+ | <code cpp> | ||
+ | e(argment_list); | ||
+ | </ | ||
+ | 加上本节的 //Lambda// 表达式,已知的可调用对象有四种: | ||
+ | * 函数 | ||
+ | * 函数指针 | ||
+ | * //Lambda// 表达式 | ||
+ | * 重载了 '' | ||
+ | ==Lambda 表达式的组成与定义== | ||
+ | |||
+ | 和函数相似,// | ||
+ | * 有 parameter list 与 返回值 | ||
+ | * 有函数体 | ||
+ | 但与函数稍微不同的是, //Lambda// 可以(通常)定义于函数的内部。其定义写法也采用了**尾置**的形式: | ||
+ | < | ||
+ | |||
+ | 其中: | ||
+ | *//Capture list// 表示需要使用的**函数局部变量**(多用于**规则函数的实参传递**,与 Lambda处于同一个作用域) | ||
+ | *// | ||
+ | *//Return type// 与普通函数相同,但必须用// | ||
+ | *//Function body// 表示 //Lamaba// 表达式的内容。 | ||
+ | 需要注意的是,// | ||
+ | <code cpp> | ||
+ | auto f = [] { return 42; } //f returns an int | ||
+ | </ | ||
+ | 但如果这种情况下函数体中语句**不止一句**,则表达式的返回类型为 '' | ||
+ | 除此之外,调用 lambda 表达式的方式与普通函数相同: | ||
+ | <code cpp> | ||
+ | cout << f() << endl; //print 42 | ||
+ | </ | ||
+ | ==向 Lambda 传递参数== | ||
+ | lambda 表达式与普通函数接收 argument 的方式相同。唯一的区别是 lambda 表达式不能设置 default argument。因此 lambda 表达式接收的 argument 与自身的 parameter 永远相等。 | ||
+ | \\ \\ | ||
+ | 之前见过的 // | ||
+ | <code cpp> | ||
+ | [](const string& s1, const string& s2) { return s1.size() < s2.size(); } | ||
+ | </ | ||
+ | 而之前使用 string 长度排序的 // | ||
+ | <code cpp> | ||
+ | stable_sort(word.begin(), | ||
+ | [] (const string& s1, const string& s2) | ||
+ | { return s1.size() < s2.size(); }) | ||
+ | </ | ||
+ | ==使用 Capture list== | ||
+ | 回到书中的例子。我们的目标是同时将上级函数 // | ||
+ | |||
+ | 捕获的意思是,当 lambda 想使用**上级函数**(// | ||
+ | <code cpp> | ||
+ | [sz] (const string& s) { return s.size() >= sz; } | ||
+ | </ | ||
+ | //Capture list// 允许我们捕获多个局部变量用于使用,只需要用 comma 隔开即可。需要注意的是,Capture list 遵循捕获后才能使用的原则;如果其为空,则意味着 lambda 无法使用任何上级函数的局部变量。 | ||
+ | == lambda 与 find_if() 的连用== | ||
+ | 此时我们的 | ||
+ | <code cpp> | ||
+ | auto wc = find_if(word.begin(), | ||
+ | [sz] (const string& s) | ||
+ | { return s.size() >= sz; }) | ||
+ | </ | ||
+ | 因为 '' | ||
+ | <code cpp> | ||
+ | cout << count << " " << make_plural(count, | ||
+ | << "of length" | ||
+ | </ | ||
+ | ==for_each() 简介== | ||
+ | // | ||
+ | <code cpp> | ||
+ | for_each(wc, | ||
+ | {cout << s << " "; }) | ||
+ | cout << endl; | ||
+ | </ | ||
+ | 需要注意的是: | ||
+ | * **只有上级函数中的局部变量,才需要捕获后使用**。除此之外,lambda 可以随意使用外部名字,比如这里的 '' | ||
+ | * 只有 non-static 的局部变量需要经过 //Capture list// 捕获才能使用。local static varible,及其在函数外部的名字均可以直接被 lambda使用。 | ||
+ | ==整个的 biggies 函数== | ||
+ | <code cpp> | ||
+ | void biggies(vector< | ||
+ | |||
+ | // | ||
+ | |||
+ | elim_dups(words); | ||
+ | |||
+ | //sort words by size, maintain the alphabetical order for words of the same length | ||
+ | |||
+ | stable_sort(words.begin(), | ||
+ | | ||
+ | { return s1.size() < s2.size(); }); | ||
+ | |||
+ | //get the sequence range of the elements that has length >= sz | ||
+ | |||
+ | auto wc = find_if(words.begin(), | ||
+ | [sz](const string& s) | ||
+ | { return s.size() >= sz; }); | ||
+ | auto count = words.end() - wc; | ||
+ | |||
+ | //print the additional info | ||
+ | |||
+ | cout << count << " " << make_plural(count, | ||
+ | << " of length " << sz << " or longer " << endl; | ||
+ | |||
+ | //print each element in the range | ||
+ | |||
+ | for_each(wc, | ||
+ | [](const string& s) { cout << s << endl; }; | ||
+ | cout << endl; | ||
+ | |||
+ | } | ||
+ | </ | ||
+ | ===Lambda 的捕获与返回=== | ||
+ | 在 lambda 被传递给算法的整个过程中,编译器主要做了两件事: | ||
+ | * 当 lambda 被定义的时候,编译器生成了一个新的(没有名字)的 class type,这个 class type 对应被定义的 lambda。 | ||
+ | * 当传递 lambda 给算法时候,编译器创建了该 class type 的一个**对象**,并将该对象作为 argument 传递给算法。 | ||
+ | 捕获列表中的局部变量也是这个对象的一部分。根据参数的传递方式的不同,捕获列表中的变量也可以分为两种捕获方式:**值捕获**(// | ||
+ | ==值捕获== | ||
+ | 值捕获与值传递的方式一样,通过拷贝的方式传递参数;因此可以使用值捕获的变量**必须能被拷贝**。与函数不同的是,值捕获的变量在 lambda 被**创建**的时候就已经复制**初始化完毕**了,因此之后对原有变量的修改不会对 lambda 的捕获变量造成任何影响: | ||
+ | <code cpp> | ||
+ | /* caputure by value: change v1 doesn' | ||
+ | void fcn1() | ||
+ | { | ||
+ | size_t v1 = 42; // local variable | ||
+ | // copies v1 into the callable object named f | ||
+ | auto f = [v1] { return v1; }; | ||
+ | v1 = 0; | ||
+ | auto j = f(); // j is 42; f stored a copy of v1 when we created it | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==引用捕获== | ||
+ | 另外一种方式是引用捕获,捕获列表中的元素是**上级函数中局部变量的引用**。由于捕获的是引用,因此通过该引用改变绑定的局部变量也会影响到 lambda: | ||
+ | <code cpp> | ||
+ | /* lambda captured a reference that refers to v1, changes v1 also changes the lambda.*/ | ||
+ | void fcn2() | ||
+ | { | ||
+ | size_t v1 = 42; // local variable | ||
+ | // the object f2 contains a reference to v1 | ||
+ | auto f2 = [&v1] { return v1; }; | ||
+ | v1 = 0; | ||
+ | auto j = f2(); // j is 0; f2 refers to v1; it doesn' | ||
+ | } | ||
+ | </ | ||
+ | 这种捕获方式也受传统引用传递方式规则的约束: | ||
+ | * 该引用遵循任何一般变量引用的规则 | ||
+ | * 通过引用方式捕获的变量,在 lambda 执行的时候**必须存在**(特别需要注意上级函数的周期,因为引用的是局部变量)。 | ||
+ | <color # | ||
+ | 在传递某些**不能被拷贝的参数**的情况下。比如我们想使用 // | ||
+ | <code cpp> | ||
+ | void biggies(vector< | ||
+ | ostream& | ||
+ | ... | ||
+ | //using fucntion parameter ostream and lambda to print | ||
+ | for_each(words.begin(), | ||
+ | [& | ||
+ | os << endl; | ||
+ | } | ||
+ | </ | ||
+ | <color # | ||
+ | 在函数**返回**自身 lambda 的情况下。通常,lambda 会作为一个对象返回。而此时,捕获列表中的局部变量会作为对象成员一起返回。由于局部变量的生命周期只到函数结束前,如果通过引用捕获获取变量,那返回的就是**指向不存在变量的引用**,这显然是不能被允许的。\\ \\ | ||
+ | <color # | ||
+ | 一个总的原则是,尽可能的保证一个简单的捕获列表。只有在捕获间接访问对象(引用,指针)的时候,我们才需要确保其存在性(引用与存在的值绑定)和正确性(引用不修改绑定的值)。因此如果可能,尽可能避免**捕获间接访问对象**。 | ||
+ | ==隐藏捕获以及混用== | ||
+ | lambda 可以在不将变量写入捕获列表的情况下,通过符号 '' | ||
+ | * '' | ||
+ | * ''&'' | ||
+ | 如果需要特殊指定某些变量的捕获方式,可以将需要指定的变量与上述符号混用,中间用逗号隔开: | ||
+ | <code cpp> | ||
+ | void biggies(vector< | ||
+ | | ||
+ | | ||
+ | { | ||
+ | |||
+ | // os implicitly captured by reference; c explicitly captured by value | ||
+ | for_each(words.begin(), | ||
+ | [&, c](const string &s) { os << s << c; }); | ||
+ | // os explicitly captured by reference; c implicitly captured by value | ||
+ | for_each(words.begin(), | ||
+ | [=, & | ||
+ | } | ||
+ | </ | ||
+ | 混用时需要注意几点: | ||
+ | * 必须以符号 '' | ||
+ | * 列表中**显式捕获的变量**(逗号后的)必须形式上与隐式捕获的符号不同。比如以 '' | ||
+ | |||
+ | ==Mutable Lambda== | ||
+ | <WRAP center round info 100%> | ||
+ | Mutable Lambda 只针对于**值捕获**,即捕获列表中的变量都是拷贝的情况。 | ||
+ | </ | ||
+ | |||
+ | **在值捕获的情况下**,lambda 默认不能修改捕获列表中的变量拷贝。如果希望修改,可以使用关键字 '' | ||
+ | <code cpp> | ||
+ | void fcn3() { | ||
+ | size_t v1 = 42; | ||
+ | auto f = [v1] () mutable { return ++v1; }; | ||
+ | auto j = f(); //j = 43 | ||
+ | } | ||
+ | </ | ||
+ | 需要注意这种情况下是不能省略参数列表的(本例中的 '' | ||
+ | 如果是**引用捕获**,那么是否能修改捕获的变量**只与引用的变量类型**有关系。如果引用的是 non-const 变量,那么就可以修改: | ||
+ | <code cpp> | ||
+ | void fcn4() { | ||
+ | size_t v1 = 42; // local variable | ||
+ | // v1 is a reference to a non const variable | ||
+ | // we can change that variable through the reference inside f2 | ||
+ | auto f2 = [&v1] { return ++v1; }; | ||
+ | v1 = 0; | ||
+ | auto j = f2(); // j is 1 | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | ==指定 Lambda 的返回类型== | ||
+ | lambda 在没有显式指定返回类型,且函数体中不单有 '' | ||
+ | \\ \\ | ||
+ | 先介绍一下 // | ||
+ | * 头两个对象指代输入序列的范围 | ||
+ | * 第三个迭代器指代源序列的起始点 | ||
+ | * 可调用对象代表要实施的操作 | ||
+ | 下列例子中,我们希望使用 lambda 对 '' | ||
+ | <code cpp> | ||
+ | transform(vi.begin(), | ||
+ | [](int i) -> int | ||
+ | {if (i < 0) return -i; else return i; }); | ||
+ | </ | ||
+ | ===方法3:参数绑定=== | ||
+ | lambda 适合简单的规则;当需要大量重复使用规则,或是规则实现复杂时,函数是才是更好的选择。但当 lambda 使用捕获列表时,可传递的 parameter 并不受算法和谓词的限制;而函数本身无法替代该功能。\\ \\ | ||
+ | 为了解决这个问题,C++ 提供了 //bind()// 函数。该函数定义于 ''< | ||
+ | ==bind 与 placeholder== | ||
+ | 以 // | ||
+ | <code cpp> | ||
+ | bool check_size(const string &s, string:: | ||
+ | return s.size() >= sz; | ||
+ | } | ||
+ | </ | ||
+ | 使用下面的写法可以得到一个新的函数 // | ||
+ | <code cpp> | ||
+ | auto check6 = bind(check_size, | ||
+ | </ | ||
+ | < | ||
+ | 实际上,// | ||
+ | <code cpp> | ||
+ | auto newCallable = bind(callable, | ||
+ | </ | ||
+ | 当调用 '' | ||
+ | 同时,之前的例子中存在 '' | ||
+ | \\ \\ < | ||
+ | '' | ||
+ | <code cpp> | ||
+ | string s = " | ||
+ | bool b1 = check6(s); | ||
+ | </ | ||
+ | |||
+ | 比较一下这种写法与 lambda 的区别: | ||
+ | <code cpp> | ||
+ | auto wc = find_if(words.begin(), | ||
+ | [sz](const string &a); | ||
+ | | ||
+ | auto wc = find_if(words.begin(), | ||
+ | | ||
+ | </ | ||
+ | 很明显,没有被绑定的参数可以被视作接受**上级函数参数的载体**,比如: | ||
+ | <code cpp> | ||
+ | void biggies(vector< | ||
+ | auto wc = find_if(words.begin(), | ||
+ | | ||
+ | } | ||
+ | </ | ||
+ | | ||
+ | 需要注意的是,一旦确定 placeholder 的位置后,其与**被适配对象**和**新产生对象**中的 parameter 都会一一对应起来。利用 //bind()// 的这种功能,可以通过对 '' | ||
+ | <code cpp> | ||
+ | auto g = bind(f,a, b, _2, c, _1)); | ||
+ | </ | ||
+ | 由于 // | ||
+ | <code cpp> | ||
+ | f(a, | ||
+ | </ | ||
+ | 如果我们希望通过 '' | ||
+ | <code cpp> | ||
+ | f(a, | ||
+ | </ | ||
+ | 那么需要替换 placeholder 在整个 argument list 中的位置即可: | ||
+ | <code cpp> | ||
+ | // g is a callable object that takes two arguments | ||
+ | auto g = bind(f, a, b, _1, c, _2); | ||
+ | </ | ||
+ | < | ||
+ | 可以总结出来的是: | ||
+ | * '' | ||
+ | * '' | ||
+ | ==使用 bind 对参数重新排序== | ||
+ | 在某些应用下,使用 //bind()// 重新安排参数顺序能达到改变函数本身功能的效果,比如: | ||
+ | <code cpp> | ||
+ | // sort on word length, shortest to longest | ||
+ | sort(words.begin(), | ||
+ | // sort on word length, longest to shortest | ||
+ | sort(words.begin(), | ||
+ | </ | ||
+ | 原本函数的功能是返回 '' | ||
+ | ==使用 placeholder 的前提== | ||
+ | placeholder 定义于命名空间 '' | ||
+ | <code cpp> | ||
+ | using std:: | ||
+ | </ | ||
+ | 如果有多个 placeholder 存在,可以直接声明其所在命名空间来简化书写: | ||
+ | <code cpp> | ||
+ | using namespace std:: | ||
+ | </ | ||
+ | 这种写法可以允许我们使用 '' | ||
+ | ==绑定引用 parameters== | ||
+ | lambda 通过捕获列表获取局部变量。// | ||
+ | 而某些对象是一定要通过引用传递的,比如之前提到的 '' | ||
+ | <code cpp> | ||
+ | //os is a local variable referring to an output stream | ||
+ | for_each(words.begin(), | ||
+ | </ | ||
+ | 如果我们希望在 bind 中以引用的形式传递该对象,那么需要使用库函数 //ref()//: | ||
+ | <code cpp> | ||
+ | for_each(words.begin(), | ||
+ | </ | ||
+ | 该函数还有传递 reference to const 的版本 // | ||
+ | <WRAP center round help 100%> | ||
+ | 更早版本的 C++ 提供了 // | ||
+ | |||
+ | ====再探迭代器==== | ||
+ | 除了一般应用于容器的迭代器以外,STL 还额外定义了几种通用的迭代器: | ||
+ | * 插入迭代器(// | ||
+ | * 流迭代器(// | ||
+ | * 反向迭代器(// | ||
+ | * 移动迭代器(// | ||
+ | ===插入迭代器=== | ||
+ | 插入迭代器拥有自身对应的**函数适配器**(// | ||
+ | <code cpp> | ||
+ | it = 222; //it denotes the insert point | ||
+ | </ | ||
+ | 根据插入迭代器的不同,其插入的位置与调用的函数都有不同: | ||
+ | * // | ||
+ | * // | ||
+ | * // | ||
+ | <WRAP center round important 100%> | ||
+ | 使用插入迭代器的容器必须支持该迭代器对应的调用。 | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 插入迭代器支持 *,++, -- 等运算,但这些运算对其没有任何影响,统统会返回迭代器本身。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==inserter== | ||
+ | 成功 '' | ||
+ | <code cpp> | ||
+ | it = inserter(c, iter); | ||
+ | </ | ||
+ | 那么当我们在使用该迭代器进行插入操作的时候: | ||
+ | <code cpp> | ||
+ | it = 222; | ||
+ | </ | ||
+ | 实际上等同于: | ||
+ | <code cpp> | ||
+ | it = c.insert(it, | ||
+ | ++it; | ||
+ | </ | ||
+ | 这是因为 // | ||
+ | \\ \\ < | ||
+ | 换句话说,插入的位置由 '' | ||
+ | ==back_inserter== | ||
+ | // | ||
+ | <code cpp> | ||
+ | it = back_inserter(c); | ||
+ | it = 222; | ||
+ | it = 333; | ||
+ | //output | ||
+ | ........, 222, 333 | ||
+ | </ | ||
+ | ==front_inserter== | ||
+ | // | ||
+ | <code cpp> | ||
+ | it = back_inserter(c); | ||
+ | it = 222; | ||
+ | it = 333; | ||
+ | //output | ||
+ | 333, | ||
+ | </ | ||
+ | 也就是插入的元素会以**倒序**的形式呈现。 | ||
+ | <WRAP center round info 100%> | ||
+ | **插入迭代器** 也拥有 ++it, it++, *it 等操作。但与一般迭代器不同,这些操作对其均不构成任何影响,统统会返回 it。 | ||
+ | </ | ||
+ | 书中的一些例子: | ||
+ | <code cpp> | ||
+ | list< | ||
+ | list< | ||
+ | // after copy completes, 1st2 contains 4 3 2 1 | ||
+ | copy(1st.cbegin(), | ||
+ | // after copy completes, 1st3 contains 1 2 3 4 | ||
+ | copy(1st.cbegin(), | ||
+ | </ | ||
+ | ===iostream 迭代器=== | ||
+ | // | ||
+ | * // | ||
+ | * // | ||
+ | 常见的操作如下表: | ||
+ | \\ < | ||
+ | ==istream_iterator 的操作== | ||
+ | 使用 // | ||
+ | * 创建 istream_iterator 时需要指定其操作对象的**类型** | ||
+ | * istream_iterator 使用 ''>>'' | ||
+ | 一些实例: | ||
+ | <code cpp> | ||
+ | istream_iterator< | ||
+ | istream_iterator< | ||
+ | ifstrem in(" | ||
+ | istream_iterator< | ||
+ | </ | ||
+ | 通过 istream_iterator 可以将输入的内容存到 vector 中. 这里 vector 的创建可以直接使用 | ||
+ | <code cpp> | ||
+ | vector< | ||
+ | //read inputs into a vector | ||
+ | while(int_it != int_eof) | ||
+ | vec.push_back(*int_it++); | ||
+ | </ | ||
+ | ==istream_iterator 与算法的配合使用== | ||
+ | 某些算法可以使用一对 istream_iterator 作为参数来获取输入范围,比如 '' | ||
+ | <code cpp> | ||
+ | istream_iterator< | ||
+ | cout << accumulate(in, | ||
+ | </ | ||
+ | 该段代码实现了所有从输入端输入的值的累加。 | ||
+ | ==istream_iterator 与 lazy evaluation== | ||
+ | istream_iterator 读取数据采用的是 lazy evaluation 的机制;也就是说,我们不能保证 istream_iterator 对 stream 的读取是即时的。该读取过程会发生在对 istream_iterator 的解应用之前。通常情况该机制下没有什么影响,除了以下两种情况: | ||
+ | * istream_iterator 创建后没被使用就被销毁了 | ||
+ | * 同时从两个 istream_iterator 读取同一段 stream | ||
+ | 这两种情况下需要考虑该机制带来的影响。 | ||
+ | ==ostream_iterator 的操作== | ||
+ | ostream_iterator 可以绑定任意支持 ''<<'' | ||
+ | <code cpp> | ||
+ | ostream_iterator< | ||
+ | for (auto e : vec) | ||
+ | *out_iter++ = e; | ||
+ | cout << endl; | ||
+ | </ | ||
+ | 当每次对 ostream_iterator 赋值时,写操作就会发生一次,同时还会附带上额外的空格字符。需要注意的是,由于解引用与自增对 ostream_iterator 无效,上述循环可以写成: | ||
+ | <code cpp> | ||
+ | for (auto e : vec) | ||
+ | out_iter = e; | ||
+ | cout << endl; | ||
+ | </ | ||
+ | 但为了保证对迭代器使用的一致性,加上解引用与自增可以使代码的表意更加清楚。\\ \\ | ||
+ | 我们也可以使用 //copy// 算法来取代上述操作: | ||
+ | <code cpp> | ||
+ | copy(vec.begin(), | ||
+ | cout << endl; | ||
+ | </ | ||
+ | ==stream 迭代器与 class type(实例)=== | ||
+ | 通过使用 stream 迭代器进行输入、输出与循环,之前 // | ||
+ | <code cpp> | ||
+ | istream_iterator< | ||
+ | ostream_iterator< | ||
+ | |||
+ | //init the first book | ||
+ | Sales_item total = *item_iter; | ||
+ | //to the next input | ||
+ | ++item_iter; | ||
+ | |||
+ | while(item_iter != eof) { | ||
+ | //current book has the same isbn | ||
+ | if(item_iter-> | ||
+ | // | ||
+ | total += *item_iter; | ||
+ | //to the next input | ||
+ | ++item_iter; | ||
+ | else { | ||
+ | //print total for the current book | ||
+ | out_iter = total; | ||
+ | //start with the new book | ||
+ | total = *item_iter; | ||
+ | //to the next input | ||
+ | ++item_iter; | ||
+ | } | ||
+ | } | ||
+ | //print the last book | ||
+ | out_iter = total; | ||
+ | </ | ||
+ | 几个要点: | ||
+ | * 通过对 '' | ||
+ | * 通过对 '' | ||
+ | * 流迭代器也可以调用成员 | ||
+ | * '' | ||
+ | ===反向迭代器=== | ||
+ | 反向迭代器(// | ||
+ | 除了 // | ||
+ | \\ \\ < | ||
+ | ==反向迭代器与其他迭代器的关系== | ||
+ | 反向迭代器与一般迭代器的遍历过程正好相反。利用这一点可以方便我们进行做一些对容器末尾的操作。比如我们有一个 string '' | ||
+ | <code cpp> | ||
+ | // find the first element in a comma-separated list | ||
+ | auto comma = find(line.cbegin(), | ||
+ | cout << string(line.cbegin(), | ||
+ | </ | ||
+ | 如果想直接找最后一个被逗号隔开的 word, 则可以利用反向迭代器: | ||
+ | <code cpp> | ||
+ | auto rcomma = find(line.rcbegin(), | ||
+ | </ | ||
+ | 不过如果对其输出,返现这个 word 是反着输出的,比如 | ||
+ | <code cpp> | ||
+ | //input | ||
+ | FIRST, MIDDLE, LAST | ||
+ | //output | ||
+ | TSAL | ||
+ | </ | ||
+ | 这是因为使用反向迭代器遍历容器时,是从右往左的。因此打印也是从右往左打印。不仅如此,由于 '' | ||
+ | <code cpp> | ||
+ | cout << | ||
+ | </ | ||
+ | 同时需要注意的是,如果使用反向迭代器代表范围,那么该范围是一个**左闭右开**的区间,比如 $[crbegin(), | ||
+ | \\ \\ \\ | ||
+ | < | ||
+ | ====泛型算法的结构==== | ||
+ | 根据算法要求的不同,迭代器可以分为如下五大类: | ||
+ | \\ \\ < | ||
+ | 这五种类别的迭代器分别具有不同的读 / 写 / 遍历 / 随机访问属性。 | ||
+ | ===迭代器的五个类别=== | ||
+ | < | ||
+ | 迭代器按照其自身允许的操作来分类的。**高等级的迭代器支持低等级迭代器的所有操作**。 | ||
+ | \\ \\ < | ||
+ | \\ \\ <color # | ||
+ | C++ 标准中指明了了每个算法对迭代器等级的最低要求。如果迭代器不满足该要求,则无法使用。比如 //find()// 算法对序列进行扫描和读取,因此至少要 //input iterator// 才能满足需求。\\ \\ | ||
+ | 但需要注意的是,很多编译器并不会对不满足要求的迭代器参数提出警告,因此使用之前一定要检查该参数是否满足要求。 | ||
+ | ==输入迭代器== | ||
+ | 输入迭代器(// | ||
+ | * 比较**两个迭代器**相等性的操作('' | ||
+ | * **向前移动迭代器**的操作('' | ||
+ | * 迭代器**解应用**操作('' | ||
+ | * 迭代器访问成员的操作('' | ||
+ | 输入迭代器只能用于**顺序访问**。同时,输入迭代器只能**保证当前位置是有效**,因此当输入迭代器自增以后,其他指向同位置输入迭代器的有效性也不会再被保证,也就是 '' | ||
+ | <WRAP center round tip 100%> | ||
+ | stream 是个很好的例子。对读取下一部分的 stream 内容会使之前部分的内容失效。如果我们通过某个迭代器控制读取,那么当该迭代器移位,其他所有的指向之前内容的迭代器都会失效。 | ||
+ | </ | ||
+ | ==输出迭代器== | ||
+ | 输出迭代器(// | ||
+ | ==前向迭代器== | ||
+ | 前向迭代器(// | ||
+ | 除此之外,前向迭代器支持 // | ||
+ | <code cpp> | ||
+ | template< | ||
+ | void replace(ForwardIt first, ForwardIt last, | ||
+ | const T& old_value, const T& new_value) | ||
+ | { | ||
+ | for (; first != last; ++first) { | ||
+ | if (*first == old_value) { //read ,1st r/w to iter " | ||
+ | *first = new_value; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | 可以看出 // | ||
+ | 其他常见的前向迭代器:// | ||
+ | ==双向迭代器== | ||
+ | 双向迭代器(// | ||
+ | ==随机访问迭代器== | ||
+ | 随机访问迭代器(// | ||
+ | * 两个迭代器之前的**关系运算操作**(//<, | ||
+ | * 单个迭代器的**移位**操作(// | ||
+ | * 两个迭代器的相减('' | ||
+ | * 下标操作(iter[n]),等同于移位再取对应元素的操作('' | ||
+ | 常见使用随机访问迭代器的例子有:// | ||
+ | ===算法形参的形式=== | ||
+ | 除开迭代器的分类以外,算法的 parameter 的形式也有助于算法的理解。通常情况下算法有如下四种形式的参数分布: | ||
+ | <code cpp> | ||
+ | alg(beg, end, other args); | ||
+ | alg(beg, end, dest, other args); | ||
+ | alg(beg, end, beg2, other args); | ||
+ | alg(beg, end, beg2, end2, other args); | ||
+ | </ | ||
+ | 一般的来说, '' | ||
+ | ==使用 dest 参数的算法== | ||
+ | '' | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | 采用 '' | ||
+ | ==使用第二个输入源的算法== | ||
+ | 有两种不同的形式用于指定第二个输入源: | ||
+ | * '' | ||
+ | * '' | ||
+ | ===算法的命名规范=== | ||
+ | 除开参数的规范,算法还遵循一系列命名和重载的规范。这些规范用于处理一些问题: | ||
+ | * 如何替换默认的关系运算符 ''<'', | ||
+ | * 决定算法结果写出的位置是其自身的输入范围,或是指定的 '' | ||
+ | ==使用重载版本传递谓词的算法== | ||
+ | 拥有重载版本的算法有如下的两个特点: | ||
+ | * 使用额外的谓词参数来代替 ''<'' | ||
+ | * 除此之外并没有额外的参数 | ||
+ | 比如: | ||
+ | <code cpp> | ||
+ | unique(beg, end); // uses the == operator to compare the elements | ||
+ | unique(beg, end, comp); // uses comp to compare the elements | ||
+ | </ | ||
+ | ==具有 if 版本的算法== | ||
+ | 只接收**单个元素值**的算法通常都会有一个 '' | ||
+ | <code cpp> | ||
+ | find(beg, end, val); // find the first instance of val in the input range | ||
+ | find_if(beg, | ||
+ | </ | ||
+ | 提供 '' | ||
+ | ==具有拷贝 / 不拷贝两种版本的算法== | ||
+ | 这种算法多半存在于排序算法中。默认情况下,排序算法会将排序结果重新写入输入的序列中。不过这些算法往往会提供第二个版本,允许算法将结果写入到指定的位置。第二个版本往往以 '' | ||
+ | <code cpp> | ||
+ | reverse(beg, | ||
+ | reverse_copy(beg, | ||
+ | </ | ||
+ | 某些版本还会基于 '' | ||
+ | <code cpp> | ||
+ | // removes the odd elements from v1 | ||
+ | remove_if(v1.begin(), | ||
+ | [](int i) { return i % 2; }); | ||
+ | // copies only the even elements from v1 into v2; v1 is unchanged | ||
+ | remove_copy_if(v1.begin(), | ||
+ | [](int i) { return i % 2; }); | ||
+ | </ | ||
+ | ====容器专属的算法==== | ||
+ | //List// 与 // | ||
+ | 除此之外,上述成员算法的泛型版本可以用于 //List// 类型,但代价太大。这是由于交换元素的方式不同:泛型版本需要通过输入序列来交换元素,而 //List// 只需要交换元素之间的**链接**即可完成元素的交换。因此,上述算法的专属版本**效率要高出很多**。\\ \\ | ||
+ | //List// 专属的成员算法如下表所示。对于没有在列表中出现的算法,只要其需求的迭代器与 //List// 提**供的迭代器能正确匹配**,那么就不会存在效率上的差异。 | ||
+ | \\ \\ < | ||
+ | ==splice 成员== | ||
+ | //List// 与 // | ||
+ | \\ \\ < | ||
+ | 需要注意的是,在调用 // | ||
+ | <code cpp> | ||
+ | flst.splice_after(flst.begin(), | ||
+ | </ | ||
+ | 而不能使用 | ||
+ | <code cpp> | ||
+ | flst.splice_after(flst.begin(), | ||
+ | </ | ||
+ | 第二种方法会丢失 '' | ||
+ | ==list 的成员算法会改变容器== | ||
+ | 需要注意的是,即便 list 的成员算法与其泛型版本在功能上没有多大的区别,但一个重要的不同是 list 的成员算法会**修改对应的容器**。比如 // | ||
+ | 同样,// |