最近放假在写一个小项目,用到了闭包和匿名函数的知识,记录一下
What?
匿名函数:匿名函数(英语:Anonymous Function)在计算机编程中是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中。C++从C++11开始支持。
闭包:闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是在支持函数编程的编程语言中实现词法绑定的一种技术。闭包在实现上是一个结构体,它存储了一个函数(通常是其入口地址)和一个关联的环境(相当于一个符号查找表)
匿名函数只是一个没有名字的函数,闭包是一个函数指针+配套环境,闭包是一个函数的实例。
当匿名函数内也有外界定义的变量时就变成了闭包,当闭包没有外界变量时可以优化成一个普通函数(不一定是匿名函数)
why?
闭包的存在是为了保护私有变量不被污染,形成不销毁的栈内存,里面的私有变量等信息保存下来。
匿名函数常用于回调函数、事件处理器、或者在需要临时定义函数的地方。它们有助于减少代码的复杂性,使代码更加简洁。
how?
匿名函数是一种技术,只有lambda表达式一种实现。
闭包是一种思想,可以用不同方式来实现
方法一:重载()操作符
C++允许进行操作符重载,可以将一个类的()操作符重载,就可以使这个类的实例可以调用,Eg:
#include <iostream>class Operator {public:int operator ()(int a, int b) {return a+b+bias;}private:int bias = 1;
};int main() {Operator op;std::cout << op(1, 2) << std::endl;return 0;
}
好奇,能不能将这个重载声明为static,试了一下发现报错说运算符不能是静态成员函数。
方法二:lambda表达式
C++11中引入了lambda表达式,lambda是函数式编程中的概念,用于定义匿名函数,Eg:
#include <iostream>
#include <functional>int main() {int bias = 1;std::function<int(int)> f = [bias](int x) { return x + bias; };// lambda函数,bias是捕获的变量,x是参数,// 如果不捕获变量,可以写成[=],如果捕获所有变量,可以写成[&]// 不捕获变量时,就是一个普通的函数,捕获后就变成了闭包std::cout << f(1) << std::endl;return 0;
}
方法三:参数绑定
C++11在标准库中新增了bind函数,std::bind是C++中实现函数参数绑定的一种方式,它允许你创建一个可调用的函数对象,该对象可以存储一部分参数,并在需要时再提供剩余的参数。这在某些情况下非常有用,比如在多线程编程中,你可能需要将参数和函数传递给不同的线程。
C++11之前boost中有参数绑定函数bind,可以调用boost::bind函数
使用bind定义闭包如下:
#include <iostream>
#include <functional>int add(int a, int b) {std::cout << "a: " << a << " b: " << b << std::endl;return a + b;
}int main()
{auto f = std::bind(add, std::placeholders::_2, std::placeholders::_1);// placeholders可以理解为占位符,_1表示第一个参数,_2表示第二个参数,以此类推// 上面的代码表示将add函数的第二个参数作为f的第一个参数,第一个参数作为f的第二个参数// 所以调用f(1,2)时,实际上调用的是add(2,1)std::cout << f(1,2) << std::endl;auto f2 = std::bind(add, 1, std::placeholders::_1);// 上面的代码表示将add函数的第一个参数固定为1,第一个参数作为f2的第二个参数std::cout << f2(2) << std::endl;auto f3 = std::bind(add, std::placeholders::_1, 2);// 上面的代码表示将add函数的第二个参数固定为2,第一个参数作为f3的第一个参数std::cout << f3(1) << std::endl;auto f4 = std::bind(add, 1, std::placeholders::_2);// 上面的代码表示将add函数的第一个参数固定为1,第二个参数作为f4的第二个参数// 所以调用时必须给两个参数std::cout << f4(1,2) << std::endl;return 0;
}
使用时要注意C++不会因为闭包而延长变量的生命周期,在lambda中引用一个已经释放了的变量是一种未定义行为,文档原话:
If a non-reference entity is captured by reference, implicitly or explicitly, and operator() of the closure object is invoked after the entity’s lifetime has ended, undefined behavior occurs. The C++ closures do not extend the lifetimes of objects captured by reference.
others
- C语言
C语言中没有语法可以支持闭包,但是C语言支持回调函数,可以通过回调函数来达到类似于闭包的效果。
在C语言中,支持回调函数的库有时在注册时需要两个参数:一个函数指针,一个独立的void*指针用以保存用户数据。这样的做法允许回调函数恢复其调用时的状态。这样的惯用法在功能上类似于闭包,但语法上有所不同。 - Python
Python从一开始就支持闭包,在Python中经常用闭包来实现装饰器
import functools
import timedef logger(func):"""装饰器函数,用于记录函数的调用信息"""@functools.wraps(func) # 保持原始函数的名称和文档字符串def wrapper(*args, **kwargs):print(f"Calling {func.__name__} at {time.ctime()}")result = func(*args, **kwargs)print(f"{func.__name__} returned {result}")return resultreturn wrapper# 使用装饰器
@logger
def add(a, b):"""Add two numbers."""return a + b# 调用被装饰的函数
result = add(3, 4)