【C++】《C++ Primer 5th》笔记-Chapter10-泛型算法

笔记:
一、概述
1、大多数算法都定义在头文件algorithm中。标准库还在头文件numeric中定义了一组数值泛型算法。
2、泛型算法本身不会执行容器的操作,它们只会运行于迭代器之上,执行迭代器的操作。泛型算法运行于迭代器之上而不会执行容器操作的特性带来了一个令人惊讶但非常必要的编程假定:算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但永远不会直接添加或删除元素。
当一个算法操作插入器时,迭代器可以完成向容器添加元素的效果,但算法自身永远不会做这样的操作。

二、初识泛型算法
1、注意""和string("")的区别,一个是const char*类型,另一个是string类型。const char*类型是没有定义+运算符的。
2、对于只读取而不改变元素的算法,通常最好使用cbegin()和cend()。但是如果你计划使用算法返回的迭代器来改变元素的值,就需要使用begin()和end()的结果作为参数。
3、那些只接受一个单一迭代器来表示第二个序列的算法,都假定第二个序列至少与第一个序列一样长。确保算法不会试图访问第二个序列中不存在的元素。
4、向目的位置迭代器写入数据的算法假定目的位置足够大,能容纳要写入的元素。
5、back_inserter接受一个指向容器的引用,返回一个与该容器绑定的插入迭代器。当我们通过此迭代器赋值时,赋值运算符会调用push_back将一个具有给定值的元素添加到容器中。
6、unique算法重排输入序列,将相邻的重复项"消除",并返回一个指向不重复值范围末尾的迭代器。
7、标准库算法对迭代器而不是容器进行操作。因此,算法不能(直接)添加或删除元素。

三、定制操作
1、谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。标准库算法所使用的谓词分为两类:一元谓词(unary predicate,意味着它们只接受单一参数)和二元谓词(binary precicate,意味着它们有两个参数)。
接受谓词参数的算法对输入序列中的元素调用谓词。因此,元素类型必须能转换为谓词的参数类型。
2、稳定排序算法stable_sort维持相等元素的原有顺序。
3、一个lambda表达式表示一个可调用的代码单元。我们可以将其理解为一个未命名的内联函数。与任何函数类似,一个lambda具有一个返回类型、一个参数列表和一个函数体。但与函数不同,lambda可能定义在函数内部。一个lambda表达式具有如下形式:
[capture list](parameter list) -> return type { function body}
其中,capture list(捕获列表)是一个lambda所在函数中定义的局部变量的列表(通常为空);return type、parameter list和function body与任何普通函数一样,分别表示返回类型、参数列表和函数体。但是,与普通函数不同,lambda必须使用尾置返回来指定返回类型。
例如:我们可以忽略参数列表和返回类型,但必须永远包含捕获列表和函数体:auto f = [] { return 42; }
lambda的调用方式与普通函数的调用方式相同,都是使用调用运算符。
空捕获列表表明此lambda不使用它所在函数中的任何局部变量。

4、如果lambda的函数体包含任何单一return语句之外的内容,且未指定返回类型,则返回void。
5、与普通函数不同,lambda不能有默认参数。因此,一个lambda调用的实参数目永远与形参数目相等。
6、虽然一个lambda可以出现在一个函数中,使用其局部变量,但它只能使用那些明确指定的变量。一个lambda通过将局部变量包含在其捕获列表中来指出将会使用这些变量。捕获列表指引lambda在其内部包含访问局部变量所需的信息。
一个lambda只有在其捕获列表中捕获一个它所在函数中的局部变量,才能在函数体中使用该变量。
7、捕获列表为空,是因为我们只对lambda所在函数中定义的(非static)变量使用捕获列表。一个lambda可以直接使用定义在当前函数之外的名字(cout是定义在头文件iostream中的)。
// 遍历并打印words中的每一个单词,且每个单词后面接一个空格.
for_each(words.begin(), words.end(),
            [](const string &s){cout << s << " ";});
捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。

8、当定义一个lambda时,编译器生成一个与lambda对应的新的(未命名的)类类型。
默认情况下,从lambda生成的类都包含一个对应该lambda所捕获的变量的数据成员。类似任何普通类的数据成员,lambda的数据成员也在lambda对象创建时被初始化。
9、类似参数传递,lambda变量的捕获方式也可以是值或引用。与传值参数类似,采用值捕获的前提是变量可以拷贝。与参数不同,被捕获的变量的值是在lambda创建时拷贝,而不是调用时拷贝。
10、一个以引用方式捕获的变量与其他任何类型的引用的行为类似。当我们在lambda函数体内使用此变量时,实际上使用的是引用所绑定的对象。
如果我们采用引用方式捕获一个变量,就必须确保被引用的对象在lambda执行的时候是存在的。
我们也可以从一个函数返回lambda。函数可以直接返回一个可调用对象,或者返回一个类对象,该类含有可调用对象的数据成员。如果函数返回一个lambda,则与函数不能返回一个局部变量的引用类似,此lambda也不能包含引用捕获。
11、捕获一个普通变量,如int、string或其他非指针类型,通常可以采用简单的值捕获方式。
如果我们捕获一个指针或迭代器,或采用引用捕获方式,就必须确保在lambda执行时,绑定到迭代器、指针或引用的对象仍然存在。而且需要保证对象具有预期的值。
12、一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且,如果可能的话,应该避免捕获指针或引用。
13、可以在捕获列表中写一个&或=来指示编译器推断捕获列表。
&告诉编译器采用捕获引用方式,=则表示采用值捕获方式。
14、当我们混合使用隐式捕获和显示捕获时,捕获列表中的第一个元素必须是一个&或=。此符号指定了默认捕获方式为引用或值。
当混合使用隐式捕获和显示捕获时,显示捕获的变量必须使用与隐式捕获不同的方式。
15、默认情况下,对于一个值被拷贝的变量,lambda不会改变其值。如果我们希望能改变一个被捕获的变量的值,就必须在参数列表首加上关键字mutable。因此,可变lambda能省略参数列表:
auto f = [v1]() mutable {return ++v1;};
另外,一个引用捕获的变量是否(如往常一样)可以修改依赖于此引用指向的是一个const类型还是一个非const类型。
16、当我们需要为一个lambda定义返回类型时,必须使用尾置返回类型。
17、对于那种只在一两个地方使用的简单操作,lambda表达式是最有用的。如果我们需要在很多地方使用相同的操作,通常应该定义一个函数,而不是多次编写相同的lambda表达式。类似的,如果一个操作需要很多语句才能完成,通常使用函数更好(也即使用bind函数来绑定函数)。
18、可以将定义在头文件functional中的bind函数看作一个通用的函数适配器,它接受一个可调用对象,生成一个新的可调用对象来"适应"原对象的参数列表。
19、标准库ref函数返回一个对象,包含给定的引用,此对象是可以拷贝的。标准库中还有一个cref函数,生成一个保存const引用的类型。与bind一样,函数ref和cref也定义在头文件functional中。

