当接触到 C++ 之后,如果说它与 JS 相近的一个地方,那么便是看到了匿名函数,或者说 Lambda 表达式(后文简称 Lambda),只是 JS 当中不会这么去称呼。
为什么说痴迷这个东西,主要是 Lambda 可以捕获所在作用域当中的变量,而无需手动传递参数。虽然从某种角度上来说,显式传递更加符合一种编程风格。
然而不同的地方来了,先谈 JS,对于函数声明和 Lambda 来说,其实差别就在于声明的关键字和声明提升,其他是没有的。然而到了 C++ 这边,就不能这么玩了,最主要的区别就是:Lambda 自递归调用。
function main() {
const dfs = (n) => {
if (n === 0) {
return;
}
console.log(n);
dfs(n - 1);
};
}
int main() {
auto dfs = [](int n) {
if (n == 0) {
return;
}
cout << n << endl;
// Error
dfs(n - 1);
};
return 0;
}
若解释的话,在 C++ 当中这似乎是这样一个动作:先创建值,后又声明。于是无法达成完成这种操作。
那是否存在替代方案,当然,就是略显繁琐,但是可以达到目的:
int main() {
auto dfs = [](int n, auto dfs) {
if (n == 0) {
return;
}
cout << n << endl;
// OK
dfs(n - 1, dfs);
};
dfs(5, dfs);
return 0;
}
由此延申两个问题:
- 为什么函数可以递归调用
- JS 为什么可以这么写
请注意,此处说的函数是普通函数,而不再是 Lambda。
首先了解一点,为什么 Lambda 不可以自递归,那就是先初始化值,再有的名称,于是无法在函数体内找到自己。
或许这时候就联想到了一个叫 this 的东西。
而普通函数可以找到自己,那是来自函数声明,通常存在头文件当中:
void dfs(int);
void dfs(int) {
// ....
}
可能想说,自己直接写的实现,就没有写声明。当然,编译器发现没有声明时,它会自己做好声明工作的。
这样的话,让人有了一个想法,那就是先完成 Lambda 声明,然后再调用如何?
看一段想法落实:
void dfs(int);
void dfs(int) {
// ...
}
int main() {
void (* dfsFn)(int);
// OK
dfsFn = dfs;
return 0;
}
一个指针函数没有毛病,再进一步,尝试创建 Lambda:
int main() {
void (* dfsFn)(int);
// OK
dfsFn = [](int){
// ...
};
return 0;
}
好,开启捕获…失败了
int main() {
void (* dfsFn)(int);
// Error
dfsFn = [&](int){
// ...
};
return 0;
}
先别谈调用,变量类型就完全对不上了,导致计划破产。
一个关键问题是,无法显式,将一个 Lambda,可捕获变量的类型写出来,而在 STL 算法当中,多数是直接使用泛型盖过。
JS 为什么可以这么写先看 C++ 的一段表现,还是从 Lambda 开始:
int main() {
// Error
auto print = [&]() {
cout << num << endl;
};
int num = 10;
return 0;
}
别提使用了 Lambda,在声明时,使用到了一个,当时还未完成初始化的变量,于是直接就在编译阶段挂掉了,这很合理。
那么回头看 JS,就不用这么复杂了,直接在函数体内犯错即可:
function createErr() {
console.log(num);
const num = 10;
}
console.log(createErr)
由于 JS 不存在编译动作,于是,只要不调用函数,那么这个错误就不会产生,哪怕是输出了这个函数的内容。
回头看一眼 TS,增添了编译环节,这样的错误也就被扼杀掉了。
于是就好说了,那就是 JS 是执行到什么,就解释什么。于是,Lambda 创建过关了,虽然在创建的时候完全不对劲,但是,在使用时,只要 Lambda 能捕获到对应的变量,那么它就不会出现错误。
-END-



