算法不依赖与容器(使用迭代器),但大多数依赖于元素类型。如find需要==运算符,其他算法可能要求支持<运算符。

算法永远不会执行容器的操作,永远不会改变底层容器的大小(添加或删除元素)。

accumulate(v.cbegin(), v.cend(), string(“ ”)) 算法累加运算符,第3个参数的类型决定了使用哪个+号运算符。

equal(v1.cbegin(), v1.cend(), v2.cbegin()),逐个比较两个序列。第二个序列至少与第一个序列一样长。

容器和元素类型都不必一样,只要支持==符号比较两个元素类型。

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

确保算法不会访问第二个序列中不存在的元素是程序员的责任。

算法不检查写操作面,向目的位置迭代器写入数据的算法

auto it = back_inserter(vec);//返回插入迭代器,使用它赋值时会调用push_back将元素添加到容器。

fill_n(it,10,0);  //添加10个元素到vec。

很多算法提供拷贝版本,返回的是一份拷贝,原序列并未改变。

sort(words.begin(), words.end());//排序

auto end_unique = unique(words.begin(), words.end());//去重,返回不重复区域后面一个位置

words.erase(end_unique,words.end());//算法不能直接增删,调用erase删除多余元素

即时words没有重复元素,此操作也是安全的

谓词(predicate)

可调用的表达式,返回结果是一个能用作条件的值

用谓词替代算法默认的操作,如定义二元isShorter函数,替代sort默认的<比较元素。

sort(words.begin(), words.end(), isShorter);

stable_sort(words.begin(), words.end(), isShorter);//维持相等元素的原有排序

partition将容器划分成两部分,true的在前。partition_stable 维持原有元素的顺序

可调用对象

函数、函数指针、函数对象,lambda表达式(可理解为一个未命名的内联函数)

[capture list] (parameter list) -> return type {function body}

其中参数列表和返回类型可以省略,但是捕获列表和函数体必须永远包含。

忽略参数列表等价于空参数列表

忽略返回类型,如果函数体只有一个return语句则从表达式的类型判断;如包含其他任意语句,则返回void。

lambda

不能有默认参数

lambda只有在其捕获列表中的捕获一个它所在函数的局部变量,才能再函数体中使用该变量。仅限局部非static变量,局部static和所在函数外声明的变量可以直接使用。

make_plural(count, “word”, “s”) //区分单复数,count为1则单数

find_if(words.begin(), words.end(), [sz](const string &a){return a.size() >=sz;});

for_each(wc,words.end(),[](const string &s){cout<<s<<” ”;});

排序-去重-删除多余-按长度排序-找到长度大于sz的起点,打印

值捕获

前提是变量可拷贝,被捕获的变量的值在lambda创建时拷贝,而不是调用时。

引用捕获

需要保证捕获的局部变量的有效性,也可以从函数返回lambda,不能包含局部变量的引用捕获。如果可能,尽量避免捕获指针或引用。

lambda捕获列表

[]

空捕获列表

[names]

名字列表,默认都是被拷贝

[&]

隐式捕获列表,都采用引用捕获

[=]

隐式捕获列表,都采用值捕获

[&,identifier_list]

除个别值捕获

[=, identifier_list]

除个别引用捕获

可变lambda

默认不会改变被捕获的变量的值,如希望改变,使用mutable。

对于只在一两个地方使用的简单操作,lambda表达式是最有用的。

对于在很多地方使用的相同操作,或者一个操作需要很多语句才能完成,通常定义一个函数更好。

捕获列表为空的lambda,通常可以用函数替代;

而对于有捕获变量的,就不那么容易了。会面临谓词参数个数不一致的问题。

bind函数(函数适配器)

接受一个可调用对象,生成一个新的可调用对象来适应原对象的参数列表。

auto newCallable = bind(callable, arg_list);

arg_list对应给callable的参数。其中可能包含_n形式的名字,表示占据了传递给newCallable参数的第n个位置。

