C++Primer第十章:泛型算法

第十章:泛型算法

一.初识泛型算法

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

在C++中,无论是C风格的字符串还是字符串常量,都不能直接用==这些运算符进行比较,因为C风格的字符串和字符串常量直接比较都是相当于在比较首指针地址的大小,会出问题。

泛型算法不改变容器的元素个数和容器大小

当我们使用一个迭代器向容器元素赋值时,我们覆盖了原来的值,而当我们使用插入迭代器向容器元素赋值时,这个值是添加到容器中的。

二.定制操作

泛型算法有时要用到比较运算符,而我们希望指定我们自己的比较规则,泛型算法也支持了我们自定义自己的比较规则,可以再添加一个谓词参数,谓词是一个可调用的表达式,其返回结果是一个能用作条件的值。接受谓词参数的算法对输入序列中的元素调用谓词。谓词根据参数的多少分为一元谓词和二元谓词。

有的算法只能接受一元谓词作为参数,但是我们又需要在谓词里面用到两个参数,这个时候就要用到其他的可调用对象了。所谓可调用对象是指可以对其使用调用运算符。一共有四种可调用对象:函数;函数指针;重载了函数调用运算符的类;lambda表达式。

一个lambda表达式表示一个可调用的代码单元,我们可以理解为一个未命名的内联函数。lambda主要由四部分组成,除了返回类型,参数列表和函数体,还有捕获列表。我们可以忽略参数列表和返回类型。如果没有指定返回类型,则若函数体为单一的return语句时返回类型为自动推导出来的类型。否则为void。lambda的调用方式同函数一样。

lambda的捕获列表可以捕获它所在函数的局部变量以在函数体中使用,而定义在全局的变量例如cout此类的可以直接使用。即:捕获列表只用于局部非static变量,lambda可以直接使用局部static变量和在它所在函数之外声明的名字。

当定义一个lambda时,编译器生成一个与lambda相对应的未命名的类类型。从lambda生成的类都包含一个对应该lambda所捕获的变量的值,这个值在lambda对象创建时被初始化。

lambda捕获有值捕获和引用捕获两种方式。值捕获类似于传值参数,前提是变量可以被拷贝,且变量是在lambda创建时拷贝,而不是调用时拷贝;引用捕获类似于引用参数,前提是在使用的时候引用的对象还存在。一般来说,我们应该尽量减少捕获的数据量,来避免潜在的捕获导致的问题。而且如果可能的话应该尽量避免捕获引用或者指针或者迭代器。

除了显式指出我们要捕获的变量,我们也可以隐式捕获,让编译器推断我们要在函数体中用到的变量,这个时候我们可以在捕获列表中写一个=或&来指定捕获的方式,也可以同时隐式捕获和显式捕获,不过这样捕获列表中第一个一定是&或=来指定隐式捕获的方式是引用捕获还是值捕获,且显式捕获和隐式捕获的传递方式必须不同。

值捕获的方式捕获的对象一般是不能修改其值的,如果要修改的话需要在参数列表后加上mutable。

在定制操作中,使用lambda可以使用捕获列表解决只接受单一参数的可调用对象的问题,但其实用函数也可以解决。即使用一个函数适配器bind,bind接收一个可调用对象,并使用占位符和参数来生成一个新的可调用对象。

占位符_n都定义在命名空间placeholders里面,而后者又定义在std里面。bind和placeholders都定义在头文件functional里面。

所谓占位符,占的是待会要传递给bind生成的新的可调用对象的位置,_1表示第一个传过来的参数,所以可以利用占位符自由安排传过来的参数是原来可调用对象的第几个参数,例如bind(f(), _2, a, b, _1),则传过来的第一个参数成了f的第四个参数,第二个参数成了f的第一个参数。

对于bind函数中的非占位符的其他参数,bind使用拷贝的方式传递,如果我们希望传递给bind一个对象而又不拷贝它,则必须使用标准库ref函数,ref返回一个对象,对象包含给定的引用,这个对象是可以拷贝的,还有一个cref函数生成一个保存const引用的类。

三.再探迭代器

除了标准库中每个容器都有的迭代器,标准库还在头文件iterator中定义:插入迭代器,流迭代器,反向迭代器,移动迭代器。

插入迭代器有三种:back_inserter, front_inserter, inserter,分别相当于调用三种方法。给这三种传入容器会返回对应的迭代器,然后直接对这个迭代器赋值即可相当于对应的插入。

对于流迭代器,主要有两种:istream_iterator读取输入流, ostream_iterator向一个输出流写数据。这些迭代器可以将他们绑定的流当作一个特殊的元素序列来处理。

istream_iterator使用>>来读取流,所以它绑定的流要定义了>>运算符。类似的。ostream_iterator需要绑定到定义了<<运算符的类型对象。同时ostream_iterator提供可选的第二参数,每次打印后会打印第二参数,第二参数需要是C风格字符串。部分泛型算法可以使用流迭代器。

