读代码的时候遇到了一些新东西,以前从没见过的语句和使用方式,惊觉 C++ 标准都扩展到 C++17 了,然而我以前写 ACM 的时候甚至连 STL 都很少用。
后来倒是陆续用过 queue 和 bitset 这样的黑科技。
这里陆续补充一点新东西的学习记录吧。
std::function
std::function 类模板是一种通用的函数封装,这个模版的实例可以对任何可以调用的目标 进行存储、复制、和调用操作,包括函数、lambda 表达式、绑定表达式、以及其它函数对象等。
直接看代码示例吧,这一段来自上面的 cppreference。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <functional> void print_num (int n) { std::cout << n << std::endl; } struct foo { foo (int n) : num (n) {} void print_add (int n) const {std::cout << num + n << std::endl;} int num; }; struct Print_num { void operator () (int n) const { std::cout << n << std::endl; } }; int main () { std::function<void (int )> f_display = print_num; f_display (100 ); std::function<void (int )> f_display_lambda = [](int n) { print_num (n);}; f_display_lambda (200 ); std::function<void ()> f_display_bind = std::bind (print_num, 300 ); f_display_bind (); std::function<void (const foo&, int )> f_add_display = &foo::print_add; foo new_foo (400 ) ; f_add_display (new_foo, 12 ); std::function<void (int )> f_add_display2 = std::bind (&foo::print_add, new_foo, std::placeholders::_1); f_add_display2 (123 ); std::function<void (int )> f_add_display3 = std::bind (&foo::print_add, &new_foo, std::placeholders::_1); f_add_display3 (234 ); std::function<void (int )> f_display_obj = Print_num (); f_display_obj (700 ); return 0 ; }
Lambda 表达式
这个东西的官方定义是能构造一个闭包:是能够捕获作用域中变量的无名函数 。
简单地说可以看成是个匿名函数,写法就是函数的结构,只是没有名字。
详细的语法:
1 2 3 4 [ capture-list ] ( params ) mutable(可选) constexpr(可选)(C++17) exception attribute -> ret { body } [ capture-list ] ( params ) -> ret { body } [ capture-list ] ( params ) { body } [ capture-list ] { body }
基本上是三个部分组成,[]
中是捕获列表,()
中是函数的输入参数(跟普通的函数是一个意思,空的就是没有输入参数),->
后面可以跟函数返回值的类型,不加的话可以自动推断,{}
中是函数体(跟正常函数一样)。
这个结构其实跟普通的函数基本是一致的。
具体的看示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 /* *********************************************** MYID : Chen Fan LANG : G++ -std=c++11 PROG : Lambda ************************************************ */ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> #include <vector> #include <ctime> int main() { srand(time(NULL)); std::vector<int> a(10); /* 这里是一个 std::function 跟 Lambda 表达式的例子 */ std::function<void()> aout = // Lambda 函数体中用到了 a 这个 vector,需要“从外界捕获到闭包内”,大致可以这么理解。 // a 前面加 & 是引用捕获,闭包内的 a 和闭包外的 a 在内存里面是一样的。 // 如果不加就是值捕获,也就是把外面的 a 在当前位置的值传进去。 // 注意当前位置这个概念很重要,用下面那个不带 & 的来测试会发现后面输出的全都是 0。 // 把这个 Lambda 表达式的定义换个位置试试就明白了。 //[a]() [&a]() { for (auto i : a) std::cout << i << std::endl; }; //----------- std::generate(a.begin(), a.end(), // 一个不需要捕获也没有输入参数的函数。 []() { return rand() % 100; }); // std::function 的调用例子,函数模版加上 Lambda 表达式之后其实可以看成是个普通的函数。 aout(); int sum = 0; std::for_each(a.begin(), a.end(), // 需要引用捕获 sum,并且要把 vector 中每个元素作为参数传进去。 // 如果不加 & 但是又在函数体中改了捕获变量的内容会发生 read-only 错误。 [&sum](int i) { sum += i; }); std::cout << "Sum: " << sum << std::endl; //----------- int step = 2; int i = 0; std::generate(a.begin(), a.end(), // 后两个都是对的,捕获列表里面可以加多个,不同变量可以用不同的捕获方式。 // 直接写 & 则 Lambda 表达式会自动推断把函数体中出现的变量全用引用捕获抓进来。 //[i, step]() //[&i, step]() [&, step]() { i += step; return i; }); aout(); std::cout << "i: " << i << std::endl; step = 3; i = 0; std::generate(a.begin(), a.end(), // 直接写 = 则 Lambda 表达式会自动推断把函数体中的变量用值捕获抓进来。 // mutable 的意思是让捕获过来的值引用变量在 Lambda 表达式的范围内可变。 // 但是在全局范围内无影响,因为毕竟还是值引用,相当于在 Lambda 的作用范围内加了个成员变量。 // 对比下面输出来的 i 的值就明白了。 //[i, step]() mutable [=]() mutable { i += step; return i; }); aout(); std::cout << "i: " << i << std::endl; //----------- step = 4; i = 0; std::function<int()> addstep = [&]() { i += step; return i; }; std::generate(a.begin(), a.end(), addstep); aout(); return 0; }
我刚开始遇到 Lambda 表达式是在 TensorFlow 的代码里面,弄明白上面这一串之后,回到 TensorFlow 的代码里,抽出来了这样一段看上去有点诡异的代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 /* *********************************************** MYID : Chen Fan LANG : G++ -std=c++11 PROG : special Lambda ************************************************ */ #include <iostream> #include <cstdio> #include <cstring> #include <algorithm> // Closure 是一个函数模版,表示一个返回值为空的函数 typedef std::function<void()> Closure; // Runner 也是一个函数模版,返回值为空,输入参数是上面那个 Closure typedef std::function<void(Closure)> Runner; Closure closure_; Runner runner_; int main() { // closure_ 可以直接赋值成一个返回值和输入参数都为空的 Lambda 表达式 closure_ = [](){ printf("aaa\n");}; // runner_ 赋值的是一个输入参数为 Closure 的 Lambda 表达式 runner_ = [](Closure f){ f();}; // runner_ 本身是一个函数,可以直接调用,输入参数是一个 Closure // 这里的 Closure 可以直接是一个符合 Closure 格式的 Lambda 表达式,因为 Closure 本身就是个函数模版 runner_([](){ printf(".....\n");}); // 也可以像这样输个 Closure 类型的参数进去 runner_(closure_); return 0; }
std::bind bind 用于把一些参数跟一个调用进行绑定起来,包括函数对象,函数指针,引用函数,成员函数的指针,指针到成员数据等等。
主要是看代码的时候看到了个占位符,不太明白,所以记一下。
还是先上代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 #include <random> #include <iostream> #include <functional> void f (int n1, int n2, int n3, const int & n4, int n5) { std::cout << n1 << ' ' << n2 << ' ' << n3 << ' ' << n4 << ' ' << n5 << '\n' ; } int g (int n1) { return n1; } struct Foo { void print_sum (int n1, int n2) { std::cout << n1+n2 << '\n' ; } int data = 10 ; }; int main () { using namespace std::placeholders; int n = 7 ; auto f1 = std::bind (f, _2, _1, 42 , std::cref (n), n); n = 10 ; f1 (1 , 2 , 1001 ); f (2 , 1 , 42 , std::cref (n), n); std::cout << "---" << std::endl; auto f2 = std::bind (f, _3, std::bind (g, _3), _3, 4 , 5 ); f2 (10 , 11 , 12 ); f (12 , g (12 ), 12 , 4 , 5 ); std::cout << "---" << std::endl; std::default_random_engine e; std::uniform_int_distribution<> d (0 , 10 ); std::function<int ()> rnd = std::bind (d, e); for (int n=0 ; n<10 ; ++n) std::cout << rnd () << ' ' ; std::cout << std::endl << "---" << std::endl; Foo foo; auto f3 = std::bind (&Foo::print_sum, foo, 95 , _1); f3 (5 ); foo.print_sum (95 , 5 ); std::cout << "---" << std::endl; auto f4 = std::bind (&Foo::data, _1); std::cout << f4 (foo) << '\n' ; std::cout << foo.data << '\n' ; }
第一段的 f1 是 f 这个函数加上一些参数的封装。_1 和 _2 都是占位符,分别表示等一下输入的第一个和第二个参数。所以后面的例子中 f1(1, 2, ...)
就等于 f(2, 1, ...)
,bind 封装里面没有用到第三个占位符,所以 f1 中输入的 1001 是没有用的。
第二段是说在 bind 中再加个 bind,占位符是共享最外层的那个。f2 中输入的 10 和 11 无效,因为没有用到 _1 和 _2。
To be continued.