C++函数对象

一、函数对象概念
在c++中,我们把所有能当作函数使用的对象统称为函数对象。它是实现operator()的任何类型,此运算符被称为调用运算符,当调用此操作符时,其表现形式如同普通函数调用一般,因此取名叫函数对象。C++标准库主要使用函数对象作为容器和算法内的排序条件。在多线程编程中,主要使用函数对象来实现参数的传递。

二、函数对象的优势(相比普通函数)

  • 函数对象可包含状态
  • 函数对象是一个类型,因此可用作模板参数

三、函数对象分类
3.1 函数类
一个函数类,即重载了()操作符的类。当用该类的对象调用此操作符时,其表现形式如同普通函数一般,因此取名函数类。

//类
class FuncObjType
{
   
public:
	//重载()操作符
	void operator()()
	{
   
		cout<<"hello C++"<<endl;
	}
}
//普通函数
void val()
{
   
	cout<<"Hello C++!"<<endl;
}

int main()
{
   
	//实例化类对象
	FuncObjType val;
	//调用函数类
	val();
	//普通函数调用
	val();
}

**注:**类FuncObjType中重载了()操作符,因此对于该类对象val,可以调用该操作符val().该调用语句跟调用普通函数完全一样。

**疑问:**既然用函数对象与调用普通函数有相同的效果,为什么还要搞这么麻烦定义一个类来使用函数对象?(主要原因有以下几点)

  • 函数对象可以有自己的状态,我们可以在类中定义状态变量,这样一个函数对象在多次的调用中可以共享这个状态。但是函数调用没这种优势,除非它使用全局变量来保存状态。
  • 函数对象有自己特有的类型,而普通函数无类型可言。这种特性对于使用C++标准库来说是至关重要的。这样我们在使用STL中的函数时,可以传递相应的类型作为参数来实例化相应的模板,从而实现我们自己定义的规则和定制自己的算法,如排序算法。

3.2 Lambda
3.2.1 Lambda产生背景
使用 STL 时,往往会大量用到函数对象,为此要编写很多函数对象类。有的函数对象类只用来定义了一个对象,而且这个对象也只使用了一次,编写这样的函数对象类就有点浪费,而且,定义函数对象类的地方和使用函数对象的地方可能相隔较远,看到函数对象,想要查看其 operator() 成员函数到底是做什么的也会比较麻烦。对于只使用一次的函数对象类,能否直接在使用它的地方定义呢?Lambda 表达式能够解决这个问题。使用 Lambda 表达式可以减少程序中函数对象类的数量,使得程序更加优雅。C++11的一大亮点就是引入了Lambda表达式。利用Lambda表达式,可以方便的定义和创建匿名函数。Lambda表达式通过在最前面的方括号[ ]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。类似参数传递方式(值传递、引入传递、指针传递),在Lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获。

3.2.2Lambda表达式的声明
格式如下:

//完整声明格式
[capture list] (params list) mutable exception-> return type {
    function body }

//忽略某些成分的声明格式
[capture list](params list)->return type{
   function body} //格式1
[capture list](params list){
   function body} //格式2
[capture list]{
   function body} //格式3

各项具体含义:

  • capture list: 捕获外部变量列表
  • params list: 形参列表
  • mutable: 用来说明是否可以修改捕获的变量
  • exception: 异常设定
  • return type: 返回类型
  • function body:函数体

格式说明:

  • 格式1声明了const类型的表达式,这种类型的表达式不能修改捕获列表中的值
  • 格式2省略了返回值类型,但是编译器根据以下规则能够推断出Lambda表达式的返回类型。(1)如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定.(2)如果function body中没有return语句,则返回值为void类型。
  • 格式3中省略了参数列表,类似普通函数中的无参函数。

示例:


#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
 
bool cmp(int a, int b)
{
   
    return  a < b;
}
 
int main()
{
   
    vector<int> myvec{
    3, 2, 5, 7, 3, 2 };
    vector<int> lbvec(myvec);
 
    sort(myvec.begin(), myvec.end(), cmp); // 旧式做法
    cout << "predicate function:" << endl;
    for (int it : myvec)
        cout << it << ' ';
    cout << endl;
 
    sort(lbvec.begin(), lbvec.end(), [](int a, int b) -> bool {
    return a < b; });   // Lambda表达式
    cout << "lambda expression:" << endl;
    for (int it : lbvec)
        cout << it << ' ';
}

注:在C++11之前,我们使用STL的sort函数,需要提供一个谓词函数。如果使用C++11的Lambda表达式,我们只需要传入一个匿名函数即可,方便简洁,而且代码的可读性也比旧式的做法好多了。

3.2.3捕获外部变量
Lambda表达式可以使用其可见范围内的外部变量,但必须明确声明。Lambda表达式通过在最前面的方括号[]来明确指明其内部可以访问的外部变量,这一过程也称过Lambda表达式“捕获”了外部变量。

示例:

#include <iostream>
using namespace std;
 
int main()
{
   
    int a = 123;
    auto f = [a] {
    cout << a << endl; }; 
    f(); // 输出:123
 
    //或通过“函数体”后面的‘()’传入参数
    auto x = [](int a){
   cout << a << endl;}(123); 
}

**注:**上面这个例子先声明了一个整型变量a,然后再创建Lambda表达式,该表达式“捕获”了a变量,这样在Lambda表达式函数体中就可以获得该变量的值。

类似参数传递方式(值传递、引入传递、指针传递),在Lambda表达式中,外部变量的捕获方式也有值捕获、引用捕获、隐式捕获

(1)值捕获
值捕获和参数传递中的值传递类似,被捕获的变量的值在Lambda表达式创建时通过值拷贝的方式传入,因此随后对该变量的修改不会影响影响Lambda表达式中的值。

示例:

int main()
{
   
    
  • 5
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值