Boost C++ 库 | 函数对象(数百家企业面试题C++分享)

151 阅读12分钟

点击上方"蓝字"关注我们

上节回顾

| Boost C++ 库 | 是什么? | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Boost C++ 库 | 智能指针(RAII、作用域指针、作用域数组) | | Boost C++ 库 | 智能指针(共享指针、共享数组、弱指针、介入式指针、指针容器)入门 |

01、Boost.Bind

Boost.Bind 是这样的一个库,它简化了由C++标准中的 std::bind1st() 和 std::bind2nd() 模板函数所提供的一个机制:将这些函数与几乎不限数量的参数一起使用,就可以得到指定签名的函数。这种情形的一个最好的例子就是在C++标准中定义的多个不同算法。

#include <iostream>#include <vector>#include <algorithm>​void print(int i) {    std::cout << i << std::endl; // 1 2 3}​int main(){    std::vector<int>v;    v.push_back(1);    v.push_back(2);    v.push_back(3);​    std::for_each(v.begin(),v.end(),print);​​    return 0;}​// 输出05:21:08: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...123
算法 std::for_each() 要求它的第三个参数是一个仅接受正好一个参数的函数或函数对象。如果 std::for_each() 被执行,指定容器中的所有元素 - 在上例中,这些元素的类型为 int - 将按顺序被传入至 print() 函数。但是,如果要使用一个具有不同签名的函数的话,事情就复杂了。例如,如果要传入的是以下函数 add(),它要将一个常数值加至容器中的每个元素上,并显示结果。 Boost.Bind 简化了不同函数之间的绑定。它只包含一个 boost::bind() 模板函数,定义于 boost/bind.hpp 中。使用这个函数,可以如下实现以上例子:
#include <iostream>#include <vector>#include <algorithm>#include <boost/bind.hpp>​​void print(int i) {    std::cout << "print = " << i << std::endl; // 1 2 3}​void add(int i, int j){    std::cout << "add = " << i + j << std::endl; // 11 12 13}​int main(){    std::vector<int>v;    v.push_back(1);    v.push_back(2);    v.push_back(3);​    // 只支持一个参数    std::for_each(v.begin(),v.end(),print);​    // 一个一元函数 - 即只要求一个参数的函数    std::for_each(v.begin(),v.end(),boost::bind(add,10,_1));​    return 0;}
像 add() 这样的函数不再需要为了要用于 std::for_each() 而转换为函数对象。使用 boost::bind(),这个函数可以忽略其第一个参数而使用。因为 add() 函数要求两个参数,两个参数都必须传递给 boost::bind()。第一个参数是常数值10,而第二个参数则是一个怪异的 _1_1 被称为占位符(placeholder),定义于 Boost.Bind。除了 _1,Boost.Bind 还定义了 _2 和 _3。通过使用这些占位符,boost::bind() 可以变为一元、二元或三元的函数。对于 _1boost::bind() 变成了一个一元函数 - 即只要求一个参数的函数。这是必需的,因为 std::for_each() 正是要求一个一元函数作为其第三个参数。当这个程序执行时,std::for_each() 对容器 v 中的第一个元素调用该一元函数。元素的值通过占位符 _1 传入到一元函数中。这个占位符和常数值被进一步传递到 add() 函数。通过使用这种机制,std::for_each() 只看到了由 boost::bind() 所定义的一元函数。而 boost::bind() 本身则只是调用了另一个函数,并将常数值或占位符作为参数传入给它。下面这个例子通过 boost::bind() 定义了一个二元函数,用于 std::sort() 算法,该算法要求一个二元函数作为其第三个参数。
#include <iostream>#include <vector>#include <algorithm>#include <boost/bind.hpp>​// 降序bool compare(int i, int j){    return i > j;}​int main(){    std::vector<int>v;    v.push_back(1);    v.push_back(4);    v.push_back(2);​    // 对容器进行降序    std::sort(v.begin(),v.end(),boost::bind(compare,_1,_2));​    for (int i : v) {        std::cout << i << std::endl;    }​    return 0;}// 输出05:37:54: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...421
因为使用了两个占位符 _1 和 _2,所以 boost::bind() 定义了一个二元函数。 std::sort() 算法以容器 v 的两个元素来调用该函数,并根据返回值来对容器进行排序。基于 compare() 函数的定义,容器将被按降序排列。但是,由于 compare() 本身就是一个二元函数,所以使用 boost::bind() 确是多余的。
#include <boost/bind.hpp> #include <vector> #include <algorithm> ​bool compare(int i, int j) {   return i > j; } ​int main() {   std::vector<int> v;   v.push_back(1);   v.push_back(3);   v.push_back(2); ​  std::sort(v.begin(), v.end(), compare); } ​// 输出05:45:22: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...421
不过使用 boost::bind() 还是有意义的。例如,如果容器要按升序排列而又不能修改 compare() 函数的定义。该例子仅改变了占位符的顺序:_2 被作为第一参数传递,而 _1 则被作为第二参数传递至 compare(),这样即可改变排序的顺序。

