C++中通过lambda表达式进行函数式编程

Lambda表达式

  C++11中的Lambda表达式用于定义并创建匿名的函数对象,是函数的一种,以简化编程工作(可替代所有函数指针的使用)

通过一道例题理解Lambda替代函数指针的过程

【例】数一个Vector里的偶数的个数和奇数的个数

函数指针写法

int Count(vector<int>& numbers, bool (*filter)(int)) {
    int cnt = 0;
    for (int x : numbers) {
        if (filter(x))
            cnt++;
    }
    return cnt;
}
bool Odd(int x) {
    return (x & 1) == 1;
}
bool Even(int x) {
    return (x & 1) == 0;
}
int CountOdds(vector<int>& numbers) {
    return Conut(numbers, &Odd);
}
int CountEvens(vector<int>& numbers) {
    return Conut(numbers, &Even);
}

Lambda表达式

template<typename U>
int Count(vector<int>& numbers, U filter) {
    int counter = 0;
    for (int x : numbers) {
        if (filter(x))
            counter++;
    }
    return counter;
}
int CountOdds(vector<int>& numbers) {
    return Count(numbers, [](int x) { return x % 2 == 1; })
}
int CountOdds(vector<int>& numbers) {
    return Count(numbers, [](int x) { return x % 2 == 0; })
}

  比较两者的编程方式,可以发现Lambda表达式把判断奇偶的函数以[](int x) { return x % 2 == 1; }这样的写法表示出来了。

分析Lambda表达式

  Lambda表达式类型是没有名字的即不同的Lambda表达式具有不同的类型,因此通常使用模板来代替它(模板这里我没搞懂 )。可以归纳出以上Lambda表达式的写法:
  [捕获](参数)->返回值类型 {实现函数功能的语句}其中(参数)是Lambda表达式的参数,->返回值类型可省略因为上面的代码使用了模板。此外,书上讲到了mutable->返回值类型这样的写法,我没有书,具体怎么操作的我还得深入学习,只要能看懂就好了
  使用模板可以让参数接受不同的Lambda表达式,从而在不同的时候这个参数具有不同的类型。(我理解不了就先记住 )模板最简单的功能:

template<typename U>

  声明一个模板参数,名字是U,用来代表一个暂时无法确定的类型。

int Count(vector<int>& numbers, U filter)

  不用管filter参数类型,可接受Lambda表达式。
  捕获
[]标识一个Lambda的开始,当然不能省略。函数对象参数是传递给编译器自动生成的函数对象类的构造函数的,方括号里可以写多项,用逗号分开;也可以是空。单项一般可以是如下几种:
  =:可以使用Lambda所在作用范围内所有的可见的局部变量,需要的值都复制(值传递方式)。例:

QPushButton *btn = new QPushButton("aaa", this);
[=](){
    btn2->setText("bbb");
}();

把aaa改成bbb的功能。其中[=](){}是一个函数,要调用的时候就要写成[=](){}()形式。若方括号里没有=,则不能实现以上功能。因为在函数体中找不到局部成员btn2,=的作用就是在函数外找到btn2.
  &:需要的的值都复制它们的引用(引用传递方式),不仅能“读”,还能修改。上面的=换成&最后的结果也是一样的。
  this:使用Lambda所在类中的成员变量(没怎么见过)。
  其他的方括号里要么省略,要么可以有以上几种的组合,比如[&, a]的意思是a进行值传递,除a以外的进行引用传递,再比如[&a]的意思是单个a进行引用传递。

通过程序来理解Lambda表达式的使用

【例】写一个可以递归的Lambda表达式:斐波那契数列

#include <iostream>
#include <functional>
using namespace std;
int main()
{
    function<int(int)> fib = [&fib](int i)->int {
        if (i < 2) {
            return 1;
        } else {
            return fib(i - 1) + fib(i - 2);
        }
    }
    for (int i = 0; i < 10; i++) {
        cout << fib(i) << endl;
    }
    return 0;
}

  让Lambda表达式看到fib就要在[]里加东西,有值传递和引用传递两种方式,随便选一种。
  fib变量的类型需要通过编译Lambda表达式来确定,而这个表达式需要捕获自己,形成了一个死循环,因此需要给fib变量一个明确的类型,于是用到了std::function<函数的实际类型(不是指针)>。Lambda表达式fib中有一个int参数,并且返回int,因此函数的实际类型是int(int)

std::function的在Lambda表达式中的使用

  一般来说,建议使用模板参数代表函数,从而使用Lambda表达式。而std::function一般都是非要把Lambda表达式保存下来才使用,如存到一个类或vector里。

阅读代码找出功能来理解

【例1】构造一个Adder函数,Adder(x)的作用是返回一个函数,给一个整数加上x

  Adder函数表达成代码:

auto f = Adder(1);
cout << f(2) << endl;//输出3

  分析:C++的语法没办法让我们拿到一个整数x就立即生成一个函数。Lambda表达式的类型没有名字 (就不能用auto了因为不能根据后面的值来推测出其类型),因此返回值是std::function。输入一个整数返回一个整数,于是返回类型是int(int)

function<int(int)> Compose(function<int(int)> f, function<int(int)> g) {
    return [=](int y){ return f(g(y)); };
}
int main() {
    auto adder = Composer(Adder(1), Adder(2));
    cout << adder(3) << endl;
    return 0;
}//输出结果6

【例2】用 Lambda 表达式,补全First和Second的内容(已用//TODO标出),这两个函数都只有一个参数——这个参数的值即为Pair的返回值。在函数体中,两个函数会分别获取并返回Pair的第一个值和第二个值(即分别为u和v)。

#include <iostream>
#include <functional>
using namespace std;
auto Pair = [](auto u, auto v) {
	return [=](auto f) {
		return f(u, v);
	};
};
auto First; //TODO
auto Second; //TODO
void PrintAll(nullptr_t) {

}
template<typename T>
void PrintAll(T t) {
	cout << First(t) << endl;
	PrintAll(Second(t));
}

int main()
{
	auto t = Pair(1, "two");
	auto one = First(t);
	auto two = Second(t);
	cout << one << ", " << two << endl;
	auto numbers = Pair(1, Pair(2, Pair(3, Pair(4, Pair(5, nullptr)))));
	PrintAll(numbers);

    return 0;
}

样例输出
1, two                                     
1
2
3
4
5

  取第一个值和第二个值就是要分别返回Pair中的u和v,Pair函数和First、Second是通过在主函数中使用PrintAll来联系的。
  上面的Pair函数具体实现过程我没看懂以及上面的PrintAll(nullptr_t)空的没有任何实现功能,我也不知道写出来有什么样的语义(标记一下日后补充说明 )
  其实First和Second的任务分别就是要返回一个二元函数的第一个参数和第二个参数,返回什么二元函数根本就不重要,因为函数式编程特点:auto根据后面的值来推测出变量类型(自己的理解极有可能是错的表达 )。因此在这两个Lambda表达式中构造一个返回一个二元函数第几个参数值功能的函数即也用Lambda表达式来实现:

auto First = [](auto p) {
    return p([](auto u, auto v) {
        return u;
    });
};
auto Second = [](auto p) {
    return p([](auto u, auto v) {
        return v;
    });
};

总结

  目前阶段我能看懂就很不错了,后面深入学习并且掌握会编程之后再来完善、修改、补充该笔记。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值