C++中的可调用对象

之前对C++中的多种可调用对象的使用总感觉很模糊,今天重新翻看了一下,总结成文如下。
       C++中有如下几种可调用对象:函数、函数指针、lambda表达式、bind对象、函数对象。其中lambda表达式和bind对象是C++11标准中提出的(bind机制并不是新标准中首次提出,而是对旧版本中bind1st和bind2st的合并)。个人认为五种可调用对象中,函数和函数指针本质相同,而lambda表达式、bind对象及函数对象则异曲同工。下面分别给出五种调用的使用:
1、函数。保证了对C的兼容。

2、函数指针。和数组名一样,函数名即为函数指针。

void fff(int x){  //被调用的函数
	cout<<x<<endl;
}
void fcn(void (*fp)(int ),int x){  //形参为函数指针
	fp(x);
}
typedef void (*Ftype)(int ); //定义一个函数指针类型Ftype
void fcn0(Ftype fp,int x){ 
	fp(x);
}
int main(){
	fcn(fff,100);  //函数fcn调用函数fff
	fcn0(fff,200); //函数fcn0调用函数fff
}
3、lambda表达式。lambda表达式就是一段可调用的代码。主要适合于只用到一两次的简短代码段。由于lambda是匿名的,所以保证了其不会被不安全的访问。lambda表达式的形式如下:
[捕获列表](参数列表)->尾置返回类型{ 执行体 }
  捕获列表用于接收lambda表达式所在函数中的局部变量,如果要在执行体中用到这些变量,必须在捕获列表中指定。捕获列表只用于局部非static变量,对于局部static变量和它所在函数之外声明的全局的名字,lambda表达式可以直接使用。捕获列表中既可以进行值传递,也可以进行引用传递,需要注意的是:在进行值传递的时候是在lambda表达式创建的时候拷贝值,而不是在调用的时候拷贝值,见下例:
void fcn1(){
	size_t v1=42;
	auto f=[v1]{ return v1;};//创建lambda表达式,如果参数列表为空,可以省去() 
	v1=0;//改变v1的值
	auto j=f(); //调用lambda表达式,j的值为42 
}
        如果执行体内只有单一的return语句,那么lambda表达式可以不指明返回类型,但如果执行体内还有其他语句,那么lambda语句必须指明返回类型,并且返回类型必须尾置。

        另外,lambda的捕获列表在一定程度上弥补了标准库中某些算法在使用谓词上的不便。例如标准库中find_if算法的第三个参数接收一个一元谓词,因此传递给find_if的可调用对象必须接收单一参数。现在有这样一个任务,words是一个存放了若干单词的vector<string>对象,现在要从中找出长度大于指定长度sz的第一个单词。见以下代码:

bool check(const string &a,string::size_type st){
	return a.size()>=st;
}
vector<string>::iterator first(const vector<string> &words,string::size_type st){
	auto wc=find_if(words.begin(),words.end(),check); //错误,check是二元谓词	
	auto wc=find_if(words.begin(),words.end(),[st](const string &a){ return a.size()>=st });//正确,这里的lambda表达式是一元谓词,只传递了一个参数	
	return wc;
}

      当编译器遇到lambda表达式时,编译器会为lambda表达式生产一个无名类的无名对象,类中重载了函数调用运算符,对应了lambda表达式的执行体,捕获列表中捕获到的值会在无名类中生产一个对应的private成员变量。上例中的lambda表达式对应的无名类见以下代码:

class 无名{
public:
	bool operator()(const string &a) const {
		return a.size()>=st;
	}
private:
	string::size_type st;
}
       无名类中的函数调用运算符默认是const的,因此lambda表达式默认是不能对捕获列表中捕获的变量进行修改的,如果想修改,必须在lambda表达式的参数列表之后和执行体之前加上mutable关键字。见下例:
void fcn(){
	int v=100;
	auto f=[v](){ return ++v; }//错误,不能修改v
	auto f=[v]()mutable { return ++v; }//正确,可以修改v 
	v=0;
	int j=f();//j为101 
}

4、bind函数。定义在头文件functional中,可以将bind函数看做一个通用的函数适配器,它生产一个可调用对象来“适应”原对象的参数列表。bind的一般调用形式如下:

auto newCallable = bind( callable,arg_list )

newCallable是由bind生成的新的可调用对象,callable是原对象,arg_list参数列表对应了原对象的参数列表。

例如对于之前的find_if的第三个参数,我们也可以用bind函数来书写:

auto wc=find_if(words.begin(),words.end(),bind(check,_1,st));
说明:_1是占位符,表明新生产的可调用对象的第一个参数对应了原对象check的第一个参数。st作为check的第二个参数。对于与句:auto g = bind(f,a,b,_2,c,_1);bind将函数g(_1,_2)映射为f(a,b,_2,c,_1)。


5、函数对象。重载了函数调用运算符的类的对象即为函数对象。


你可能感兴趣的:(lambda,函数对象,可调用对象,bind对象)