10-泛型算法

10.1 概述

#include <algorithm> //大部分算法定义

#include <numeric> //数值泛型算法  

容器查找

auto ret = find(v.cbegin(), v.cend(), val);

if (ret == v.cend()){

//未找到

}else{

//找到,ret指向第一个找到的元素的迭代器

}

find(begin(arr), end(arr), val);  //内置数组

算法运行于迭代器之上,依赖于迭代器支持的迭代器操作。泛型算法不会改变底层容器的大小,不会直接添加和删除元素。


10.2 初识泛型算法

大部分算法对一个范围内的元素进行操作,[begin, end),一般是前两个参数。

10.2.1 只读算法

只读取范围内的参数,不改变元素。例如find。

#include <numeric>

int sum = accumulate(v.cbegin(), v.cend(), val); //对v中元素求和,和的初值为val。

v的元素必须支持加到val上的操作,类型匹配或者可以转换。

equal(r1.cbegin(), r1.cend(), r2.cbegin()); //对r1,r2中相应位置元素比较,所有对应元素相等返回true。

对于容器和元素的类型没有强制要求相同,只需支持用==比较即可。假定r2的待比较序列的长度至少与r1一样长。

10.2.2 可写容器元素算法

fill(v.begin(), v.end(), val);//注意操作范围应有效。

fill_n(v.begin(), v.size(), val);

fill_n(pos, n, val);

以上的操作需保证v的空间足够,因为普通的迭代器不会改变容器的空间。

插入迭代器:一种向容器中添加元素的迭代器。

#include <iterator>

//例如

vector<int> vec;

auto it = back_inserter(vec);

*it = 42;

//

fill_n(back_inserter(vec), n,  0);//添加n个元素到容器中,底层调用push_back()

copy(begin(arr1), end(arr2), arr2);//将数组arr1特定位置元素拷贝到arr2数组中,返回arr2尾元素之后的位置

replace(l.begin(), l.end(), origal_val, val);//原序列改变

replace_copy(l.cbegin(), l.cend(), back_inserter(vec), origal_val, val);//原序列未改变,修改后的版本存入vec中

10.2.3 重排容器元素算法

sort(v.begin(), v.end());//利用元素类型的<运算符实现排序

unique(v.begin(), v.end());//返回指向不重复区域之后的位置的迭代器,不改变容器大小

erase(pos_begin, pos_end);//删除,会改变容器大小

10.3 定制操作

10.3.1 向算法传递函数

谓词:可调用表达式,返回结果是一个可以用作条件的值。

一元谓词:只接受一个参数

二元谓词:只接受二个参数

使用谓词参数的算法对输入序列的每个元素调用谓词,元素类型可转换为谓词参数的类型

bool isShorter(const string &s1, const string, &s2){

        return s1.size() < s2.size();

}

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

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

10.3.2 lambda表达式

可调用对象:可使用调用运算符()的表达式(函数,函数指指针,lambda)

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

捕获列表:捕获lambda所在函数中定义的局部变量的列表。

如果忽略返回类型,若函数体只有一个return语句,则根据语句推断出返回类型;否则,返回void;可忽略参数列表和返回类型。

auto f = [] {return 42;};

cout << f() <<endl;

lambda不可有默认参数。

stable_sort(v.begin(), v.end(),[](const string &a, const string &b){ return a.size() < b.size(); });

auto f = [sz](const string&a) { return a.size() >= sz;  };

find_if(v.begin(), v.end(), f);//找出第一个返回

for_each(v.begin(), v.end(), [] (const string &s) { cout <<s<<" ";});

10.3.3 lambda 捕获和返回

当定义一个lambda时,编译器生成一个与lambda对应的类类型和此类型的对象。传递给函数的参数即是此类的未命名对象,捕获成员即是此类的数据成员。

值捕获:[val],val的值是在lambda被创建时绑定的

引用捕获:[&val],实际使用的是引用绑定的对象