反向迭代器可以直接通过容器的rbegin或者rend生成,除了forward_list其他都有反向迭代器。所谓反向迭代器其实就是开始位置和移动方向是反的。

我们可以通过反向迭代器的base方法将其转换为一个普通的迭代器,但是一定注意,普通迭代器和反向迭代器之间的转换不是一致的,即两个迭代器在转换后所指位置不相同了,具体表现是反向迭代器会比普通迭代器靠右一个。

四.泛型算法结构

任何算法最基本的特性是它要求其迭代器提供哪些操作。根据迭代器的能力,可以把迭代器分为5类,算法应该至少需要哪一类的迭代器进行说明。

对于某些算法其有很多重载的版本,例如可以输入自己的谓词。在算法名称后面加上_if组成新的算法,这个算法需要我们传入一个自己的谓词。加上 _copy表示将新生成的内容复制到额外的目的空间。

list和forward_list自带一些算法,例如remove,merge,sort,splice等,因为有些算法需要随机访问迭代器而list和forwardlist没有。且某些算法是对值的操作,而list和forward_list有时候只需要修改链接即可。因此,对于list和forward_list,应该优先使用它们容器自带的算法。另外,自带的算法和泛型算法有一个很大的不同,自带算法是可以修改底层的容器的。

练习

10.1

int main() {
    vector<int> vec;
    int temp = 0;
    while (cin >> temp) {
        vec.push_back(temp);
    }
    int val = 2;
    cout << count(vec.begin(), vec.end(), val) << endl;
    return 0;
}

10.2

int main() {
    vector<string> vec;
    string temp;
    while (getline(cin, temp)) {
        vec.push_back(temp);
    }
    string val = "test";
    cout << count(vec.begin(), vec.end(), val) << endl;
    return 0;
}

10.3

int main() {
    vector<int> vec{1,2,3,4,5};
    cout << accumulate(vec.cbegin(), vec.cend(), 0) << endl;
    return 0;
}

10.4

0是int类型的数据,而vector中存储的是double数据,为了能与int相加,double会转为int,可能会损失数据。

10.5

equal是使用运算符来比较是否相等,string冲在所以可以正常比较,而char*使用==比较实际上是在比较指针地址是否相同,所以除非指针地址相同

10.6

int main() {
    vector<int> vec{1,2,3,4,5};
    fill_n(vec.begin(), vec.size(), 0);
    return 0;
}

10.7

(a)vec是一个空的vector,没有空间存储copy过来的值,可以使用back_inserter插入迭代器提取vec
(b)reserve只是有10个元素的空间但是没有10个元素,vec还是空的,所以还是插入不了,还是可以使用back_inserter

10.8

泛型算法并不知道容器的存在,它只是通过迭代器来操作对应的元素,而普通的迭代器不能对容器大小进行改变,而back_inserter的机制决定算法操作它时它可以改变容器大小,所以不是算法改变了容器大小,而是算法操作的迭代器改变的。

10.9

void show (const vector<string> &words) {
    for (auto iter = words.begin(); iter != words.end(); ++iter) {
        cout << *iter << " ";
    }
    cout << endl;
}

void elimDups(vector<string> &words) {
    show(words);
    sort(words.begin(), words.end());
    show(words);
    auto uniq = unique(words.begin(), words.end());
    show(words);
    words.erase(uniq, words.end());
    show(words);
}

int main() {
    vector<string> vec{"1","3","2","2","0"};
    elimDups(vec);
    return 0;
}

感觉这道题有点问题,在我的编译器上面,unique会删除元素。

10.10

为了实现算法和数据结构的脱离,必须要用迭代器作为中间来让算法操作容器,而普通的迭代器本身也不具备改变容器大小的能力。

10.11

#include <vector>
#include <algorithm>
#include <iostream>
#include <string>

bool isShorter(const std::string &s1, const std::string &s2) {
    return s1.size() < s2.size();
}

void elimDups(std::vector<std::string> &words) {
    // 按长度排序,长度相同的单词维持字典序
    std::stable_sort(words.begin(), words.end(), isShorter);
    for (std::string &s : words)
        std::cout << s << " ";
    std::cout << words.size() << std::endl;
}

int main() {
    std::vector<std::string> svec = {"the", "quick", "red", "fox", "jumps",
                                     "over", "the", "slow", "red", "turtle"};
    elimDups(svec);

    return 0;
}

10.12

答案

10.13

void show (const vector<string> &words) {
    for (auto iter = words.begin(); iter != words.end(); ++iter) {
        cout << *iter << " ";
    }
    cout << endl;
}

bool func(const string &str) {
    return str.size() >= 5;
}
int main() {
    vector<string> vec{"111111","3","2222222","2","0"};
    partition(vec.begin(), vec.end(), func);
    show(vec);
    return 0;
}

10.14

