之前有吐槽过 C++ STL 算法中的
for_each
函数华而不实, 主要原因是受限于 C++ 的函数编写语法, 即传统上 C++ 要求函数须独立编写, 而不能嵌套在其它函数内. 不过, 欢迎来到二十一世纪的第一个 0x10 年, 现在有了 lambda.与其它语言一样, C++ 的 lambda 就是个匿名函数, 它的语法结构如
[ 上下文变量绑定方式 ] ( 形式参数列表 ) 可省略的返回值类型声明
{
函数体
}
// 定义变量 sqr 为一个匿名函数
// 这个匿名函数的上下文变量绑定方式为空, 因为它无须引入任何上下文变量
// 这个匿名函数接受一个 int 作为参数
// 返回值类型声明省略, 由编译器推倒出返回 int
auto sqr = [](int x)
{
return x * x;
};
int x = 10;
// 这个匿名函数的上下文变量绑定方式为 & 即引用方式, 它引用了上文中定义的 x
// 这个匿名函数接受一个 int 作为参数
// 返回值类型声明为 bool
auto equalsToX = [&](int y) -> bool
{
return x == y;
};
// 这个匿名函数的上下文变量绑定方式为 = 即复制方式, 它引用了上文中定义的 x
// 这个匿名函数接受一个 int 作为参数
// 返回值类型声明省略
auto lessThanX = [=](int y)
{
return y < x;
};
作为 C++ 程序员的一个特异能力便是能够在写代码时就预见到这段程序运行时内存与对象的流动. 看了上面匿名函数的各种奇葩绑定上下文的方法, 很明显它们不仅是一个函数那么简单, 如果必要匿名函数会占用一些内存空间, 可以做这样一个实验来试试.
struct Point {
Point()
: x(0)
, y(0)
{}
double x;
double y;
};
int main()
{
Point p;
auto f = [&]()
{
std::cout << p.x << "," << p.y << std::endl;
};
auto g = [=]()
{
std::cout << p.x << "," << p.y << std::endl;
};
std::cout << sizeof(f) << " - " << sizeof(Point*) << std::endl;
std::cout << sizeof(g) << " - " << sizeof(Point) << std::endl;
auto sqr = [](int x)
{
return x * x;
};
std::cout << sizeof(sqr) << std::endl;
return 0;
}
p
时, 匿名函数占用的空间与一个指针占用的内存空间相同; 而以复制方式绑定时, 匿名函数占用的空间与一个 Point
变量占用的空间相同. 也就是说匿名函数与其说是像一个函数, 反而更类似于一个语法上简化的, 重载了 operator()
的类型的实例对象.相应地, 正如不要返回局部变量的引用或指针这一 C++ 血的禁忌一样, 既然匿名函数也视作对象, 那么将匿名函数作为返回值传递时, 一方面不能返回匿名函数自身的引用, 另一方面如果匿名函数以引用方式绑定了上下文中的栈内对象, 那么在函数返回后调用该匿名函数也很可能将内存君玩坏掉.
那么为什么最后输出
sizeof(sqr)
会得到 1 而不是 0 呢? sqr
可没有绑定任何局部变量, 一个 bool
一个 char
都没拾人牙慧. 这又是 C++ 黑魔法另一个规定了: 任何 C++ 对象至少要占用 1 字节.匿名函数单独看起来可能并没什么了不起的, 但绝对是 STL 团战的超强辅助, 有了匿名函数, STL 算法中的那些函数再也不会心高手残了. 如
std::vector<Point> some_points /* = ??? */;
// 找到一个位于第一象限的点
auto iter = std::find_if(some_points.begin(), some_points.end(),
[&](Point const& p)
{
return p.x > 0 && p.y > 0;
});
std::cout << iter->x << "," << iter->y << std::endl;
// 遍历输出所有点
std::for_each(some_points.begin(), some_points.end(),
[&](Point const& p)
{
std::cout << p.x << "," << p.y << std::endl;
});