bool check_size(const string& s, string::size_type sz) {return s.size() >= sz;}

auto check6 = bind(check_size, _1, 6);//

bind调用只有一个占位符,便是check6只接收一个参数,占位符出现在第一个arg_list的第一个位置,表示check6此参数对应check_size的第一个参数const string&。

auto g =bind(f, a, b, _2, c, _1);//f有5个参数,其中_1和_2分别对应g的第1个和第2个参数

g(_1,_2)映射为f(a, b, _2, c, _1)

需要包含命名空间using std::placeholders::_1;

using namespace std::placeholders;

对于不是占位符的参数,默认是拷贝到bind返回的可调用对象中的,有时候需要用引用方式传递ref(os),cref()生成const引用。

bind ref cref都在头文件functinal里。

注:bind1st和bind2nd,分别只能绑定第一个或第二个参数,由于局限性强已经在新标准中弃用。binder1st和binder2nd也类似,只不过他们是模板类需要指定op的类型

bind1st(op, value)(x)  ->  op(value,x)

bind2nd(op,value)(x)  ->  op(x,value)

其他迭代器

插入迭代器:绑定到容器上,可以向容器插入元素

流迭代器:绑定到输入输出流,可以遍历关联的IO流,只要定义了<<或>>运算符的对象

反向迭代器:向后移动,forward_list没有

移动迭代器:不是拷贝其中的元素,而是移动move它们。

插入迭代器

*it, ++it, it++ 不会对it做任何事情,返回it

back_inserter          push_back

front_inserter         push_front

inserter                      insert

如inserter生成的迭代器做如下赋值操作 *it = val;

效果相当于以下代码

it = c.insert(it, val);//it指向新加入的首元素

++it;

使用不同的插入迭代器,最后生成的序列顺序会不同。

流迭代器

创建时必须制定将要读写的对象类型,可以在创建时将其绑定到一个流上,也可以默认初始化迭代器当尾后迭代器使用。

istream_iterator<int> in_iter(cin);

istream_iterator<int> eof;

while(in_iter!=eof)

vec.push_back(*in_iter++);

可以改成成如下形式

istream_iterator<int> in_iter(cin), eof;

vector<int> vec(in_iter, eof); //从迭代器范围构造vec

从流中读取的数据来构造vec

与某些算法结合,来处理流数据。输入流迭代器可以作为源迭代器,输入作为目标迭代器

如cout<<accumulate(in, eof, 0)<<endl;

istream_iterator允许懒惰求值,推迟中流中读取数据。标准库保证在第一次解引用前,从流中读取数据的操作已经完成。

如果迭代器可能销毁,或者两个对象同步使用一个流,那么何时读取就很重要了。

ostream_iterator允许第二个参数,是C风格的字符串,在输出每个元素之后都会打印此字符串。必须绑定到一个流。

*out, ++out, out++提供形式支持,不会做任何事情

ostream_iterator<int> out_iter (cout, “ “);

for (auto e : vec)

*out_iter++ = e;

cout<<endl;

其中*和++都可以忽略,但是推荐这种写法,可以保持与其他迭代器的使用保持一致,修改容易,而且对读者来说也更为清晰。

反向迭代器

使我们可以使用算法透明地向前或向后处理容器。

需要既支持++也支持--,forward_list和流迭代器不能创建。

当使用反向迭代器,在打印的时候会反向,需要使用base成员来转换。

[line.crbegin(), rcomma)和[rcomma.base(), line.end())指向line中相同的元素范围。

另外反向迭代器表示的范围是不对称的,当从普通迭代器初始化反向迭代器或是给其赋值的时候,其指向并不是相同的。

5种迭代器类型

迭代器类别

输入迭代器

只读,不写;单遍扫描,只能递增

输出迭代器

只写,不读;单遍扫描,只能递增

前向迭代器

可读写;多遍扫描,只能递增

双向迭代器

可读写;多遍扫描,可递增递减