四、再探迭代器
1、除了forward_list之外的标准库容器都有反向迭代器。
2、插入器有三种类型,差异在于元素插入的位置:
back_inserter创建一个使用push_back的迭代器。
front_inserter创建一个使用push_front的迭代器。
inserter创建一个使用insert的迭代器。此函数接受第二个参数,这个参数必须是一个指向给定容器的迭代器。元素将被插入到给定迭代器所表示的元素之前。
3、只有在容器支持push_front的情况下,我们才可以使用front_inserter。类似的,只有在容器支持push_back的情况下,我们才能使用back_inserter。
4、istream_iterator读取输入流,ostream_iterator向一个输出流写数据。这些迭代器将它们对应的流当作一个特定类型的元素序列来处理。通过使用流迭代器,我们可以用泛型算法从流对象读取数据以及向其写入数据。
5、C风格字符串即,一个字符串字面常量或者一个指向以空字符结尾的字符数组的指针。
6、运算符*和++实际上对ostream_iterator对象不做任何事情,因此忽略它们对我们的程序没有任何影响。
7、可以通过向sort传递一对反向迭代器来将vector整理为递减序。
sort(vec.begin(), vec.end());    // 按"正常序"排序vec
sort(vec.rbegin(), vec.rend());    // 按逆序排序: 将最小元素放在vec的末尾
8、除了forward_list之外,标准容器上的其他迭代器都既支持递增运算又支持递减运算。但是流迭代器不支持递减运算,因为不可能在一个流中反向移动。因此,不可能从一个forward_list或一个流迭代器创建反向迭代器。
9、反向迭代器的目的是表示元素范围,而这些范围是不对称的,这导致一个重要的结果:当我们从一个普通迭代器初始化一个反向迭代器,或是给一个反向迭代器赋值时,结果迭代器与原迭代器指向的并不是相同的元素。(例如crbegin()和cend指向的不是同一个位置而是相邻位置)

五、泛型算法结构
1、输入迭代器:可以读取序列中的元素。
输入迭代器只用于顺序访问。
对于一个输入迭代器,*it++保证是有效的,但递增它可能导致所有其他指向流的迭代器失效。其结果就是,不能保证输入迭代器的状态可以保存下来并用来访问元素。因此,输入迭代器只能用于单遍扫描算法。
2、输出迭代器:可以看作输入迭代器功能上的补集——只写而不读元素。
我们只能向一个输出迭代器赋值一次。类似输入迭代器,输出迭代器只能用于单遍扫描算法。
用作目的位置的迭代器通常都是输出迭代器。
3、前向迭代器:可以读写元素。这类迭代器只能在序列中沿一个方向移动。前向迭代器支持所有输入和输出迭代器的操作,而且可以多次读写同一个元素。因此,我们可以保存前向迭代器的状态,使用前向迭代器的算法可以对序列进行多遍扫描。算法replac要求前向迭代器,forward_list上的迭代器是前向迭代器。
4、双向迭代器:可以正向/反向读写序列中的元素。除了支持所有前向迭代器的操作之外,双向迭代器还支持前置和后置递减运算符(--)。算法reverse要求双向迭代器,除了forward_list之外,其他标准库都提供符合双向迭代器要求的迭代器。
5、随机访问迭代器:提供在常量时间内的访问序列中任意元素的能力。此类迭代器支持双向迭代器的所有功能。
6、向输出迭代器写入数据的算法都假定目标空间足够容纳写入的数据。
7、接收单独beg2的算法假定从beg2开始的序列与beg和end所表示的范围至少一样大。

六、特定容器算法
1、对于list和forward_list,应该优先使用成员函数版本的算法而不是通用算法。
2、链表特有版本与通用版本间的一个至关重要的区别是链表版本会改变底层的容器。

一些术语:
1、算法从不直接改变它们所操作的序列的大小。它们会将元素从一个位置拷贝到另一个位置,但不会直接添加或删除元素。
2、虽然算法不能向序列添加元素,但插入迭代器可以做到。一个插入迭代器被绑定到一个容器上。当我们将一个容器元素类型的值赋予一个插入迭代器时,迭代器会将该值添加到容器中。
3、二元谓词:接受两个参数的谓词。
4、泛型算法:类型无关的算法。
5、谓词:返回可以转换为bool类型的值的函数。泛型算法通常用来检测元素。标准库使用的谓词是一元(接受一个参数)或二元(接受两个参数)的。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值