《Essential C++ 中文版》Chapter3 如何实现一个泛型算法

送外卖转行计算机

问题先由一个很简单的需求开始: 给一个整数vector, 输出所有小于10的整数, 于是有了下面的函数

void filter(const std::vector<int> *numbers) {
    for (int number : *numbers) {
        if (number < 10) {
            std::cout << number << std::endl;
        }
    }
}

新需求: 要小于指定的数字。那么只要多加一个参数就好

void filter(const std::vector<int> *numbers, int less_than_val) {
    for (int number : *numbers) {
        if (number < less_than_val) {
            std::cout << number << std::endl;
        }
    }
}

新需求: 可以指定不同的操作, 比如 大于、小于、等于。这个时候需要把 输出number 这个逻辑抽象出来, 其实就是给定两个数字, 经过一个判断, 然后决定是否输出第一个数字, 所以可以将该逻辑抽象为一个固定形式的函数 bool comp(int a, int b), 那么如何来指定这个函数到底用哪个呢? 这里就要用到 函数指针 了:

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

// 注意 函数指针 的写法, 这里参数名当然可写可不写
void filter(const std::vector<int> *numbers, int filter_val, bool(*comp)(int, int)) {
    for (int number : *numbers) {
        if (comp(number, filter_val)) {
            std::cout << number << std::endl;
        }
    }
}

bool bigger(int a, int b) {
    return a > b;
}

bool equal(int a, int b) {
    return a == b;
}

int main() {
    int numbers_arr[3] = {1, 2, 11};
    std::vector<int> numbers(numbers_arr, numbers_arr + 3);
    // 传入bigger函数, 输出比10大的数字
    filter(&numbers, 10, bigger); 
    return 0;
}

新需求: 消除具体的容器类型和具体的元素类型
在这之前先介绍一下function object, 它其实是一个实例对象, 但是由于它重写了function call运算符, 也就是()括号操作符, 所以它可以被当成普通函数一样来使用, 举个例子来说

class Person {
public:
    void operator() () {
        std::cout << "重载了function call 也就是()操作符" << std::endl;
    }
};

那么如果建立一个它的实例对象 Person p, 那么就可以这样来使用p(), 理解起来比较简单, 一些标准库中的常见function object

  • 算术运算: plus<type>, modules<type> ...
  • 关系运算: less<type>, less_equal<type>, greater<type> ...
  • 逻辑运算: logical_and<type> ...

都是泛型算法, 使用的时候需要填一下type
那么function object的一些用途, 比如sort函数, 就可以塞一个关系运算的function object, 比如sort(x.begin, x.end, less<int>()), 其实这里的less<int>()的含义就是less这个泛型类的重载过后的function call也就是()方法, 你自己写一个泛型函数接受两个type值比较大小, 也能起到一样的效果, 不过库文件中的less<type>应该是把常见的类型的比较都帮你实现了, 所以我猜测这里sort函数的第三个参数其实接收的是一个接受两个值返回bool的函数指针而已。

了解了function object以后, 再了解一下function object adapater, 书中介绍的是bind adapter, 这里就简单讲一下这个adapter, 刚才知道function object重载的()函数是有参数的, 在我们的场景下, 需要比较两个数的大小, 所以其接受的是两个数, 但是find_if()泛型算法的第三个参数要求的是一个一元运算符, 所以不能直接把两个参数的function object直接塞进去, 而是要想办法把它变成一个一个参数的function object, 实现的方法就是使用bind adapterbind2nd()函数, 它可以绑定一个function object的第二个参数的值。

了解完什么是function object和什么是function object adapter以后, 就可以把刚才的filter函数继续改写成与容器无关和容器类型无关的函数了

  • 与容器无关: 通过传入iterator
  • 与类型无关: 通过泛型

那么为什么还要引入function object呢, 仅仅是为了适配find_if泛型算法吗, 我觉得这可能也是合理的吧, 毕竟既然标准库已经有比较运算的泛型算法了, 自己再去实现一遍没有任何好处, 还有可能会犯错。
下面就是最终的代码啦~

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

// InputIterator : 输入的 iterator 类型
// OutputInterator: 输出的 iterator 类型
// ElemType: 元素类型
// Comp: function object 类型, 理应是一个 关系运算
template <typename InputIterator, typename OutputIterator, typename ElemType, typename Comp> 
OutputIterator filter(InputIterator begin, InputIterator end, OutputIterator at, const ElemType &val, Comp pred) {
    while ((begin = std::find_if(begin, end, std::bind2nd(pred, val))) != end) {
        std::cout << *begin << std::endl;
        *at++ = *begin++;
    }
};

int main() {
    int numbers_arr[3] = {1, 2, 11};
    std::vector<int> numbers(numbers_arr, numbers_arr + 3);
    std::vector<int> result(3); // 因为函数中是移位赋值, 所以需要提前预留够足够的内存
    filter(numbers.begin(), numbers.end(), result.begin(), 10, std::greater<int>());
    return 0;
}

filter函数可能写的可读性没那么强, 它等价于以下这种形式

// InputIterator : 输入的 iterator 类型
// OutputInterator: 输出的 iterator 类型
// ElemType: 元素类型
// Comp: function object 类型, 理应是一个 关系运算
template <typename InputIterator, typename OutputIterator, typename ElemType, typename Comp> 
OutputIterator filter(InputIterator begin, InputIterator end, OutputIterator at, const ElemType &val, Comp pred) {
    while (begin != end) {
        begin = std::find_if(begin, end, std::bind2nd(pred, val));
        std::cout << *begin << std::endl;
        *at = *begin;
        at++;
        begin++;
    }
};

我理解这里引出知识点的逻辑是这样的 我想要使用泛型 -> 我需要一个泛型的关系比较函数(不想自己实现) -> 我想使用find_if来查找 -> 使用adapter来结合泛型关系比较函数和find_if
以上。

阅读 1.2k

四年送外卖经验, 自学转行计算机

12 声望
0 粉丝
0 条评论

四年送外卖经验, 自学转行计算机

12 声望
0 粉丝
宣传栏