随机访问迭代器

可读写;多遍扫描,支持全部迭代器运算

算法指明了迭代器参数的最小类别,如传递的迭代器参数基本更低会产生错误。

输入迭代器:

至少支持==, !=, 前后置++, 解引用*(赋值的右侧), ->

*it++保证是有效的,但是递增可能导致其他迭代器失效,不能保证输入迭代器的状态可以保存下来并用来访问元素。因此只能用于单遍扫描算法。

如find和accumulate支持istream_iterator

输出迭代器:(可看做输入迭代器的补集)

至少支持前后置++, 解引用*(赋值的左侧),只能向一个输出迭代器赋值一次,单遍扫描。

如copy第3个参数,ostream_iterator

前向迭代器:

replace, forward_list

双向迭代器:(--)

reverse

随机访问迭代器:

< <= > >= + += - -= 两个迭代器相减 下标运算符

sort          array deque string vector 内置数组元素的指针

常见算法形参类型

alg(beg, end, other args);

alg(beg, end, dest, other args);

alg(beg, end, beg2, other args);

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

向输出迭代器接入数据的算法都是假定目标空间足够。

单个beg2的算法假定从beg2开始的序列至少与序列1一样大。

算法命名规范

重载传递一个谓词

_if版本 将接受一个元素值版本改成接受一个谓词版本,使用不同的函数名避免可能的重载歧义。

copy版本

默认重排元素的算法写回输入序列,copy版本写到一个指定的输出目的位置。

链表类型特有算法

其他通用算法也可以用于链表,但是代价过高,需要交换元素。链表可以通过改变链接来完成交换,而不用真正交换元素。所以优先使用成员函数版本。

list forward_list成员函数版本算法(都返回void)

lst.merge(lst2)

合并,两个都必须是有序的,合并之后lst2为空

lst.merge(lst2,comp)

同上,使用谓词给定的比较操作

lst.remove(val)

erase删除元素

lst.remove_if(pred)

同上

lst.reverse()

反转序列

lst.sort()

使用<或给定比较操作排序

lst.sort(comp)

lst.unique()

删除同一个值得连续拷贝

lst.unique(pred)

同上,使用二元谓词

splice成员

lst.splice(args)或flst.splice_after(args)

(p, lst2)  将lst2中所有元素移动到lst中p之前或flst中p之后,元素将从lst2删除,不能为同一个链表

(p, lst2, p2) 将p2指向的元素移动到lst中,可以是相同的链表

(p, lst2, b, e) 将b和e之间指向的元素移动到lst中,可以是相同的链表,可以是同链表但不能包含p。

链表特有版本一个重要的区别是会改变底层的容器。

C/C++基础----泛型算法的更多相关文章

  1. C++基础之泛型算法

    标准库并未给每个容器添加大量功能,因此,通过大量泛型算法,来弥补.这些算法大多数独立于任何特定的容器,且是通用的,可用于不同类型的容器和不同的元素. 迭代器使得算法不依赖容器,但是算法依赖于元素的类型 ...

  2. C++ 泛型算法

    <C++ Primer 4th>读书笔记 标准容器(the standard container)定义了很少的操作.标准库并没有为每种容器类型都定义实现这些操作的成员函数,而是定义了一组泛 ...

  3. Chapter10:泛型算法

    泛型算法的基础是迭代器. 迭代器令算法不依赖于容器,但是算法依赖于元素类型的操作.也即:算法永远不会执行容器的操作. 那么,如果想向容器中添加元素或者执行其他的一些操作呢?标准库提供了插入迭代器来完成 ...

  4. c++11の泛型算法

    一.泛型算法泛型算法这个概念是针对容器操作的,我们知道,c++11的顺序容器有vector,list,deque等,对于这些容器,c++11并没给出相应的增删改查方法,而是定义了一组泛型算法 一般的泛 ...

  5. c++ primer 11 泛型算法

    使用泛型算法必须包含头文件#inlucde <algorithm> 标准库还定义一组泛化的算术算法,其命名习惯与泛型算法相同,包含头文件#include <numeric> f ...

  6. Java面试宝典系列之基础排序算法

    本文就是介绍一些常见的排序算法.排序是一个非常常见的应用场景,很多时候,我们需要根据自己需要排序的数据类型,来自定义排序算法,但是,在这里,我们只介绍这些基础排序算法,包括:插入排序.选择排序.冒泡排 ...

  7. C++的那些事:容器和泛型算法

    一.顺序容器 1,标准库定义了3种类型的顺序容器:vector.list和deque.它们的差别主要在于访问元素的方式,以及添加或删除元素相关操作运算代价.标准库还提供了三种容器适配器:stack.q ...

  8. C++ Primer : 第十章 : 泛型算法 之 只读、写和排序算法

    大多数算法都定义在<algorithm>头文件里,而标准库还在头文件<numeric>里定义了一组数值泛型算法,比如accumulate. ●  find算法,算法接受一对迭代 ...

  9. 【STL】帮你复习STL泛型算法 一

    STL泛型算法 #include <iostream> #include <vector> #include <algorithm> #include <it ...

