越来越多的迹象表明,也许继面向对象编程之后,函数式编程会成为下一个编程的主流范式(只是有可能 Maybe)。
说到函数式编程编程就不得不说面向对象。
面向对象是把一个功能的一组操作和相关数据封装在一个对象里,即对象满天飞。函数式编程是把一个功能的一个操作和相关数据封装在一起,即函数满天飞。函数式编程比面向对象的优势就是粒度更小,生命周期更短。减少bug的有效途径就是减少变量的生命周期,缩小模块的粒度;这也可能是函数式编程能够活下来的原因之一吧。
是一种编程范型,它将计算机运算视为数学上的函数计算,并且避免使用程序状态以及易变对象。λ演算,即lambda calculus。
函数式编程强调程序的执行结果比执行过程更重要。关注于描述问题,而不是怎么实现,隐藏实现细节。
化繁为简。
面向对象编程有三大特性:封装、继承、多态。函数式编程存在自身特性。
纯函数(referential transparency) :函数式编程中的函数是纯函数,即不会修改任何外部状态,也不会产生副作用。纯函数只依赖于输入参数,输出结果也只与输入参数有关,因为调用函数的结果具有一致性,所以根本不需要加锁,也就不存在死锁的问题。也因此具有可重复性和可测试性。
不可变性:保证了程序是无状态的,很多难解的bug往往是由各种复杂的状态引起的。比如发现某些情况下程序运行有问题,是某一个状态引起的,但是这个状态有1000种可能性,在10000个地方都有对这个状态进行操作。数据不可变性同时保证了函数没有“副作用”。
高阶函数:函数式编程中的函数可以作为参数传递给其他函数,也可以作为返回值返回。这种函数作为一等公民(first class method)的特性,使得函数式编程具有更高的抽象能力和灵活性。
延迟计算:函数式编程中的计算是延迟计算的,即只有在需要结果时才进行计算。这种特性可以提高程序的效率和性能,也可以避免不必要的计算和资源浪费。
函数组合:函数式编程中的函数可以通过组合来创建更复杂的函数。这种特性可以提高程序的可读性和可维护性,也可以避免代码重复和冗余。
倒序递归化(tail call optimization):函数调用要压栈保存现场,递归层次过深,压栈过多会引发性能问题。所以引入倒序递归优化,每次递归时都会重用栈,提升性能。
函数式编程的特性使得它在处理复杂问题时具有更高的抽象能力和灵活性,也可以提高程序的可读性、可维护性和可测试性。因此,在一些需要处理大量数据和并发访问的场景中,函数式编程已经成为了一种重要的编程范式。
在C++中,lambda表达式可以用于以下场景:
std::vector vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
std::sort(vec.begin(), vec.end(), [](int a, int b) { return a < b; });
这里使用lambda表达式定义了一个简单的函数对象,用于对vector进行排序。
std::vector vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
std::for_each(vec.begin(), vec.end(), [](int x) { std::cout << x << " "; });
这里使用lambda表达式定义了一个简单的函数,用于输出vector中的每个元素。
std::thread t([]() { std::cout << "Hello, world!" << std::endl; });
t.join();
注意:这里 g++ 编译需要加 -pthread 参数
除了上述提到的场景,lambda表达式在C++中还可以用于以下场景:
std::vector vec = {3, 1, 4, 1, 5, 9, 2, 6, 5, 3};
auto it = std::find_if(vec.begin(), vec.end(), [](int x) { return x > 5; });
auto index = std::distance(vec.begin(), it);
这里使用lambda表达式定义了一个简单的函数对象,用于查找vector中第一个大于5的元素,并返回其下标。
auto add = [](int a, int b) { return a + b; };
auto add5 = std::bind(add, std::placeholders::_1, 5);
std::cout << add5(3) << std::endl; // 输出8
这里使用lambda表达式定义了一个简单的加法函数对象,然后使用std::bind进行柯里化,得到一个新的函数对象,用于将任意一个数加5。
总之,lambda表达式在C++中可以用于很多场景,可以提高代码的简洁性和可读性。但是,在编写复杂的函数时,应该使用命名函数来提高代码的可维护性和可读性。