• 【c++ primer 笔记】第10章 泛型算法


    在这里插入图片描述

    🎉作者简介:👓 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢 c + + , g o , p y t h o n , 目前熟悉 c + + , g o 语言,数据库,网络编程,了解分布式等相关内容 \textcolor{orange}{博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++,go语言,数据库,网络编程,了解分布式等相关内容} 博主在读机器人研究生,目前研一。对计算机后端感兴趣,喜欢c++,go,python,目前熟悉c++go语言,数据库,网络编程,了解分布式等相关内容
    📃 个人主页: \textcolor{gray}{个人主页:} 个人主页: 小呆鸟_coding
    🔎 支持 : \textcolor{gray}{支持:} 支持: 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦 \textcolor{green}{如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦} 如果觉得博主的文章还不错或者您用得到的话,可以免费的关注一下博主,如果三连收藏支持就更好啦👍 就是给予我最大的支持! \textcolor{green}{就是给予我最大的支持!} 就是给予我最大的支持!🎁
    💛本文摘要💛

    本专栏主要是对c++ primer这本圣经的总结,以及每章的相关笔记。目前正在复习这本书。同时希望能够帮助大家一起,学完这本书。 本文主要讲解第10章 泛型算法

    ♦️第10章 泛型算法

    • 泛型算法是提供一个算法,对于不同类型的容器和不同类型的元素。因此叫做泛化。

    🐬10.1 概述

    • 大多数算法定义在头文件 algorithm 中,部分在 numeric 中
    • 这些算法不直接操作容器,而是操作迭代器
    • 算法不会改变容器的大小。永远不会执行容器操作
    • 大多数算法是通过遍历两个迭代器标记的一段元素来实现其功能。
    • 算法永远不会改变底层容器的大小。算法可能改变容器中保存的元素的值,也可能在容器内移动元素,但不能直接添加或者删除元素。

    🐬10.2 初始泛型算法

    • 标准库算法都对一个范围内的元素进行操作,此元素范围称为“输入范围”
    • 理解算法的最基本的方法是了解它们是否读取元素、改变元素、重排元素顺序。
    • 部分算法从两个序列中读取元素,两个序列不必相同(如vector和list),序列中的元素也不必相同(如double和int),要求是只要能比较两种元素即可。

    俩种假设

    1. 当算法只接受三个迭代器时,前俩个迭代器表示第一个序列范围,第三个迭代器表示第二个序列元素范围。这种情况假定第二个序列至少与第一个一样长。
    2. 向目的位置迭代器写入 n 个数据的算法假定目的位置足够大(大于等于 n)

    🐾10.2.1 只读算法

    • 对于只读而不改变元素的算法,通常使用cbegin()cend()
    //对输入范围内的元素在 val 的基础上求和。可以对字符串“+”。注意元素是加在val上,如果元素是double,val是int,和会被转换成int
    int sum = accumulate(vec.cbegin(),vec.cend(),val);
    
    //比较两个序列的元素是否全部相等(假定第二个序列至少与第一个序列一样长
    bool F = equal(vec1.cbegin(),vec1,end(),vec2.cbegin());
    
    //查找符合某条件的元素,返回迭代器,如果没有就返回尾迭代器
    auto iter = find_if(vec.begin(),vec.end(),'一元谓词');
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    🐾10.2.2 写容器元素算法

    • 写元素算法,必须注意确保原序列大小至少不小于要求算法写入元素的数目。
    fill(vec.begin(),vec.end(),val);//将 val 赋予输入序列中的每一个元素
    fill(vec.begin(),vec.begin() + vec.size()/2,10);//将一个容器的前一半的值写为10
    
    fill_n(dest,n,val);//将 val 赋予从 dest 开始的连续 n 个元素。假定dest开始的序列有至少 n 个元素
    fill_n(vec.begin(),vec.size(),0);//将 vec 的所有元素重置为0
    
    for_each(vec.begin(),vec.end(),'可调用对象');
    //对输入范围内的每一个元素执行可调用对象的内容。注意:可调用对象是一元谓词,参数类型是迭代器指向的类型
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    插入迭代器

    • 是一种向容器中添加元素的迭代器。
    • 函数 back_inserter 定义在头文件iterator中。
    • back_inserter接收一个指向容器的引用,返回一个与该容器绑定的插入迭代器。
    vector<int> vec;
    auto it = back_inserter(vec);//it 即为vec新加的插入迭代器
    *it = 24;//给 it 赋值后,vec 现在有一个元素为 24
    
    • 1
    • 2
    • 3

    back_inserter() 函数的返回值作为算法的目的位置

    fill_n(back_inserter(vec),10,0);//添加 10 个元素到 vec
    
    • 1

    拷贝算法

    • 输入:前两个参数指定输入范围,第三个指向目标序列。
    • copy时必须保证目标目的序列至少要包含与输入序列一样多的元素。
    auto ret = copy(begin(a1),end(a1),begin(a2));//把数组 a1 的内容拷贝给 a2,返回的是目的位置迭代器(递增后)的值
    
    replace(ilst.begin(),ilst.end(),0,42);//把输入范围内所有值为 0 的元素改为 42
    replace_copy(ilst.begin(),ilst.end(),back_inserter(ivec),0,42);//把输入范围内所有值为 0 的元素改为 42 并拷给 dest 开头的序列。
    
    • 1
    • 2
    • 3
    • 4
    • replace:只是不保留原序列,将原序列的值全部换成新的
    • replace_copy:保留原序列,ivec包含ilst的一份拷贝,原来的ilst中的值为0的元素在ivec中都变为42.

    🐾10.2.3 重排容器元素的算法

    //默认按字典序从小到达排列输入范围内的元素。重复的元素会相邻
    sort(words.begin(),words.end());
    
    //将输入范围内的元素重排,将重复的元素里不是第一次出现的元素都放到序列的后端。返回指向序列后端重复元素中的第一个重复元素的迭代器。
    auto end_unique = unique(words.begin(),words.end());
    
    //删除重复的元素。erase()函数可以接受两个相等的迭代器作为参数
    words.erase(end_unique,words.end());
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    🐬10.3 定制操作

    • 默认情况下,这类算法使用元素类型的<或=运算符完成比较。但现在允许我们自定义操作来代替默认运算符

    🐾10.3.1 向算法传递函数

    • 谓词是一个可调用的表达式,返回结果是一个可以作为条件的值,如返回一个 bool 值。
    • 谓词分为一元谓词二元谓词,分别接收一个参数俩个参数
    • 元素类型必须能转换为谓词参数的类型接收谓词参数的算法对输入序列中的元素调用谓词
    'sort'
    bool isShorter(const string &s1, const string &s2)
    {
    	return s1.size() < s2.size();
    }
    sort(words.begin(), words.end(), isShorter);
    
    'stable_sort 稳定排序'
    //会按照元素长度大小排序,而长度相同的单词会保持字典序
    stable_sort(words.begin(), words.end(), isShort);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    🐾10.3.2 lambda表达式

    • 不止一元谓词和二元谓词,我们希望操作可以接受更多的参数。(不止上面sort接收的二元。)
    • 可以向一个算法传递任何类别的可调用对象。
    • 四种可调用对象:函数、函数指针、重载了函数调用运算符的类、lambda表达式

    形式:

    [capture list](parameter list) -> return type {function body}
    • 1
    • capture list捕获列表是一个lambda所在函数定义的局部变量的列表(通常为空)。不可忽略。
    • return type是返回类型。可忽略。
    • parameter是参数列表。可忽略。
    • function body是函数体。不可忽略。

    注意

    • 与函数不同,lambda可能定义在函数内部
    • lambda必须使用尾置返回来指定返回类型
    • 捕获列表的内容为 lambda所在函数中定义的局部变量(直接写局部变量的名字即可,通常为空)。捕获列表只用于局部非 static 变量。
    • 参数列表和返回类型都可以省略。如果忽略返回类型,则根据 return 语句推断返回类型(此时函数体必须只有 return 语句)。
    • lambda 没有默认参数。
    • lambda表达式的调用方式也是使用调用运算符,内含实参。
     auto f = [] {return 42;}
    
    • 1

    例子

    • find_if
      
      • 1
      • 接受一对表示范围的迭代器和一个谓词,用来查找第一个满足特定要求的元素。返回第一个使谓词返回非0值的元素。
      • auto wc = find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >= sz;});
    • for_each
      
      • 1
      • 接受一个可调用对象,并对序列中每个元素调用此对象。
      • for_each(wc, words.end(), [](const string &s){cout << s << " ";})

    🐾10.3.3 lambda捕获和返回

    • 当向一个函数传递一个 lambda 时,同时定义lambda时会生成一个新的类类型和该类型的一个对象。(传递的参数实际上就是此编译器生成的类类型的未命名对象。)
    auto f = [ ]{ return 42; } 实际上定义了一个类类型的对象。
    
    • 1
    • 默认情况下,从 lambda 生成的类都包含一个对应该 lambda 所捕获的变量的数据成员。
    • lambda 捕获变量的方式分为值捕获引用捕获,类似参数传递。

    值捕获

    • 值捕获的前提是变量可以拷贝
    • 被值捕获的变量的值是在 lambda 创建时拷贝,而非调用时拷贝。因此在 lambda 创建后改变被捕获的变量不会影响 lambda 中对应的值。
    void func()
    {
    	size_t v1 = 42            //局部变量
    	auto f = [v1]{return v1;};//将v1拷贝到名为f的可调用对象
    	v1 = 0;
    	auto j = f();	      //j为42;f保存了创建它时v1的拷贝
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 不能拷贝ostream对象,因此想要捕获os对象唯一方法就是捕获其引用(或指向os的指针)

    引用捕获

    • 注意采用引用捕获必须确保被引用的对象在 lambda 执行时是存在的。
    • lambda 捕获的都是上层函数的局部变量,在函数结束后就都不复存在了。
    • 引用捕获可以用于捕获变量是 IO 类型时。
    void func()
    {
    	size_t v1 = 42            //局部变量
    	auto f2 = [&v1]{return v1;};//将v1拷贝到名为f的可调用对象
    	v1 = 0;
    	auto j = f2();	      //j为0;f2保存了v1的引用,而非拷贝
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    隐式捕获

    • 编译器会根据 lambda 中的代码来推断要使用的变量。
    • 让编译器推断捕获列表,在捕获列表中写一个&(引用方式)=(值方式)auto f3 = [=] {return v1;}
    wc = find_if(words.begin(), words.end(), [=](const string& s){ return s.size()>=sz; });
    
    • 1

    混合显示捕获与隐式捕获

    • 对一部分采用值捕获,另一部分采用隐式捕获
    • 采用混合捕获,捕获列表中的第一个元素必须是一个&或=
      1. 如果是 &,表示采用隐式的引用捕获,此时后面显式捕获的变量必须都采用值捕获。
      2. 如果是 =,表示采用隐式的值捕获,此时后面显式捕获的变量必须都采用引用捕获。
    for_each(words.begin(), words.end(), [&,c](const string& s){ os<<s<<c; });   // 这里的 os 是通过隐式的引用捕获得到的。
    for_each(words.begin(), words.end(), [=, &os](cosnt string& s){ os<<s<<c;}); // 这里的 c 是通过隐式的值捕获得到的。
    
    • 1
    • 2

    lambda捕获列表

    [],[参数序列]             // 空列表与显式捕获
    [=],[&]                  // 隐式值捕获与隐式引用捕获
    [=,参数列表],[&,参数列表] // 混合捕获
    
    • 1
    • 2
    • 3

    可变lambda

    • 默认情况对于一个值拷贝的变量,lambda不会改变其值。如果希望改变一个被捕获变量的值,可以在参数列表首加上关键字muitable
    • 引用捕获的变量是否可以修改依赖于引用指向的是 const 还是非 const 类型。
    void func()
    {
    	size_t v1 = 42            //局部变量
        //f可以改变他所捕获的变量的值
    	auto f = [v1]() mutable{return ++v1;};
    	v1 = 0;
    	auto j = f();	      //j为43
    }
    
    
    void func()
    {
    	size_t v1 = 42            //局部变量
        //v1是一个非const变量的引用
        //可以通过v2中的引用改变它
    	auto f = [&v1]() mutable{return ++v1;};
    	v1 = 0;
    	auto j = f();	      //j为1
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    指定lambda返回类型

    • lambda 中只包含一个单一的 return 语句,可以省略返回类型。
    • lambda 中除了 return 还有其他语句,此时应该指明返回类型。
    • lambda定义返回类型时,必须使用尾置返回类型

    🐾10.3.4 参数绑定

    • lambda表达式更适合在一两个地方使用的简单操作。如果是很多地方使用相同的操作,还是需要定义函数。
    • 对于捕获列表为空的 lambda,通常可以用函数来代替。对于捕获列表不为空的 lambda,不容易使用函数替换。

    标准库bind函数

    • 定义在头文件functional中,可以看做为一个通用的函数适配器。
    • bind 函数接受一个可调用对象,生成一个新的调用对象来“适应”原对象的参数列表。
    auto newCallable = bind(allable, arg_list);
    
    • 1
    • newCallable本身是一个可调用对象。
    • arg_list是一个逗号分隔的参数列表,对应给定的callable的参数。
    • 我们再调用newCallable的时候,newCallable会调用callable并传递给它arg_list中的参数。

    在这里插入图片描述
    bind占位符

    • _1,_2,_n 等占位符分别表示新可调用对象的第 1,2,n 个参数,将 _n 放在 bind 中不同的参数位置,表示将新可调用对象的第 n 个参数和旧可调用对象在该位置的参数绑定在了一起。
    • _1,_2 等定义在命名空间placeholders中,placeholders 这个名字定义在 std 中。placeholders 的实现定义在 functional 头文件中。
    using namespace std::placeholders::_1;//使用 _1,_2 前要先声明使用命名空间 placeholders。
    
    • 1

    bind的参数

    • bind绑定的主要功能有俩个
      • 可以减少参数数目。(减少掉的参数被设为一个固定值)
      • 改变参数的顺序
    auto g = bind(f, a, b, _2, c, _1); // g 只有两个参数,两个参数分别传递给 f 的第 5 个和第 3 个参数
    
    调用g(X, Y),会调用f(a, b, Y, c, X);
    
    • 1
    • 2
    • 3

    将 bind 用于算法的谓词

    sort(words.beegin(), words.end(), isShorter); //长度由短到长排序
    sort(words.begin(), word.end(), isShorter(, _2, _1));//长度由长到短排序
    
    • 1
    • 2

    绑定引用参数

    • 默认情况下,bind 的那些不是占位符的参数是值传递,被拷贝到 bind 返回的可调用对象中。
    • 有时需要像lambda一样需要传递引用或者参数类型不能进行拷贝(如os),此时需要使用ref函数cref函数
    [&os, c](const string &s){os << s << c;}; //正确
    bind(print, os, _1, ' ');       // 错误,os 不能拷贝
    bind(print, ref(os), _1, ' ');   // 正确
    
    • 1
    • 2
    • 3

    🐬10.4 再探迭代器

    • 除了为每个容器定义的迭代器之外,标准库在头文件iterator中还定义了额外几种迭代器。
    1. 插入迭代器绑定到容器上,可以来向容器插入元素。
    2. 流迭代器绑定到输入或输出流上,可以用来遍历所关联的 IO 流。
    3. 反向迭代器这些迭代器向后移动,不能向前移动除了 forward_list 外的标准库容器都有反向迭代器。
    4. 移动迭代器:移动迭代器不拷贝其中的元素,而是移动他们。

    🐾10.4.1 插入迭代器

    • 插入器是一种迭代器适配器,接受一个容器,生成一个迭代器,能实现向给定容器添加元素。

    注意

    • back_inserter 是插入器,back_insert_iterator> 是插入迭代器类型。back_inserter(v) 返回绑定到容器 v 的 back_insert_iterator,并实现其自增。

    插入迭代器的三种类型

    • back_inserter:创建一个使用push_back的迭代器。
    • front_inserter:创建一个使用push_front的迭代器。
    • inserter:创建一个使用insert的迭代器。接受第二个参数,即一个指向给定容器的迭代器,元素会被查到迭代器所指向的元素之前。
    操作解释
    it=tit指定的当前位置插入值t。假定cit绑定的容器,依赖于插入迭代器的不同种类,此赋值会分别调用c.push_back(t)c.push_front(t)c.insert(t, p),其中p是传递给inserter的迭代器位置
    *it, ++it, it++这些操作虽然存在,但不会对it做任何事情,每个操作都返回it

    插入迭代器的定义

    • 有两种方式,一种是直接定义,另一种是通过插入器生成
    vector<int> v;
    '使用插入器生成'
    auto bIn = back_inserter(v);  // bIn 是一个绑定到 v 的插入迭代器。
    
    '直接定义'
    back_insert_iterator<vector<int>> bIn(v);        // bIn 是一个绑定到 v 的插入迭代器。
    insert_iterator<vector<int>> iIn(v, v.begin());  // iIn 是一个绑定到 v 的插入迭代器
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    插入迭代器的操作

    • 插入迭代器操作只有一种,就是赋值操作。
    • 当通过插入迭代器赋值时,插入迭代器调用容器操作来向给定容器的指定位置插入一个元素。
    *bIn = 30;//在 bIn 所绑定的容器的尾元素之后插入一个 30
    *iIn = 10;//在 iIn 所绑定的容器的相应元素之前插入一个 10
    
    • 1
    • 2

    插入器 inserter 与函数 insert 的不同

    • 插入迭代器是恒定指向同一个元素的。而 insert 返回的是指向所插入元素的迭代器
    '1'
    auto f1 = inserter(v, v.begin());
    *f1 = 10;
    
    '2'
    iter = v.insert(v.begin(), 10);
    ++iter;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    插入迭代器的使用

    vector<int> v1,v2;
    *back_inserter(v1) = 2;    // 在 v1 的尾元素之后插入一个 2
    *inserter(v1, v1.begin()) = 6; // 在 v1 的首元素之前插入一个 6
    copy(v1.begin(),v1.end(),back_inserter(v2)); // 将 v1 的所有元素按顺序拷贝到 v2 的尾元素之后。
    copy(v1.begin(),v1.end(),inserter(v2,v2.begin())); // 将 v1 的所有元素按顺序拷贝到 v2的首元素之前
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🐾10.4.2 iostream迭代器

    虽然iostream类型不是迭代器,但定义了可以使用这些IO类型对象的迭代器。iostream有俩类

    1. istream_iterator:读取输入流。
    2. ostream_iterator:向输出流写数据。

    流迭代器作用

    • 迭代器可与输入或输出流绑定在一起,用于迭代遍历所关联的 IO 流。
    • 通过使用流迭代器,我们可以用泛型算法从流对象中读取数据以及向其写入数据。创建流迭代器时,必须指定迭代器将要读写的对象类型。
    • 流迭代器可以绑定到 iostream,fstream,stringstream。它将对应的流当作一个元素序列来处理。
    • 可以为任何具有输入运算符的(>>)类型定义istream_iterator,即所有内置类型和重置了 >> 的类。

    创建流迭代器
    1. 绑定到一个流
    2. 默认初始化:采用这种方式创建的迭代器可以当作尾后值使用,可以在 for 循环中作为终止条件。

    istream_iterator<int> int_it(cin);//创建了一个流迭代器 iInt,iInt 从 cin 读取 int
    istream_iterator<int> int_eof;//尾后迭代器。当关联的流遇到文件尾或遇到错误,迭代器的值就与尾后迭代器相等。
    while(in_it != eof)
    {
    	vec.push_back(*in_iter ++);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    istream_iterator使用

    • 使用算法操作流迭代器
    istream_iterator<T> in(is); //in从输入流is读取类型为T的值
    istream_iterator<T> end;    //读取类型是T的值的istream_iterator迭代器,表示尾后位置
    in1 == in2; in1 != int2;    //首先 in1 和 in2 必须读取相同类型。如果 in1 和 in2 都是尾后迭代器或绑定到相同的输入,则两者相等。
    *in;                        //返回从流中读取的值
    ++in;in++;                 //递增操作
    
    • 1
    • 2
    • 3
    • 4
    • 5

    '1'
    istream_iterator<int> inInt(cin),eof;
    while(inInt != eof)
        vec.push_back(*inInt++);
        
    '2'
    vector<int> vec(inInt,eof);//和上面效果相同,当遇到文件尾或遇到第一个不是 int 的数据停止。   
    
    '3'
    istream_iterator<int> in(cin), eof;
    cout << accumulate(in, eof, 0) << endl; 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    ostream_iterator 定义

    • 可以为任何具有输出运算符的(<<)类型定义 ostream_iterator,即所有内置类型和重置了 << 的类。
    ostream_iterator<int> out(os);//out 将类型为 int 的值写到输出流 os 中
    ostream_iterator<int> out(os,str);//out 将类型为 int 的值写到 os 中,每个值后面都跟着一个C风格字符串 str。(字符数组)
    
    • 1
    • 2

    ostream_iterator操作

    • ostream_iterator 的赋值操作等价于输出流的输出操作。每赋一次值,输出一次。
    ostream_iterator<T> out(os);     //out将类型为T的值写到输出流os中
    ostream_iterator<T> out(os, d);  //out将类型为T的值写到输出流os中,每个值后面都输出一个d。d指向一个空字符结尾的字符数组。
    out = 6;                        //等价于 os<<6。将 6 写入到 out 绑定的输出流中。
    *out; ++out; out++;             //这些运算符存在但没有意义。
    
    • 1
    • 2
    • 3
    • 4

    ostream_iterator 的使用

    for(auto e:vec)
        out = e;   //赋值语句将元素写到 cout,每赋一次值,写操作就会被提交一次。
        
     '* 和 ++可以省略'   
    for(auto e:vec) 
    	*out++ = e;//与上面的语句实际效果相同,看起来更清晰。 
    
    '使用 copy 来打印 vec 中的元素,更为简单。'
    copy(vec.begin(),vec.end(),out);//将 vec 中的元素写入到了 out 绑定的输出流中   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    🐾10.4.3 反向迭代器

    • 反向迭代器就是在容器中从尾元素向首元素反向移动的迭代器。
    • 对于反向迭代器,递增和递减的操作含义会颠倒
    • 除了 forward_list 外,其他容器都支持反向迭代器。
    • 反向迭代器支持递增和递减操作。流迭代器不支持递减操作。
    • 实现向后遍历,配合rbeginrend

    函数 riter.base() 返回相应的正向迭代器,正向迭代器指向靠后一个元素。

    auto riter = string.rbegin()//反向迭代器 riter 指向 string 的尾元素
    auto iter = riter.base();//正向迭代器 iter 指向 string 的尾后元素
    
    • 1
    • 2

    🐬10.5 泛型算法结构

    🐾10.5.1 5类迭代器

    迭代器类别解释支持的操作
    输入迭代器只读,不写;单遍扫描,只能递增 ==,!=,++,*,->
    输出迭代器只写,不读;单遍扫描,只能递增++,*
    前向迭代器可读写;多遍扫描,只能递增 ==,!=,++,*,->
    双向迭代器可读写;多遍扫描,可递增递减==,!=,++,--,*,->
    随机访问迭代器可读写,多遍扫描,支持全部迭代器运算 ==,!=,<,<=,>,>=,++,--,+,+=,-,-=,*,->,iter[n]==*(iter[n])

    输入迭代器

    输入迭代器只用于顺序访问,只能用于单遍扫描算法,如算法 find 和 accumulate。

    Istream_iterator 是一种输入迭代器。

    输出迭代器

    只能向一个输出迭代器赋值一次,只能用于顺序访问的单遍扫描算法。如 copy 函数的第三个参数。

    ostream_iterator 是一种输出迭代器。

    前向迭代器

    可以读写元素。只能在序列中沿一个方向移动,可以多次读写同一个元素。

    可以保存前向迭代器的状态,使用前向迭代器的算法可以对序列多遍扫描。

    算法 replace 要求前向迭代器,forward_list 的迭代器是前向迭代器。

    双向迭代器

    可以正向/反向读写序列中的元素。支持递增递减运算符。

    算法 reverse 要求双向迭代器。list 的迭代器是双向迭代器。

    随机访问迭代器

    提供在常量时间内访问序列中任意元素的能力。

    支持以下操作:

    1. 支持 <, <=, >, >= 等关系运算符。
    2. 支持迭代器与整数的加减。
    3. 支持两个迭代器之间的相减。
    4. 支持下标运算符。iter[n] 和 *(iter[n]) 含义相同。

    算法 sort 要求随机访问迭代器。array, deque, vector, string 的迭代器是随机访问迭代器。

    🐾10.5.2 算法的形参模式

    大多数算法一般有以下几种形式

    • alg(beg, end, other args);
    • alg(beg, end, dest, other args);
    • alg(beg, end, beg2, other args);
    • alg(beg, end, beg2, end2, other args);

    其中,alg是算法名称,beg和end表示算法所操作的输入范围。dest、beg2、end2都是迭代器参数,是否使用要依赖于执行的操作。

    dest 经常是一个插入迭代器或一个 ostream_iterator,他们都能确保不管写多少元素都肯定是安全的。

    🐾10.5.3 算法命名规范

    • 算法遵循一同命名和重载规范。
    • 一些算法使用重载形式传递一个谓词,来代替 < 或 ==。
    unique(beg, end);          // 使用 == 比较元素
    unique(beg, end, comp);    // 使用 comp 比较元素
    
    • 1
    • 2

    _if版本算法

    • 接受一个元素值的算法通常有一个不同名的版本:加_if,接受一个谓词代替元素值。
    find(beg, end, val);       // 查找范围内 val 第一次出现的版本。
    find_if(beg, end, pred);   // 查找第一个令 pred 为真的元素。
    
    • 1
    • 2

    拷贝版本算法

    • 区分拷贝元素的版本和不拷贝的版本:拷贝版本通常加_copy。
    • 默认情况下,重排元素的算法会将重排后的元素写回给定的输入序列中。拷贝版本则将元素写到一个给定的位置。
    reverse(beg, end);             // 反转序列中的元素。
    reverse_copy(beg, end, dest);  // 将元素按逆序拷贝到 dest。
    
    remove_if(v1.begin(), v1.end(), [](int i){return i%2;});                         // 从 v1 中删除奇数元素。
    remove_copy_if(v1.begin(), v1.end(), back_inserter(v2), [](int i){return i%2;}); // 将去掉了奇数元素的 v1 序列拷到 v2 中。v1当中的元素不变
    
    • 1
    • 2
    • 3
    • 4
    • 5

    🐬10.6 特定容器算法

    • 链表类型 list 和 forward_list 定义了几个成员函数形式的算法。它们定义了独有的 sort, merge, remove, reverse 和 unique。
    • 通用版本的 sort 要求随机访问迭代器,而 list 和 forward_list 分别提供双向迭代器和前向迭代器,因此不能用于 list 和 forward_list。
    • 其他链表类型定义的算法的通用版本可以用于链表,但是性能差很多,应该优先使用成员函数版本的算法。
    lst.merge(lst2);        // 将 lst2 的元素合并入 lst。两者必须都是有序的,合并后 lst2 将变为空。使用 < 比较元素。
    lst.merge(lst2,comp);   // 上面的 merge 的重载版本,用给定的 comp 代替 <
    
    lst.remove(val);        // 调用 erase 删除掉与给定值相等的每个元素。
    lst.remove_if(pred);    // pred 是个一元谓词,删除掉 lst 中使 pred 为真的每个元素。
    
    lst.reverse();          // 反转 lst 中元素的顺序。
    
    lst.sort();             // 使用 < 给元素排序
    lst.sord(comp);         // 重载版本
    
    lst.unique();          // 调用 erase 删除同一个值的连续拷贝。
    lst.unique(pred);      // pred 是个二元谓词,删除使 pred 为真的连续拷贝。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 上面的操作都返回void

    list和forward_list的splice成员函数版本的参数:

    • 链表类型定义了 splice 算法,此算法是链表特有的,没有通用版本。
    • splice 算法用来在两个链表间移动元素或在一个链表中移动元素的位置
    lst.splice(p, lst2); flst.splice_after(p, lst2);              // p 是一个指向 lst 中元素的迭代器,或一个指向 flst 首前位置的迭代器。
            // 函数将 lst2 中的所有元素移动到 lst 中 p 之前的位置或 flst  中 p 之后的位置。
    lst.splice(p, lst2, p2); flst.splice_after(p, lst2, p2);      // p2 是一个指向 lst2 中位置的迭代器。
            // 函数将 p2 指向的元素移动到 lst 中,或将 p2 之后的元素移动到 flst 中,lst2 可以是与 lst 或 flst 相同的链表。
    lst.splice(p, lst2, b, e); flst.splice_after (p, lst2, b, e); // b 和 e 表示 lst2 中的合法范围。
            // 将给定范围中的元素从 lst2 移动到 lst 中。lst2 可以是与 lst 或 flst 相同的链表,但是 p 不能指向 b 和 e 之间的元素。   
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 使用lst.splice(args)flst.splice_after(args)
  • 相关阅读:
    支持末尾携带标签的多行TextView
    基于matlab的FP-MAP球形检测算法误码率仿真
    Flutter插件之阿里百川
    Java中高级面试题
    c++系列之string的模拟实现
    创建HTTP请求的几种方式
    基于数据库(MySQL)与缓存(Redis)实现分布式锁
    因为CMake文件出错,导致链接器报错重定义
    「分布式」——微服务抽奖系统后台整合MyBatis-Plus
    LeetCode 2172. 数组的最大与和【状压DP,记忆化搜索;最小费用最大流】2392
  • 原文地址:https://blog.csdn.net/weixin_45043334/article/details/126002739