C++11学习笔记2

std::function std::bind

我们知道在C里面有函数指针这么回事,我们用函数指针的目的就是将仿函数作为参数,传递给另外一个函数,并供他调用。但是显然,函数指针那种写法还是相当恶心的,比如:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
int callback(int x){
std::cout<<x<<std::endl;
}
int func(int(*f)(int)){
f(3);
}
int main(){
func(callback);
}

而C++11里提供了std::function这个工具来封装函数指针,上面的写法就可以变成下面的:

1
2
3
4
5
6
7
8
9
10
11
#include<iostream>
#include<functional>
int callback(int x){
std::cout<<x<<std::endl;
}
int func(std::function<int(int)>f){
f(3);
}
int main(){
func(callback);
}

用函数模板来体现作为参数的函数类型,看起来更优雅方便。

但是有时候,我们可能希望这个function能够绑定一些参数,那么就需要用到std::bind这个工具来封装旧的function,并返回新的function:

1
2
3
4
5
6
7
8
9
#include<iostream>
#include<functional>
int callback(int x1,int x2){
...
}
int main(){
std::function<int(int)> binded=std::bind(callback,10,std::placeholders::_1);
binded(3);
}

上面的意思是将int(int,int)这个函数封装成了int(10,int)这个函数并起名为binded,std::placeholders::_1表示的就是在调用时传入的第一个参数(这里是3)。非常好理解。

使用std::function和std::bind可以非常装逼的组合多个函数,非常具有模块化的思想,比如下面的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
#include<vector>
#include<functional>
#include<algorithm>
int main(){
int low=1;
int high=10;
std::function<bool(int)> judge=std::bind(
std::logical_and<bool>(),
std::bind(std::greater_equal<int>(),std::placeholders::_1,low),
std::bind(std::greater_equal<int>(),high,std::placeholders::_1)
);
std::vector<int>v={1,2,9,10,11};
int count = std::count_if(v.begin(),v.end(),judge);
std::cout<<count<<std::endl;
}

其实就是实现了统计[low,high]之间的数字个数的功能。。。

lambda表达式

lambda表达式我认为说白了就是一种匿名函数的简写形式,是一种仿函数的语法糖,可以看成是一个std::function对象,没什么稀奇的。

基本写法
基本写法如下:

1
[capture] (params) opt -> ret {body;};

比如下面的函数fun1、fun2就是等价的:

1
2
3
4
5
6
7
int callback(int x,int y){
return x+y;
}
int main(){
std::function<int(int,int)> fun1=callback;
std::function<int(int,int)> fun2=[](int x,int y)->int{return x+y;};
}

捕获外部变量
为了保证良好的封装性,lambda并不能随意访问非参数外的其他变量,这些变量的访问权限交由[]来控制,具体用法如下:

  • []不捕获任何变量
  • [&]按引用捕获所有变量
  • [=]按值捕获所有变量
  • [=,&foo]按值捕获所有变量,并按引用捕获foo变量
  • [foo]按值捕获foo变量,不捕获其他变量
  • [this]捕获当前类中的this指针

比如下面的例子就是按值捕获了外部变量x,如果使用[],那么编译会报错。

1
2
3
4
5
6
#include<iostream>
int main(){
int x=10;
auto f=[=](){std::cout<<x;};
f();
}

需要注意的是,捕获的过程是在函数定义的时候就发生的,在捕获时就会复制一个新的变量,因此我们注意到下面的例子:

1
2
3
4
5
6
7
#include<iostream>
int main(){
int x=10;
auto f=[x](){std::cout<<x;};
x=9;
f(); //the output is 10
}

如果我们需要即时的捕获外部变量,我们就需要按引用捕获。

mutable选项
由于C++的规定,lambda表达式的operator()是const的,也就是说我们按值捕获的值默认是const的,因此我们对按值捕获的值是无法修改的,这就很蛋疼了?这时候就用到了mutable选项来取消const的限制了:

1
2
3
4
5
6
#include<iostream>
int main(){
int x=10;
auto f1=[x]()mutable{x++;};//编译正确
auto f2=[x](){x++;};//编译错误
}

简单应用
有了lambda函数,很多事情就变的简单了,比如之前的统计一定范围内数字个数的程序就可以修改成下面这样:

1
2
3
4
5
6
7
8
9
10
#include<iostream>
#include<vector>
#include<algorithm>
int main(){
int low=1;
int high=10;
std::vector<int>v={1,2,9,10,11};
int count = std::count_if(v.begin(),v.end(),[low,high](int x){return x>=low&&x<=high;});
std::cout<<count<<std::endl;
}

tuple元组

没想到C++里面竟然也有元组,元组这个东西其实就是一个简化的结构体,方便我们将不同的数据进行打包,跟python中的用法类似:
创建元组

1
2
3
4
5
6
7
#include<iostream>
#include<tuple>
#include<string>
int main(){
std::tuple<std::string,int> t1("df",324);
auto t2=std::make_tuple(3432,"dsf");
}

两种方法,一种是构造函数,一种是make_tuple函数。

提取元素

1
2
3
4
5
6
7
8
9
10
11
12
#include<iostream>
#include<tuple>
#include<string>
int main(){
std::tuple<std::string,int> t1("df",324);

std::cout<<std::get<1>(t1)<<std::endl;

int d;
std::tie(std::ignore,d)=t1;
std::cout<<d<<std::endl;
}

两种方法,一种是get函数,可以指定提取的元素位置,另一种是std::tie方法,可以一次提取所有的元素也可以提取某一个元素,其他位置用std::ignore占位。
如果忘记了tuple的大小,我们可以使用tuple_size来获得tuple的大小:

1
2
3
4
5
6
7
#include<iostream>
#include<tuple>
int main(){
std::tuple<int,int> t(23,43);

std::cout<<std::tuple_size<decltype(t)>::value;
}

元组连接

1
2
3
4
5
6
7
8
9
#include<iostream>
#include<tuple>
#include<string>
int main(){
std::tuple<std::string,int> t1("df",324);
std::tuple<int,int> t2(23,43);

auto t3=std::tuple_cat(t1,t2);
}

通过tuple_cat来连接两个tuple。

参考资料

深入应用c++11
C++FAQ
cppreference–tuple