随机推荐

  1. python3.6 连接mysql数据库

    ==================pymysql=================== 由于 MySQLdb 模块还不支持 Python3.x,所以 Python3.x 如果想连接MySQL需要安装 ...

  2. Python 数据类型--字典类型

    字典 dict 字典是Python的另一种有序的可变数据结构,且可存储任意类型对象. 字典是一种键值对的数据容器,每个键值(key:value)对用冒号(:)分割,每个对之间用逗号(,)分割,整个字典 ...

  3. openssl 非对称加密DSA,RSA区别与使用介绍(转)

    openssl 非对称加密DSA,RSA区别与使用介绍(转) 博客分类: OS.Linux Security   在日常系统管理工作中,需要作一些加解密的工作,通过openssl工具包就能完成我们很多 ...

  4. php property_exists

    property_exists("Device",$prop))判断Device 类中是否存在 $prop 这个属性该函数用来判断一个类中是否存在某个属性. 这里分析了php面向对 ...

  5. php 给对象动态增加属性 及子类继承父类的构造方法

    <?php error_reporting(-1); ini_set('display_errors','on'); class A { public $a = 'hello'; public  ...

  6. Linux:at命令详解

    at命令 at命令为单一工作调度命令.at命令非常简单,但是在指定时间上却非常强大 语法 at [选项] time at > 执行的命令 ctrl+d 选项 -m :当指定的任务被完成之后,将给 ...

  7. IntelliJ IDEA使用(二):tomcat和jetty配置(转自:http://www.cnblogs.com/jenkinschan/p/6052948.html)

    上一讲用idea创建了maven web项目,接下来我们把项目发布到tomcat和jetty运行,以便进一步地开发和调试 配置tomcat 第一.打开菜单栏 第二.点击设置按钮,添加应用服务器,选择t ...

  8. Vue CLI 3 配置兼容IE10

    最近做了一个基于Vue的项目,需要兼容IE浏览器,目前实现了打包后可以在IE10以上运行,但是还不支持在运行时兼容IE10及以上. 安装依赖 yarn add --dev @babel/polyfil ...

  9. Android Studio中设置提示函数用法

    Eclipse有一个很好的功能,就是当你代码调用某个android API时,鼠标移到对应的函数或者方法上,就会自动有一个悬浮窗提示该函数的说明(所包含的参数含义,该方法功能).迁移到Android ...

  10. MyEclipse多次重装、删除注册表、重装系统激活都不成功,终极解决方法 - imsoft.cnblogs

      问题:注册成功的MyEclipse被修改了一个配置文件之后,激活失败,然后在网上找激活码,激活不成功,但激活文件一直保存在电脑中,每次打开MyEclipse的激活界面总看得到之前的激活码.后面尝试 ...