《C++ Primer》学习笔记(28)Generic Algorithms

来源:本站
导读:目前正在解读《《C++ Primer》学习笔记(28)Generic Algorithms》的相关信息,《《C++ Primer》学习笔记(28)Generic Algorithms》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《《C++ Primer》学习笔记(28)Generic Algorithms》的详细说明。
简介:为每一种容器增加很多功能,显然是不合算的。实际上,库为所有的容器提供了通用的算法,来操作容器和容器的元素。


查找元素、替换或移除值、重新排序……都属于这一范畴。

10.1 Overview

----------------------------------------------------------------

使用这些算法时,请包含下面的头文件:

#include <algorithm>

#include <numeric>

算法并不直接作用于容器,而是作用于容器的迭代器,比如:

int val = 42;

auto result = find(vec.begin(), vec.end(), val);

(???此处使用cbegin和cend会报错。)

有了迭代器,独立于容器的算法才成为可能。

算法虽然独立于容器,但是它依赖于容器的元素类型。

算法不会执行容器的operations,它不会增加或删除容器的元素。

10.2 A First Look at the Algorithms

----------------------------------------------------------------

虽然库提供了100多种算法,但是它们和容器类似,具备一致的结构,易于理解和使用。

1. 有一些算法是只读的,比如find、count和accumulate。

auto result3 = accumulate(vch.begin(), vch.end(), 0);

string sum = accumulate(v.begin(), v.end(), string(""));

(accumulate的第三个参数类型,确定了相加之和的类型。)

还有一种只读的算法是equal:

equal = (roster1.begin(), roster1.end(), roster2.begin());

roster1和roster2的容器类型可以不一样,只要它们的元素可以比较即可。

2. 有一些算法是可写的,比如fill或fill_n:

fill(vec.begin(), vec.end(), 10);

一般来说,算法无法添加或者删除元素,但是使用insert iterator可以。

back_inserter使用容器的引用作为参数,返回insert iterator,从而调用push_back方法:

auto it = back_inserter(vch);

*it = 5;

我们可以使用back_inserter来创建迭代器,用作算法的destination:

fill_n(back_inserter(vch), 5, 7);

for (auto &r : vch)

cout << r << endl;

copy、replace也是可写的算法。

3. 有一些算法用来重新排序,比如sort:

vector<string> words = {"hello", "world", "hello", "maria"};

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

for (auto &r : words)

cout << "a: " << r << endl;

虽然算法不能直接操作容器(不能添加或删除元素),但它可以调用容器的方法。

“算法基于迭代器来操作以实现泛型,而当需要在容器中添加或删除元素时, 不知道容器和容器元素的具体

类型,就不可能知道需要增加或减少多少空间,就无法实现容器添加或删除元素的目的。 添加或删除容器元

素的操作是与类型强相关的,而泛型的算法做不到这点。” —— (知乎)南friend

10.3 Customizing Operations

----------------------------------------------------------------

我们可以定制自己的算法。

重载的一种sort函数,使用了第三个参数作为predicate(谓词),它是一个表达式,返回conditon值。

predicates可以有一个参数(unary predicates),或两个参数(binary predicates)。

比如:

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

{

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

}

int main(int argc, char *argv[])

{

vector<string> words = {"hello", "world1", "hello111", "maria1111", "apple"};

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

for (auto &r : words)

cout << "a: " << r << endl;

return 0;

}

执行的结果是:

a: hello

a: apple

a: world1

a: hello111

a: maria1111

使用stable_sort算法,可以在根据predicates排序的基础上,再根据alphabetical排序。

有时候我们需要给predicates输入更多的参数,此时可以使用Lambda表达式。

predicates可以是任何能够使用call方法的表达式,目前我们使用的是functions,此外还有两种:

* classes that overload the function-call operator

* lambda expressions

lambda表达式可以被认为是,一种未命名的inline函数:

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

capture list:(定义此lambda表达式的)函数的局部变量。

parameter list:参数的参数列表。

return type:使用trailing return来指定返回值的类型。

比如下面这段代码:

auto f = [] {return 42;};

cout << f() << endl;

char c = ' ';

ostream &os = cout;

auto ff = [&os, c](const string &s) {os << s << c;};

ff("hello");

ff("world");

lambda表达式不可以有默认的参数。

现在我们就可以使用lambda表达式来解决predicates的问题了:

vector<string>::size_type sz;

sz = 5;

stable_sort(words.begin(), words.end(),

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

lambda表达式的创建,实际上是一个(未命名的)新类的创建。

它即创建类的type,又创建了一个类的实体,lambda的capture就是此类的变量成员。

capture list传递的不是变量地址,而是变量的值,因此它的值在lambda表达式创建时就确定了,

如果要传递变量的话,使用引用即可。

lamdba表达式可以作为函数的返回值。

由于lambda表达式的特点,我们最好将它的capture list保持得简单直接一些。

如何告诉编译器,此lambda表达式通通使用变量的值:

auto sz = s.size();

auto wc = find_if(words.begin(), words.end(),

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

如何告诉编译器,此lambda表达式通通使用变量的引用:

auto sz = s.size();

auto wc = find_if(words.begin(), words.end(),

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

另外,它们可以混合使用:

[&, c]

[=, &os]

实际上,由于算法经常需要使用predicates,所以它和lambda表达式经常紧密的结合。

虽然lambda表达式在需要使用local变量的时候有着优势,但我们仍然可以通过函数来实现它:

auto newCallable = bind(callable, arg_list);

比如:

#include <functional>

using namespace std;

using namespace std::placeholders;

bool check_size(const string &s, string::size_type sz)

{

return s.size() >= sz;

}

int main(int argc, char *argv[])

{

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

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

}

(???由于GCC版本问题,bind未能解析。)

10.4 Revisting Iterators

----------------------------------------------------------------

插入迭代器:

*it = val;

等于:

it = c.insert(it, val);

++it;

流迭代器:

vector<int> vec;

istream_iterator<int> in_iter(cin);

istream_iterator<int> eof;

while (in_iter != eof)

vec.push_back(*in_iter++);

for (auto &a : vec)

cout << a << endl;

还可以更简单的写成:

istream_iterator<int> in_iter(cin), eof;

vector<int> vec(in_iter, eof);

(* 为什么要把流写成迭代器的形式呢?因为算法需要迭代器啊!)

(* 更复杂的用法,以后用到的时候再详细学习。)

反向迭代器:

for (auto r_iter = words.rbegin(); r_iter != words.rend(); ++r_iter)

cout << *r_iter << endl;

反向迭代器便于算法的反向操作。

反向迭代器不支持forward_list和流迭代器。

另外,反向迭代器的base()和它指向的是相邻的元素,而不是同一个。

10.5 Structure of Generic Algorithms

----------------------------------------------------------------

迭代器可以分为5类。

算法的命名是遵循一定规律的。

10.6 Container-Specific Algorithms

----------------------------------------------------------------

list和forward_list定义了自己的算法,比如sort、merge、remove、reverse和unique。

它们对于自身的效率更高。

它们会改变容器本身。

提醒:《《C++ Primer》学习笔记(28)Generic Algorithms》最后刷新时间 2024-03-14 00:57:47,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《《C++ Primer》学习笔记(28)Generic Algorithms》该内容的真实性请自行鉴别。