[&os, c] (const string &s) { os << s << " ";};

隐式捕获

[=],值捕获所有的局部非static变量

[&],引用捕获所有的局部非static变量

总结捕获方式:


[ ]

[name_list]

[&]

[=]

[=,name_list],name_list采用引用捕获,不包括this

[&,name_list],name_list采用值捕获


可变lambda

[v1] () mutable { return ++val; };//可改变值捕获变量的值

指定lambda返回类型

transform(v.begin(), v.end(), v.begin(), [] (int i) { return i<0 ? -i : i; });

transform(begin(c), end(c), begin(c), [](int i)->int {

if (i < 0) {

return i;

}

else {

return -i;

}

});

10.3.4 参数绑定

#include <functional>

bind函数可视为一个通用的函数适配器,接受一个可调用对象,生成一个新的可调用对象,来适应原对象的参数列表。(修正参数或改变参数顺序)

auto newCallable = bind(callable, arg_list);

占位符 _n

using std::placeholders::_1;

using namespace std::placeholders;

_1为newCallable的第一个参数,_2为第二个参数,以此类推。

auto func1 = bind(func, _1,val);

func1(arg);//func1调用func,类似于func(arg,val);

find_if(v.begin(), v.end(), bind(func, _1, other));

auto g = bind(f, a,b,_2,c,_1);

g(d,e);等价于f(a,b,e,c,d);

绑定引用参数

默认情况下,bind的非占位符参数被拷贝到bind的返回的可调用对象中。传递对象但不拷贝,可用引用绑定。

for_each(v.begin(), v.end(), bind(print, ref(os),_1, ' '));

#include <functional>

ref(os);返回一个对象,包含os的引用,可拷贝。

cref(os);生成一个保存const引用的类。

10.4 再探迭代器

插入迭代器:被绑定到一个容器上,用来向容器插入元素

流迭代器:绑定到输入输出流,用来遍历关联的IO流

反向迭代器:向后移动,除forward_list标准库容器都有此迭代器

移动迭代器:不拷贝容器的元素,只是移动

10.4.1 插入迭代器

使用插入迭代器赋值,迭代器调用容器操作给给定容器的指定位置插入一个元素。

支持的操作:

it = t; //it为插入迭代器,插入t到it指向的位置

*it, ++it, it++ //不会执行实际操作,返回it


back_inserter:创建一个使用push_back的迭代器

front_inserter:创建一个使用push_front的迭代器

inserter:创建一个使用insert的迭代器


auto it = inserter(c, iter);

*it = val;//将val插入到iter指向元素之前的位置,it指向原来的元素位置

等价于

it = c.insert(it, val);

++it;

back_inserter,front_inserter迭代器插入后会改变当前迭代指向的位置,而inserter迭代器不会。

与普通的迭代器的区别是:算法可以间接的改变容器的大小。

10.4.2 iostream迭代器

此种迭代器将关联的流当做一个特定类型的元素序列来处理,便于使用泛型算法。istream_iterator关联的类型需支持>>运算符,ostream_iterator关联的类型必须支持<<运算符。

流迭代器不支持--运算。

允许使用懒惰求值,在我们第一次解引用迭代器之前,数据就已经从流中读取出来了。


istream_iterator<int>  int_it(cin);//从cin读取int

istream_iterator<int> it_eof;//默认初始化迭代器生成尾后迭代器

int_it1 == int_it2;

int_it1 != int_it2;

*int_in;

int_in->men(或(*int_in).men)

++int_it;//使用元素类型定义的>>读取流中下一个值

int_it++;

例子:

ifstream in("path");

istream_iterator<string> str_in(in);

while (int_in != it_eof){

v.push_back(*int_it++);

}

vector<int> vec(int_it, it_eof);//将流的数据初始化vector


ostream_iterator<T> out(os);//将类型T的元素写入到os中

ostream_iterator<T> out(os, d);//每写一个元素就附加一个d,d指向一个C风格的字符串数组