#include <iostream>#include <vector>#include <algorithm>#include <boost/bind.hpp>​// 升序bool compare(int i, int j){    return i > j;}​int main(){    std::vector<int>v;    v.push_back(1);    v.push_back(4);    v.push_back(2);​    // 对容器进行升序    std::sort(v.begin(),v.end(),boost::bind(compare, _2 ,_1));​    for (int i : v) {        std::cout << i << std::endl;    }​    return 0;}​// 输出05:46:42: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...124

02、Boost.Ref

本库 Boost.Ref 通常与 Boost.Bind 一起使用,所以我把它们挨着写。它提供了两个函数 - boost::ref() 和 boost::cref() - 定义于 boost/ref.hpp.

当要用于 boost::bind() 的函数带有至少一个引用参数时,Boost.Ref 就很重要了。由于 boost::bind() 会复制它的参数,所以引用必须特别处理。

#include <iostream>#include <vector>#include <algorithm>#include <iostream>#include <boost/ref.hpp>#include <boost/bind.hpp>​​void add(int i, int j,std::ostream &os){    os << "add = " << i + j << std::endl;}​int main(){    std::vector<int>v;    v.push_back(1);    v.push_back(4);    v.push_back(2);​    std::for_each(v.begin(),v.end(),boost::bind(add, 10 ,_1, std::ref(std::cout)));​    for (int i : v) {        std::cout << "i = " << i << std::endl;    }​    return 0;}// 输出05:52:42: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...add = 11add = 14add = 12i = 1i = 4i = 2
以上例子使用了上一节中的 add() 函数。不过这一次该函数需要一个流对象的引用来打印信息。因为传给 boost::bind() 的参数是以值方式传递的,所以 std::cout 不能直接使用,否则该函数会试图创建它的一份拷贝。通过使用模板函数 boost::ref(),像 std::cout 这样的流就可以被以引用方式传递,也就可以成功编译上面这个例子了。要以引用方式传递常量对象,可以使用模板函数 boost::cref()

03、Boost.Function

为了封装函数指针,Boost.Function 提供了一个名为 boost::function 的类。它定义于 boost/function.hpp,用法如下:

#include <iostream>#include <vector>#include <algorithm>#include <iostream>#include <cstdlib>#include <cstring>​#include <boost/ref.hpp>#include <boost/bind.hpp>#include <boost/function.hpp>​int main(){    //  const char* 的参数 返回一个类型为 int 的值    boost::function<int (const char*)> f = std::atoi;    // 字符串转为整形    std::cout << std::atoi("1609") << std::endl;    // f->atoi    std::cout << f("1609") << std::endl;​    // f->strlen    f = std::strlen;​    // std::strlen("1609");    std::cout << std::strlen("1609") << std::endl;    std::cout << f("1609") << std::endl;​    return 0;}// 输出06:03:24: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...1609160944
boost::function 可以定义一个指针,指向具有特定签名的函数。以上例子定义了一个指针 f,它可以指向某个接受一个类型为 const char* 的参数且返回一个类型为 int 的值的函数。定义完成后,匹配此签名的函数均可赋值给这个指针。这个例程就是先将 std::atoi() 赋值给 f,然后再将它重赋值为 std::strlen()。注意,给定的数据类型并不需要精确匹配:虽然 std::strlen() 是以 std::size_t 作为返回类型的,但是它也可以被赋值给 f。因为 f 是一个函数指针,所以被赋值的函数可以通过重载的 operator()() 操作符来调用。取决于当前被赋值的是哪一个函数,在以上例子中将调用 std::atoi() 或 std::strlen()。如果 f 未赋予一个函数而被调用,则会抛出一个 boost::bad_function_call 异常。
#include <boost/function.hpp>#include <iostream>​int main(){    try    {        boost::function<int (const char*)> f;        f("");    }    catch (boost::bad_function_call &ex)    {        std::cout << ex.what() << std::endl;    }}// 输出06:08:11: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...call to empty boost::function
注意,将值 0 赋给一个 boost::function 类型的函数指针,将会释放当前所赋的函数。释放之后再调用它也会导致 boost::bad_function_call 异常被抛出。要检查一个函数指针是否被赋值某个函数,可以使用 empty() 函数或 operator bool() 操作符。通过使用 Boost.Function,类成员函数也可以被赋值给类型为 boost::function 的对象。
#include <boost/function.hpp>#include <iostream>​struct world{  void hello(std::ostream &os){        os << "Hello, world!" << std::endl;    }};​int main(){    boost::function<void (world*, std::ostream&)> f = &world::hello;    world w;    f(&w, boost::ref(std::cout));}// 输出06:18:06: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...Hello, world!
在调用这样的一个函数时,传入的第一个参数表示了该函数被调用的那个特定对象。因此,在模板定义中的左括号后的第一个参数必须是该特定类的指针。接下来的参数才是表示相应的成员函数的签名。这个程序还使用了来自 Boost.Ref 库的 boost::ref(),它提供了一个方便的机制向 Boost.Function 传递引用。

