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

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

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

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

void filter(const std::vector *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
#include
#include
#include
#include

// 注意 函数指针 的写法, 这里参数名当然可写可不写
void filter(const std::vector *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 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, modules ...
  • 关系运算: less, less_equal, greater ...
  • 逻辑运算: logical_and ...

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

// InputIterator : 输入的 iterator 类型
// OutputInterator: 输出的 iterator 类型
// ElemType: 元素类型
// Comp: function object 类型, 理应是一个 关系运算
template  
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 numbers(numbers_arr, numbers_arr + 3);
    std::vector result(3); // 因为函数中是移位赋值, 所以需要提前预留够足够的内存
    filter(numbers.begin(), numbers.end(), result.begin(), 10, std::greater());
    return 0;
}

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

// InputIterator : 输入的 iterator 类型
// OutputInterator: 输出的 iterator 类型
// ElemType: 元素类型
// Comp: function object 类型, 理应是一个 关系运算
template  
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
以上。

你可能感兴趣的:(c++,泛型)