out  = val;//将val写入到out关联的流中

*out ,++out , out++ 不对out执行任何操作,返回out

例子:

ostream_iterator<int> out_it(cout, '" ");

for (auto e : vec){

*out_it++ = e;//输出vec的元素到cout中,等价于out_it=e

}

copy(vec.begin(), vec.end(), out_it);//打印vec元素到cout中

cout<<endl;

10.4.3 反向迭代器

容器中从尾元素向首元素移动的迭代器。

rbegin, rend, crbegin, crend.

iter = riter.base();//返回一个普通的迭代器,riter,iter的位置是相邻的

10.5 泛型算法结构

算法要求的迭代器操作分为5个迭代器类别,

输入迭代器:只读;单遍扫描,递增

输出迭代器:只写;单遍扫描,递增

前向迭代器:可读写,多遍扫描,递增

双向迭代器:可读写;多遍扫描,递增递减

随机访问迭代器:可读写;多遍扫描,支持所有迭代器操作

10.5.1 

对泛型和数值算法来说,迭代器参数的能力必须与算法规定的迭代器最小类别的至少相当。

10.5.2 算法形参模式

大部分算法具有如下形式:

alg(beg, end, arg_list);

alg(beg, end, dest, arg_list);

alg(beg, end, beg2, arg_list);

alg(beg, end, beg2, end2, arg_list);

10.5.3 算法命名规范

接受谓词参数来代替<或==运算符的算法,以及不接受额外参数的算法,通常都是重载的函数。一个版本使用元素类型的运算符来比较,另一个版本使用谓词参数代替<或==;

接受一个元素值的算法通常有一个附加_if的函数(非重载)。一般是在给定范围查找元素第一次出现的位置。

某些算法会将对元素的操作写回原容器,一般附加_copy的算法会将操作执行早附加的目标容器,而不改变原容器。

10.6 特定容器算法

list和forward_list的特定成员函数算法

lst.merge(list2);//lst2合并到lst中,都需要有序,lst2最后变为空,使用元素的<

lst.merge(lst2, comp);//使用comp比较

lst.remove(val);//调用erase删除val

lst.remove_if(pred);//调用erase删除谓词为真的元素

lst.reverse();//翻转lst

lst.sort()

lst.sort(comp)

lst.unique();//调用erase删除同一个值得连续拷贝,使用==

lst.unique(pred);//使用给定二元谓词

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

参数args类型:

(p, lst2):将lst2的所有元素移动到lst中p指向的位置或flst中p之后的位置,lst2之后为空,不可为同一个链表

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

(p, lst2, b, e):将lst2的[b, e)范围的元素移动到lst或flst,可与lst或flst是相同的链表,p不可以指向此范围的元素。

链表特有的算法会改变容器,与通用版本有区别。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,319评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,801评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,567评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,156评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,019评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,090评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,500评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,192评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,474评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,566评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,338评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,212评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,572评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,890评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,169评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,478评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,661评论 2 335

推荐阅读更多精彩内容

  • 10泛型算法 10.1概述 泛型算法不能改变容器的大小,依赖于元素类型的操作。 10.2初识泛型算法 10.2.1...
    龟龟51阅读 222评论 0 0
  • STL整体结构 STL主要由六部分组成,分别为容器(containers)、迭代器(iterators)、空间配置...
    孙浩_9bfd阅读 346评论 0 1
  • 前言 把《C++ Primer》[https://book.douban.com/subject/25708312...
    尤汐Yogy阅读 9,484评论 1 51
  • 算法 头文件 (STL算法部分主要由头文件,,组成。要使用STL中的算法函数必须包含头文件,对于数值算法须包含,中...
    Wancho阅读 400评论 0 1
  • 人们常说,人到了一定年龄之后就特别爱回忆,也许他们说得没错,时间如梭,我如今已过不惑之年,近几年就特别爱回忆以前的...
    欲望之城阅读 545评论 11 3