04、Boost.lambda

匿名函数 - 又称为 lambda 函数 - 已经在多种编程语言中存在,但 C++ 除外。不过在 Boost.Lambda 库的帮助下,现在在 C++ 应用中也可以使用它们了。

lambda 函数的目标是令源代码更为紧凑,从而也更容易理解。以本章第一节中的代码例子为例。

#include <iostream>#include <vector>#include <algorithm>​void print(int i){    std::cout << i << std::endl;}​int main(){    std::vector<int> v;    v.push_back(1);    v.push_back(3);    v.push_back(2);​    std::for_each(v.begin(), v.end(), print);}
这段程序接受容器 v 中的元素并使用 print() 函数将它们写出到标准输出流。由于 print() 只是写出一个简单的 int,所以该函数的实现相当简单。严格来说,它是如此地简单,以致于如果可以在 std::for_each() 算法里面直接定义它的话,会更为方便;从而省去增加一个函数的需要。另外一个好处是代码更为紧凑,使得算法与负责数据输出的函数不是局部性分离的。Boost.Lambda 正好使之成为现实。
#include <boost/lambda/lambda.hpp>#include <iostream>#include <vector>#include <algorithm>​int main(){    std::vector<int> v;    v.push_back(1);    v.push_back(3);    v.push_back(2);​    std::for_each(v.begin(), v.end(), std::cout << boost::lambda::_1 << "\n");}// 输出06:22:42: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...132
Boost.Lambda 提供了几个结构来定义匿名函数。代码就被置于执行的地方,从而省去将它包装为一个函数再进行相应的函数调用的这些开销。与原来的例子一样,这个程序将容器 v 的所有元素写出至标准输出流。与 Boost.Bind 相类似,Boost.Lambda 也定义了三个占位符,名为 _1_2 和 _3。但与 Boost.Bind 不同的是,这些占位符是定义在单独的名字空间的。因此,该例中的第一个占位符是通过 boost::lambda::_1 来引用的。为了满足编译器的要求,必须包含相应的头文件 boost/lambda/lambda.hpp。虽然代码的位置位于 std::for_each() 的第三个参数处,看起来很怪异,但 Boost.Lambda 可以写出正常的 C++ 代码。通过使用占位符,容器 v 的元素可以通过 << 传给 std::cout 以将它们写出到标准输出流。虽然 Boost.Lambda 非常强大,但也有一些缺点。要在以上例子中插入换行的话,必须用 "\n" 来替代 std::endl 才能成功编译。因为一元 std::endl 模板函数所要求的类型不同于 lambda 函数 std::cout << boost::lambda::_1 的函数,所以在此不能使用它。下一个版本的 C++ 标准很可能会将 lambda 函数作为 C++ 语言本身的组成部分加入,从而消除对单独的库的需要。但是在下一个版本到来并被不同的编译器厂商所采用可能还需要好几年。在此之前,Boost.Lambda 被证明是一个完美的替代品,从以下例子可以看出,这个例子只将大于1的元素写出到标准输出流。
#include <boost/lambda/lambda.hpp>#include <boost/lambda/if.hpp>#include <iostream>#include <vector>#include <algorithm>​int main(){    std::vector<int> v;    v.push_back(1);    v.push_back(3);    v.push_back(2);​    std::for_each(v.begin(), v.end(),        boost::lambda::if_then(boost::lambda::_1 > 1,        std::cout << boost::lambda::_1 << "\n"));}// 输出06:28:06: Starting /home/whois/MyQProject/boost/build/Desktop_Qt_6_5_3_GCC_64bit-Debug/boost...32
头文件 boost/lambda/if.hpp 定义了几个结构,允许在 lambda 函数内部使用 if 语句。最基本的结构是 boost::lambda::if_then() 模板函数,它要求两个参数:第一个参数对条件求值 - 如果为真,则执行第二个参数。如例中所示,每个参数本身都可以是 lambda 函数。除了 boost::lambda::if_then(), Boost.Lambda 还提供了 boost::lambda::if_then_else() 和 boost::lambda::if_then_else_return() 模板函数 - 它们都要求三个参数。另外还提供了用于实现循环、转型操作符,甚至是 throw - 允许 lambda 函数抛出异常 - 的模板函数。虽然可以用这些模板函数在 C++ 中构造出复杂的 lambda 函数,但是你必须要考虑其它方面,如可读性和可维护性。因为别人需要学习并理解额外的函数,如用 boost::lambda::if_then() 来替代已知的 C++ 关键字 if 和 else,lambda 函数的好处通常随着它的复杂性而降低。多数情况下,更为合理的方法是用熟悉的 C++ 结构定义一个单独的函数。

总结

C++从入门到入坑课程免费分享,过了这个村就没这个店了。
【超级会员V1】通过百度网盘分享的文件:各厂商笔试面试题链接:pan.baidu.com/s/1lB7CjY3S… 即可获取」
下节不见不散,资源持续分享

故我在

点击下方卡片 关注我

↓↓↓