`
RednaxelaFX
  • 浏览: 3016259 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

关于变量,作用域与闭包;也再讨论一下C++0x的lambda表达式(预演……)

阅读更多
(本来是想详细点写的……不过这坑似乎太大了。这个就算是预演好了。主要是写了不发怕坑掉,发了不完整又どうかと思って)

在程序设计语言的语境下,一个“闭包”到底是什么?这还是得从一些别的基本概念说起。

=======================================================================================

基本概念解释

在程序设计语言中,变量可以分为自由变量(free variable)与约束变量(bound variable)两种。简单来说,一个函数里局部变量和参数都被认为是约束变量;而不是约束变量的则是自由变量。
在冯·诺依曼(von Neumann)计算机体系结构的影响下,命令式语言(imperative language)里一个变量的属性可以看作一个六元组:(名字,地址,值,类型,生命期,作用域)
名字:顾名思义。
地址:变量所关联的存储器地址。这个属性有明显的冯·诺依曼体系结构的色彩。
:变量所关联的存储器单元的内容。
类型:规定了变量可以取的值得范围,以及该类型的值可以进行的操作。根据类型的值的可赋值状况,可以把类型分为三类:
- 1、一级的(first class)。该等级类型的值可以传给子程序作为参数,可以从子程序里返回,可以赋给变量。大多数程序设计语言里,整型、字符类型等简单类型都是一级的。
- 2、二级的(second class)。该等级类型的值可以传给子程序作为参数,但是不能从子程序里返回,也不能赋给变量。
- 3、三级的(third class)。该等级类型的值连作为参数传递也不行。
生命期:变量与一个特定的存储区地址相绑定的过程。一个变量的生命期从它与一个特定的存储器地址相绑定开始,到它与存储器解除绑定为止。根据生命期分类,变量可以被分为四类:
- 1、静态(static)。该种变量的存储器分配在程序开始运行之前就决定,并且在程序运行过程中一直不变,直到程序结束为止。
- 2、栈动态(stack-dynamic)。该种变量的存储器分配在声明它的语句被执行到的时候才决定,但变量类型是静态决定的。顾名思义,空间是在运行时栈上分配的。
- 3、显式堆动态(explicit heap-dynamic)。该种变量由程序员显式使用运行时指令(或运算符)来指定在堆上空间的分配(和/或回收)。典型的例子是使用newdelete运算符。
- 4、隐式堆动态(implicit heap-dynamic)。该种变量每次被赋值时都会重新在堆上分配空间,不需要特别的运行时指令(或运算符)来指定。
作用域:变量在语句中可见的范围。如果在某个语句中可以引用某个变量,则该变量在该语句中可见。根据作用域的特征分类,程序设计语言中所支持的作用域可以被分为:
- 1、静态作用域。在静态作用域的规定下,变量的作用域与其在代码中所处的位置相关;因为代码可以静态决定(运行前就可以决定),所以变量的作用域也可以被静态决定,所以这种规定被称为静态作用域。
- 2、动态作用域。相对的,在动态作用域的规定下,变量的作用域与代码的执行顺序相关;执行顺序只有在程序运行时才能被决定,所以这种规定被称为动态作用域。
某种意义上来说,这两种规定是统一的:静态作用域由空间(代码字面上的空间)范围决定,动态作用域由时间(程序的执行)顺序决定。但由于作用域本身是个空间概念,所以一般而言静态作用域更容易被人理解。

变量的生命期与作用域并不一样,一个是时间概念,一个是空间(源代码字面上的)概念。但支持静态作用域,并且支持栈动态变量的程序设计语言中,这两个概念会有一些联系,例如:一种支持静态作用域与栈动态变量的语言(例如C)中,一个原始类型(或者在某些语言中,值类型)的局部变量会在栈上分配空间,它的生命期从其所在的函数被调用的时候开始,到调用结束的时候为止;如此生命期就与作用域联系在了一起。
在允许在全局层次定义变量的语言里,全局变量是个特例。全局变量的存储器空间分配与局部变量不一样,一般是静态决定其分配的地址(在存储器的全局空间部分),所以它的生命期涵盖程序的整个执行过程。因此,虽然对于所有函数来说全局变量都是自由变量,但下面的讨论中我们不需要对其做讨论(因为它不涉及动态的存储器空间分配)。某些语言里的static关键字也可以对局部变量指定使用静态存储器空间分配,这样它们虽然仍遵守静态作用域,生命期却与一般的栈动态局部变量不同。

在静态作用域中,也可以分为两类:可以嵌套定义子程序的与不可以的。基于C的语言(指标准C、C++、Java)都无法在一个函数里再嵌套定义函数;而其它的一些,像是Scheme、Pascal、Ada、JavaScript、C#等则允许嵌套的函数定义。
虽然基于C的语言不允许嵌套定义函数,但这类语言都支持块结构。通过代码块,我们可以在一个静态作用域里新建一个嵌套的静态作用域。这样就可以有效的控制变量的作用域,使其尽量的小,便于减少变量名冲突的问题。

(不过等C++0x正式成为标准后,基于C的语言里也将允许这种嵌套的函数定义)

=======================================================================================

让我们来看一个支持静态作用域,同时支持栈动态局部变量的例子:(C++)
int global;
// ...
void m( int paramOfM ) {
    int localOfM;
    // ...
    for ( int localToFor = 0; localToFor < 10; ++localToFor ) {
        int localToForBody;
        // ...
    }
}

void n( int paramOfN ) {
    int localOfN;
    // ...
}

上面的代码所对应的作用域状况是:

很正常对吧,学过C/C++的人都知道这个。
在第一行声明的global变量是一个全局变量。它的作用域是它所在的整个源文件,在它声明了之后的部分。
paramOfM是函数m()的形式参数。它的作用域覆盖整个m()的范围。
localOfM是函数m()之内的一个局部变量。它的作用于从它声明的位置开始,到m()结束的地方为止。
localToFor是m()中的一个for循环中的一个控制变量。它是一个局部于for语句的变量,作用域覆盖了整个for语句中的部分,直到离开for语句块为止。注意到早期的C++并不是这样规定的;早期的C++规定,for语句开头的括号中声明的变量的作用域一直到包含这个for循环的代码块结束的位置为止。因此用过Visual C++ 6.0的人肯定都有过痛苦的经历:
for ( int i = 0; i < 10; ++i) {
    // ...
}

for ( int i = 0; i < 10; ++i) { // error: variable i is already defined
    // ...
}

这个问题也不能怪VC++6不标准,VS6出的时候C++98还没什么编译器能完全正确的实现出来吧。
回到主题。前面代码里,localToForBody是在for语句的循环体里定义的一个局部变量。它的作用域从它声明的位置开始,到for的循环体结束的位置为止。值得注意的是,由于for语句开头的括号在for的循环体之前出现,所以循环体里定义的局部变量在开头的括号里都是不可见的。
后面是函数n()的作用域。可以看到m()与n()的作用域相互没有影响——它们相互没有交集。

=======================================================================================

让我们来看看,在现实程序中一个函数调用是如何实现的。下面的代码来自AQUAPLUS的ToHeart2。
my_inc2/MM_std.cpp, line 766:
BOOL STD_CheckFile( char *fname )
{
    HANDLE fh;

    fh = CREATE_READ_FILE( fname );
    if( fh == INVALID_HANDLE_VALUE ) return FALSE;
    CloseHandle( fh );

    return TRUE;
}

其中CREATE_READ_FILE是一个宏,在my_inc2/MM_std.h定义:
#define	CREATE_READ_FILE(FNAME)		CreateFile( FNAME, GENERIC_READ , FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL)

于是通过宏展开,上面的函数变为:
BOOL STD_CheckFile( char *fname )
{
    HANDLE fh;

    fh = CreateFile( fname, GENERIC_READ , FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
    if( fh == INVALID_HANDLE_VALUE ) return FALSE;
    CloseHandle( fh );

    return TRUE;
}

然后在程序启动时会调用上述函数,在WIN_Init()中,ScriptEngine/src/Winmain.cpp, line 1844:
InitBootFlag = !STD_CheckFile( "Sys.sav" );

调用关系是:
WIN_Init()
- STD_CheckFile()
-- CreateFile()
让我们仔细观察一下这个调用是如何进行的。注意到这里都采用C调用约定(cdecl),所以参数从右向左压入栈中,由调用者负责清理栈。
调用者的汇编代码是:
004B9025  push ToHeart2.00542C18                       ;  ASCII "Sys.sav"
004B902A  call ToHeart2.004A6BA0

运行到这个call时,相应的运行时栈的内容是:
0012FD30   00542C18  ASCII "Sys.sav"

这个地方就是当前栈顶了。

跟进这个调用,看到栈的内容发生了变化:
0012FD2C   004B902F  返回到 ToHeart2.004B902F 来自 ToHeart2.004A6BA0
0012FD30   00542C18  ASCII "Sys.sav"

栈顶变为0012FD2C。可以看到,call指令的执行导致函数的返回地址被压入栈顶。

然后来看看被调用者(STD_CheckFile())的汇编代码:
004A6BA0  mov eax,dword ptr ss:[esp+4]                 ; 将第一个参数读入EAX寄存器中
004A6BA4  push 0                                       ; /hTemplateFile = NULL
004A6BA6  push 80                                      ; |Attributes = NORMAL
004A6BAB  push 3                                       ; |Mode = OPEN_EXISTING
004A6BAD  push 0                                       ; |pSecurity = NULL
004A6BAF  push 1                                       ; |ShareMode = FILE_SHARE_READ
004A6BB1  push 80000000                                ; |Access = GENERIC_READ
004A6BB6  push eax                                     ; |FileName
004A6BB7  call dword ptr ds:[<&KERNEL32.CreateFileA>]  ; \CreateFileA <- 调用CreatFileA()。上面是压入参数的过程
004A6BBD  cmp eax,-1                                   ; 将返回值与INVALID_HANDLE_VALUE比对
004A6BC0  jnz short ToHeart2.004A6BC5                  ; 若返回值是INVALID_HANDLE_VALUE则跳转到004A6BC5
004A6BC2  xor eax,eax                                  ; 将EAX清零
004A6BC4  retn                                         ; 正常返回
004A6BC5  push eax                                     ; /hObject
004A6BC6  call dword ptr ds:[<&KERNEL32.CloseHandle>]  ; \CloseHandle
004A6BCC  mov eax,1                                    ; 将EAX设置为1
004A6BD1  retn                                         ; 无法打开文件,出错返回

该函数的第一行将调用时传入的第一个参数读入了EAX。对应C的源代码看,源代码的第一行声明了一个局部变量。这个变量跑哪儿去了呢?
其实是被编译器优化掉了。如果编译器没有优化的话,这个变量很可能位于当前栈顶的“上面”(低地址方向)。于是这个例子里我们不会在栈上看到属于STD_CheckFile()的局部变量。它在\CreateFileA()返回后被放置在EAX里了。

继续运行到对CreateFileA的call那句时,栈的状况是:
0012FD10   00542C18  |FileName = "Sys.sav"
0012FD14   80000000  |Access = GENERIC_READ
0012FD18   00000001  |ShareMode = FILE_SHARE_READ
0012FD1C   00000000  |pSecurity = NULL
0012FD20   00000003  |Mode = OPEN_EXISTING
0012FD24   00000080  |Attributes = NORMAL
0012FD28   00000000  \hTemplateFile = NULL
0012FD2C   004B902F  返回到 ToHeart2.004B902F 来自 ToHeart2.004A6BA0
0012FD30   00542C18  ASCII "Sys.sav"

留意到现在从栈顶开始7个存储器单元(每个单元是一个双字,DWORD,32位)都是对CreateFileA()调用而传入的参数。

同样,跟进这个调用,看看栈的变化:
0012FD0C   004A6BBD  /CALL 到 CreateFileA 来自 ToHeart2.004A6BB7
0012FD10   00542C18  |FileName = "Sys.sav"
0012FD14   80000000  |Access = GENERIC_READ
0012FD18   00000001  |ShareMode = FILE_SHARE_READ
0012FD1C   00000000  |pSecurity = NULL
0012FD20   00000003  |Mode = OPEN_EXISTING
0012FD24   00000080  |Attributes = NORMAL
0012FD28   00000000  \hTemplateFile = NULL
0012FD2C   004B902F  返回到 ToHeart2.004B902F 来自 ToHeart2.004A6BA0
0012FD30   00542C18  ASCII "Sys.sav"

跟前一个调用一样,call指令使返回地址被压入栈顶。

在CreatFileA()运行完毕,返回到STD_CheckFile()之后,栈的内容是:
0012FD2C   004B902F  返回到 ToHeart2.004B902F 来自 ToHeart2.004A6BA0
0012FD30   00542C18  ASCII "Sys.sav"

可以看到,跟调用CreatFileA()之前一样。retn指令会从栈顶弹出一个值作为返回地址,然后把EIP(指令指针)设置到那个值上,完成返回跳转。

程序从STD_CheckFile()返回到WIN_Init()之后,栈顶是:
0012FD30   00542C18  ASCII "Sys.sav"

由于C调用约定是由调用者来清理栈,而碰巧WIN_Init()接下来还要用到这个值,所以编译器优化让这个值保持在了栈顶。否则这个值也应该被抛弃掉了。

上面的例子主要是想说明C语言中的函数调用在现实中运行的情况,以便说明其语义。
每个函数被调用时,都会有一个活动记录(activation record)伴随产生。像C这样的语言,活动记录是在运行时栈上分配空间的,所以也成为栈帧(stack frame)。一个函数的活动记录以及该函数能访问到的任何“东西”(变量之类)的总和,被称为这个函数拥有的引用环境(referencing environment)。
一般来说,一个函数的参数和返回地址都会放置在栈帧里,在未经优化的时候局部变量也会放在栈帧里。这种运行方式使得一个函数的参数和局部变量都只在其运行当中才被分配空间;函数的运行一结束,分配的空间就解除了与相应变量的绑定(约束)。由于参数与局部变量的状况都是可以在程序运行前确定的,所以它们在栈帧中的位置(偏移量)可以静态确定。栈帧本身的存在于否只能到运行的时候才能知道,有动态性,所以这种生命期得名“栈动态”。
这样,变量的生命期就与静态作用域的规定产生了联系。

那么假如允许嵌套定义函数,并且允许内层函数访问外围函数里的变量会怎样呢?
可以观察到,内层函数只能为自己的参数和局部变量分配空间,而无法为外围函数中的变量分配空间;但同时,内层函数需要能够访问到外围函数里的局部变量,意味着它的活动记录里必须有某种手段去访问到外围函数的活动记录,否则内层函数的“引用环境”就不包含外围函数的活动记录也就无法访问外围函数里的局部变量。

(本来是想画图来表示的……时间不足,只好直接贴文字。下次补完吧……)

=======================================================================================

在接着讨论之前,先得说明一点:静态作用域并不总是意味着被嵌套的作用域(nested scope)中能访问包围它的外围作用域(enclosing scope)中的变量。
如果一种语言中,被嵌套的作用域总是能访问其外围作用域中的变量(或者说名字),则该语言被认为支持词法作用域(lexical scope)。反之则不算支持词法作用域。
(有些资料上将“静态作用域”与“词法作用域”写为同一个概念,或许这里还有值得商榷的地方吧。)
从一般意义的词法作用域概念来说,Python虽然支持静态作用域,但并不是一般的词法作用域。虽然有些Python用户可能会争论这点,但Python的词法作用域很明显与“一般的”不一样:
引用
If a name is assigned to anywhere in a code block (even in unreachable code), and is not mentioned in a global statement in that code block, then it refers to a local name throughout that code block.

C:\Python25>python
ActivePython 2.5.1.1 (ActiveState Software Inc.) based on
Python 2.5.1 (r251:54863, May  1 2007, 17:47:05) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def outer(x):
...   def inner():
...     print x
...   inner()
...   inner()
...
>>> outer(3)
3
3

Python里只能对函数内的局部变量或者全局变量赋值,而不能对非局部非全局的变量赋值;可以读取非局部非全局变量,但是一旦尝试赋值,Python就会自动创建一个新的同名的局部变量。根据词法作用域,上面的例子表明Python中嵌套的内部函数可以读取外围函数里的变量:对inner()来说x是一个自由变量,非局部非全局,并且被inner()外围的闭包所捕获因而可以访问。但下面的例子却会失败:
C:\Python25>python
ActivePython 2.5.1.1 (ActiveState Software Inc.) based on
Python 2.5.1 (r251:54863, May  1 2007, 17:47:05) [MSC v.1310 32 bit (Intel)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> def outer(x):
...   def inner():
...     x += 1
...     print x
...   inner()
...   inner()
...
>>> outer(2)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 5, in outer
  File "<stdin>", line 3, in inner
UnboundLocalError: local variable 'x' referenced before assignment

+=运算符包含了三个操作,先计算运算符左边的表达式,再将运算符右边的表达式的值加上左边表达式原本的值,最后赋值回到左边的表达式对应的变量上。由于包含了赋值操作,Python在查找变量x时只在局部或全局范围内查找,而此时符合这样条件的局部变量x还不存在(或者说还未被赋值过),所以出现了错误。这是设计者为了安全而做的设计取舍,不过无论如何它与一般意义上的词法作用域不一样。
最典型的地方是Python里成员方法都必须以self为第一个参数的做法(名字不一定要是self,不过约定上都叫这个名字)……不熟悉Python的话算了,这个不用深究。

=======================================================================================

允许嵌套定义子程序的语言也可以被分为三类:允许将函数作为参数传递的与不允许的,还有中间比较奇怪的(=_=)。如果一种语言允许将函数作为参数传递,意味着这种语言里的函数至少是二级的类型。

先从不能将函数作为参数传递的开始讨论。

(待补完)

------------

然后是允许将函数作为参数传递的。

标准Pascal(ISO 7185:1990)
引用Wikipedia上的例子:
function E(x: real): real
 
    function F(y: real): real
    begin
        F := x + y
    end
 
begin
    E := F(3)
end

留意到在F()里,外围的E()中的x是如何被“捕获”到当前作用域的。对于F()来说,y是一个形式参数,因而是一个“约束变量”;x既不是局部变量或者参数,也不是全局变量,因而是一个“自由变量”。F()的执行依赖于外部环境提供的“x”变量才可以进行。
Pascal是支持栈动态变量的语言。上面的例子中,x和y的存储器空间都是在运行时栈上分配的。

ISO 7185:1990,6.6.3 Parameters部分说明了将过程或者函数作为参数传递的一些规定。

------------

GNU C:
GNU C算是个比较特殊的实现吧。上面Pascal的例子在GNU C里也可以写出来:
float E(float x)
{
    float F(float y)
    {
        return x + y;
    }
    return F(3);
}

注意:只是GNU C支持嵌套定义函数,GNU C++是不支持的。
C里的函数指针可以传递函数的代码,却无法传递函数的环境。因此,当允许嵌套定义函数时,内层函数只有在外围函数活动的时候才能捕获到其引用环境。下次再详细说……


接下来让我们看看JavaScript的例子。


(待补完)

=======================================================================================

闭包与对象的等价性

(待补完)

=======================================================================================

闭包的应用意义
1、状态隐藏
2、算法封装
3、自定义控制流
...

(待补完)

=======================================================================================

Lambda Expressions and Closures: Wording for Monomorphic Lambdas (Revision 4)
在C++0x之前,我们经常会使用functor来解决一些问题:
引用
class between {
    double low, high;
public:
    between(double l, double u) : low(l), high(u) { }
    bool operator()(const employee& e) {
        return e.salary() >= low && e.salary() < high;
    }
}

....
double min_salary;
....
std::find_if(employees.begin(), employees.end(),
    between(min_salary, 1.1 * min_salary));

The constructor call between(min_salary, 1.1 * min_salary) creates a function object, which is comparable to
what, e.g., in the context of functional programming languages is known as a closure. A closure stores the
environment, that is the values of the local variables, in which a function is defined. Here, the environment
stored in the between function object are the values low and high, which are computed from the value of the
local variable min_salary.

但是这样挺麻烦的。明明只是需要一个函数,却需要写一整个类出来。而有了C++0x的lambda表达式之后,就可以在函数内直接定义匿名的嵌套函数了。
double min_salary = ....
....
double u_limit = 1.1 * min_salary;
std::find_if(employees.begin(), employees.end(),
    [&](const employee& e) {
        return e.salary() >= min_salary && e.salary() < u_limit;
    });


#include <algorithm>
#include <vector>

void outer( ) {
    std::vector<int> v;
    int i;
    // ...
    std::for_each( v.begin(), v.end(), [ &i ]( int& elem ) {
        std::cout << elem << ' ';
    });
    // ...
}

这段代码里的作用域:

(哎呀,图做完了才发觉for_each之前漏了std::……算了懒得重新做图,凑合吧)

不过这个例子在C++0x里也未必要用到lambda表达式了,直接用新增加的for-each循环就行:
std::vector<int> v;
int i;
// ...
for ( int elem : v ) {
    cout << elem << ' ';
    ++i;
}

如果在elem前面加上&来修饰,则对elem的赋值也会反映到容器内。真好。

引用
If every name in the effective capture set is preceded by &, F is publicly derived from std::reference_closure<
R(P)>
(20.5.17), where R is the return type and P is the parameter-type-list of the lambda expression. Converting
an object of type F to type std::reference_closure<R(P)> and invoking its function call operator shall
have the same effect as invoking the function call operator of F. [ Note: This requirement effectively means that such
F must be implemented using a pair of a function pointer and a static scope pointer. —end note ]


照这么说,C++0x里的闭包也是D 2.0早期所定义的“动态闭包”了。关于D的动态闭包,可以看看我去年写的这帖,靠下面的部分。

(待补完)

=======================================================================================

相关链接:
Wikipedia:
Closure (computer science)
Lambda Calculus
Free variables and bound variables
Nested Function
C++0x
Cunningham & Cunningham, Inc.:
Lexical Closure
分享到:
评论

相关推荐

    C++ 11,14,17中的 Lambda 表达式 _ Microsoft Docs[2].pdf

    查Lambda表达式资料时很容易被函数闭包、Lambda演算、形式系统这些深奥名词淹没而放弃学习,其实Lambda表达式就是匿名函数(annoymous function)——允许我们使用一个函数,但不需要给这个函数起名字。还是有点难懂...

    一文读懂c++11 Lambda表达式

    实际上是一个闭包(closure),类似于一个匿名函数,拥有捕获所在作用域中变量的能力,能够将函数做为对象一样使用,通常用来实现回调函数、代理等功能。Lambda表达式是函数式编程的基础,C++11引入了Lambda则弥补了...

    【JavaScript源代码】JS难点同步异步和作用域与闭包及原型和原型链详解.docx

    JS难点同步异步和作用域与闭包及原型和原型链详解  目录 JS三座大山同步异步同步异步区别作用域、闭包函数作用域链块作用域闭包闭包解决用var导致下标错误的问题投票机闭包两个面试题原型、原型链原型对象原型链...

    JS的作用域与闭包

    JS作用域与闭包。

    python、lambda表达式的用法举例 .txt 文本格式

    内容概要:python、lambda表达式的用法举例。 采用.txt 文本格式。无需解压,直接使用。 总结了lambda表达式的六种常见用法: 1.命名使用 2.与map()、 filter()和 reduce()等高阶函数配合 3.赋予key参数使用 4.直接...

    lambda表达式详解及实际运用

    Lambda 表达式,也可称为闭包(闭包就是能够将一个方法作为一个变量去存储,这个方法有能力去访问所在类的自由变量),它是推动 Java 8 发布的最重要新特性。 Lambda 允许把函数作为一个方法的参数(函数作为参数...

    浅谈C++11新引入的lambda表达式

    ISO C++ 11 标准的一大亮点是引入Lambda表达式。基本语法如下: [capture list] (parameter list) -&gt;return type { function body } 简单的讲一下各个部分的作用 1.[capture list]捕获列表,捕获到函数体中,使得...

    作用域与闭包

    由作用域引出的作用域链的问题, 并在此基础上讨论闭包的问题

    C++-lambda闭包函数.pdf

    c++ C++_lambda闭包函数.pdf

    Python中 Lambda表达式全面解析

    Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。 Lambda是一种匿名函数,当我们需要重复调用某一函数,又不想写那么多代码时可以使用lambda表达式来代替。 lambda的通用格式: lambda argument: ...

    基于C++ Lambda表达式的程序优化

    这里的Lambda表达式实际上和block非常类似,当然如果你用它和Swift语言的闭包比较,那就是一回事了。 这是一个关于C\C++程序员的一个小故事,关于C++11——刚刚通过的新标准的一个小故事… 请不要误会,

    作用域和闭包知识点文档

    JavaScript作用域与闭包知识点详细说明文档,有相关案例代码,欢迎下载,有问题可留言讨论哈哈哈!

    Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作示例

    本文实例讲述了Python 闭包,函数分隔作用域,nonlocal声明非局部变量操作。分享给大家供大家参考,具体如下: 实例对象也可以实现闭包的功能,不过实例对象消耗的资源(内存)比闭包多。 demo.py(闭包): # 闭包,...

    你并不了解 JavaScript(YDKJS)第二版:作用域与闭包.epub

    你并不了解 JavaScript(YDKJS)第二版:作用域与闭包

    JavaScript作用域、闭包、对象与原型链概念及用法实例总结

    本文实例讲述了JavaScript作用域、闭包、对象与原型链概念及用法。分享给大家供大家参考,具体如下: 1 JavaScript变量作用域 1.1 函数作用域 没有块作用域:即作用域不是以{}包围的,其作用域完成由函数来决定,...

    C++11中的Lambda表达式

    “Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包...

    for循环_作用域_闭包.html

    for循环_作用域_闭包.html

    Python语言基础:作用域.pptx

    作用域作用域作用域的查找顺序新作用域的引入目录Contents01作用域作用域作用域就是一个 Python 程序可以直接访问命名空间的正文区域。Python 中,变量的访问权限决定于这个变量是在哪里赋值的。变量的作用域决定了...

    java中lambda表达式语法说明

    “Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包...

Global site tag (gtag.js) - Google Analytics