C/C++基础----泛型算法

算法不依赖与容器(使用迭代器),但大多数依赖于元素类型。如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。

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

原文地址:https://www.cnblogs.com/logchen/p/9576388.html