int main() {
    auto f = [](int a, int b) {return a + b;};
    cout << f(11, 19) << endl;
    return 0;
}

10.15

int main() {
    int b = 11;
    auto f = [b](int a) {return a + b;};
    cout << f(19) << endl;
    return 0;
}

10.16

答案

10.17

[](const Sales_data &lhs, const Sales_data &rhs) {return lhs.isbn() < rhs.isbn();}

10.18 10.19

答案

10.20

int main() {
    vector<string> svec = {"123", "345678", "123456789", "1"};
    cout << count_if(svec.begin(), svec.end(), [](const string &str) -> bool{return (str.size() > 6 ? true : false);});
    cout << endl;
    return 0;
}

10.21

int main() {
    int a = 0;
    auto f = [&a]() -> bool{
        if (a == 0) {
            return true;
        }
        else {
            --a;
            return false;
        }
    };
    return 0;
}

10.22

bool func(const string &str, int len) {
    return str.length() <= 6;
}
int main() {
    vector<string> svec = {"123", "345678", "123456789", "1"};
    cout << count_if(svec.begin(), svec.end(), bind(func, _1, 6));
    cout << endl;
    return 0;
}

10.23

它所绑定的可调用对象的参数数量加一。

10.24

#include <iostream>
#include <vector>
#include <string>
#include <functional>
#include <algorithm>

using namespace std;
using std::placeholders::_1;

bool check_size(const string &s, int sz) {
    return s.size() < sz;
}

void biggies(vector<int> &ivec, const string &s) {
    // 查找第一个大于等于 s 长度的数值
    auto p = find_if(ivec.begin(), ivec.end(),
            bind(check_size, s, _1));

    // 打印结果
    cout << "第" << p - ivec.begin() + 1 << "个数" << *p
         << "大于" << s << "的长度" << endl;
}

int main() {
    vector<int> iv = {3, 2, 4, 5, 7};

    biggies(iv, "C++");
    biggies(iv, "Primer");

    return 0;
}

10.25

答案

10.26

三种插入迭代器不同之处主要在插入的位置。

10.27

int main() {
    list<int> test1;
    vector<int> test2{1,2,3};
    unique_copy(test2.begin(), test2.end(), back_inserter(test1));
    cout << test1.size() << endl;
    return 0;
}

10.28

int main() {
    list<int> list1, list2, list3;
    vector<int> vec{1,2,3,4,5,6,7,8,9};
    copy(vec.begin(), vec.end(), inserter(list1, list1.begin()));
    for (auto i : list1) {
        cout << i << " ";
    }
    cout << endl;
    return 0;
}

10.29

int main() {
    ifstream in("./input.txt");
    istream_iterator<string> put(in), eof;
    vector<string> vec(put, eof);
    for (auto i : vec) {
        cout << i << endl;
    }
    return 0;
}

10.30

答案

10.31

把copy换成unique_copy就可以了

10.32

答案

10.33

int main() {
    ifstream in("./input");
    ofstream out1("./output1"), out2("./output2");
    istream_iterator<int> pull(in), eof;
    ostream_iterator<int> push1(out1, " "), push2(out2, " ");
    while (pull != eof) {
        if (*pull % 2 != 0) {
            *(push1++) = *pull;
        }
        else {
            *(push2++) = *pull;
        }
        ++pull;
    }
    return 0;
}

10.34

int main() {
    vector<int> vec{1,2,3};
    for_each(vec.crbegin(), vec.crend(), [](int a)->void{cout << a << " ";});
    return 0;
}

10.35

int main() {
    vector<int> vec{1,2,3};
    auto it = vec.end();
    --it;
    while (true) {
        if (it == vec.begin()) {
            cout << *it << " ";
            break;
        }
        cout << *it << " ";
        --it;
    }
    return 0;
}

10.36

int main() {
    list<int> test{1,2,3,0,1,1,0,23};
    cout << *(find(test.crend(), test.crbegin(), 0)) << endl;;
    return 0;
}

10.37

答案

10.38

答案

10.39

list 上的迭代器是双向迭代器,vector 上的迭代器是随机访问迭代器。

10.40

copy 要求前两个参数至少是输入迭代器,表示一个输入范围。它读取这个范围中的元素,写入到第三个参数表示的输出序列中,因此第三个参数至少是输出迭代器。
reverse 要反向处理序列,因此它要求两个参数至少是双向迭代器。
unique 顺序扫描元素,覆盖重复元素,因此要求两个参数至少是前向迭代器。“至少” 意味着能力更强的迭代器是可接受的。

10.41

答案

10.42

int main() {
    list<int> test{1,2,3,1,4,5,3};
    for_each(test.cbegin(), test.cend(), [](int a)->void{cout << a << " ";});
    test.sort();
    test.unique();
    cout << endl;
    for_each(test.cbegin(), test.cend(), [](int a)->void{cout << a << " ";});
    cout << endl;
    cout << test.size() << endl;
    return 0;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值