Anonyme Funktion erschien früher in der Programmiersprache Sie erschien zuerst in der Lisp-Sprache, und dann begannen viele Programmiersprachen Diese Funktion ist verfügbar,
Javascript und C# sind derzeit weit verbreitet, PHP unterstützte anonyme Funktionen erst ab 5.3 und der neue C++-Standard C++0x begann ebenfalls zu unterstützen Es.
Eine anonyme Funktion ist eine Art Funktion oder Unterroutine, die ohne Angabe eines Bezeichners aufgerufen werden kann. Anonyme Funktionen können bequem als Parameter an andere Funktionen übergeben werden. Die häufigste Anwendung ist eine Rückruffunktion.
Wenn es um anonyme Funktionen geht, müssen wir „Closures“ für „Lexical Closures“ nennen und beziehen uns auf freie Variablen Wenn es die Umgebung verlässt, in der es erstellt wurde, kann ein Abschluss auch als eine Einheit betrachtet werden, die aus einer Funktion und den zugehörigen Referenzen besteht. In einigen Sprachen kann es beim Definieren einer anderen Funktion innerhalb einer Funktion zu einem Abschluss kommen, wenn die innere Funktion auf die Variablen der äußeren Funktion verweist. Beim Ausführen einer externen Funktion wird ein Abschluss gebildet. Das Wort
kann leicht mit anonymen Funktionen verwechselt werden. Tatsächlich handelt es sich um zwei verschiedene Konzepte. Dies kann daran liegen, dass viele Sprachen die Bildung von Abschlüssen bei der Implementierung anonymer Funktionen ermöglichen.
Wie bereits erwähnt, wurden anonyme Funktionen nur in PHP5.3 offiziell unterstützt. Zu diesem Zeitpunkt haben aufmerksame Leser möglicherweise Meinungen, weil dort Eine Funktion kann eine anonyme Funktion generieren: Funktion „create_function“ Sie finden diese Funktion in PHP4.1 und PHP5 im Handbuch. Diese Funktion kann normalerweise auch als anonyme Callback-Funktion verwendet werden, beispielsweise wie folgt:
<?php $array = array(1, 2, 3, 4); array_walk($array, create_function('$value', 'echo $value'));
Dieser Code gibt lediglich die Werte im Array nacheinander aus, kann aber natürlich auch mehr. Warum handelt es sich also nicht um eine echte anonyme Funktion? Schauen wir uns zunächst den Rückgabewert dieser -Funktion an. Normalerweise können wir eine Funktion wie folgt aufrufen:
<?php function a() { echo 'function a'; } $a = 'a'; $a();
Wir können diese Methode auch bei der Implementierung der Callback-Funktion verwenden, zum Beispiel:
<?php function do_something($callback) { // doing # ... // done $callback(); }
Auf diese Weise kann die durch $callback angegebene Funktion aufgerufen werden, nachdem die Funktion do_something() ausgeführt wurde. Zurück zum Rückgabewert der Funktion „create_function“: Die Funktion gibt einen eindeutigen String-Funktionsnamen oder FALSE zurück, wenn ein Fehler auftritt. Diese Funktion erstellt also nur dynamisch eine Funktion, und diese Funktion hat einen Funktionsnamen , was bedeutet, dass sie nicht wirklich anonym ist. Es wird lediglich eine weltweit einzigartige Funktion erstellt.
<?php $func = create_function('', 'echo "Function created dynamic";'); echo $func; // lambda_1 $func(); // Function created dynamic $my_func = 'lambda_1'; $my_func(); // 不存在这个函数 lambda_1(); // 不存在这个函数
Der erste Teil des obigen Codes ist leicht zu verstehen. So wird er später über den Funktionsnamen aufgerufen Stellen Sie sicher, dass diese Funktion global eindeutig ist. Lambda_1 scheint ein sehr häufiger Funktionsname zu sein. Was ist, wenn wir hier zuerst eine Funktion namens lambda_2 definieren? Funktion. Finden Sie den entsprechenden Funktionsnamen, aber was ist, wenn wir nach create_function eine Funktion namens lambda_1 definieren? Diese Implementierung ist wahrscheinlich nicht die beste Methode, die ich erwähnt habe tritt nicht auf, wenn die Funktion namens lambda_1 hinzugefügt wird. Was ist los? Die letzten beiden Zeilen des obigen Codes veranschaulichen dieses Problem. Tatsächlich ist keine Funktion namens lambda_1 definiert.
Mit anderen Worten, unser Lambda_1 und das von create_function zurückgegebene Lambda_1 sind nicht dasselbe! Wenn wir echo, wird Lambda_1 ausgegeben und unser lambda_1 wird von uns selbst eingegeben. Schauen wir uns das mit der Funktion debug_zval_dump an.
<?php $func = create_function('', 'echo "Hello";'); $my_func_name = 'lambda_1'; debug_zval_dump($func); // string(9) "lambda_1" refcount(2) debug_zval_dump($my_func_name); // string(8) "lambda_1" refcount(2)
Wie Sie sehen können, sind ihre Längen tatsächlich unterschiedlich, was bedeutet, dass es sich nicht um dieselbe Funktion handelt. Schauen wir uns also einfach die Funktion create_function an Funktion tun? Siehe die Implementierung: $PHP_SRC/Zend/zend_builtin_functions.c
#define LAMBDA_TEMP_FUNCNAME "lambda_func" ZEND_FUNCTION(create_function) { // ... 省去无关代码 function_name = (char *) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG); function_name[0] = '\0'; // <--- 这里 do { function_name_length = 1 + sprintf(function_name + 1, "lambda_%d", ++EG(lambda_count)); } while (zend_hash_add(EG(function_table), function_name, function_name_length+1, &new_function, sizeof(zend_function), NULL)==FAILURE); zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME)); RETURN_STRINGL(function_name, function_name_length, 0); }
该函数在定义了一个函数之后,给函数起了个名字,它将函数名的第一个字符变为了'\0'也就是空字符,然后在函数表中查找是否已经定义了这个函数,如果已经有了则生成新的函数名, 第一个字符为空字符的定义方式比较特殊, 因为在用户代码中无法定义出这样的函数, 也就不存在命名冲突的问题了,这也算是种取巧(tricky)的做法,在了解到这个特殊的函数之后,我们其实还是可以调用到这个函数的, 只要我们在函数名前加一个空字符就可以了, chr()函数可以帮我们生成这样的字符串, 例如前面创建的函数可以通过如下的方式访问到:
<?php $my_func = chr(0) . "lambda_1"; $my_func(); // Hello
这种创建"匿名函数"的方式有一些缺点:
函数的定义是通过字符串动态eval的, 这就无法进行基本的语法检查;
这类函数和普通函数没有本质区别, 无法实现闭包的效果.
在PHP5.3引入的众多功能中, 除了匿名函数还有一个特性值得讲讲: 新引入的invoke 魔幻方法。
这个魔幻方法被调用的时机是: 当一个对象当做函数调用的时候, 如果对象定义了invoke魔幻方法则这个函数会被调用,这和C++中的操作符重载有些类似, 例如可以像下面这样使用:
<?php class Callme { public function invoke($phone_num) { echo "Hello: $phone_num"; } } $call = new Callme(); $call(13810688888); // "Hello: 13810688888
前面介绍了将对象作为函数调用的方法, 聪明的你可能想到在PHP实现匿名函数的方法了,PHP中的匿名函数就的确是通过这种方式实现的。我们先来验证一下:
<?php $func = function() { echo "Hello, anonymous function"; } echo gettype($func); // object echo get_class($func); // Closure
原来匿名函数也只是一个普通的类而已。熟悉Javascript的同学对匿名函数的使用方法很熟悉了,PHP也使用和Javascript类似的语法来定义, 匿名函数可以赋值给一个变量, 因为匿名函数其实是一个类实例, 所以能复制也是很容易理解的, 在Javascript中可以将一个匿名函数赋值给一个对象的属性, 例如:
var a = {}; a.call = function() {alert("called");} a.call(); // alert called
这在Javascript中很常见, 但在PHP中这样并不可以, 给对象的属性复制是不能被调用的, 这样使用将会导致类寻找类中定义的方法,在PHP中属性名和定义的方法名是可以重复的, 这是由PHP的类模型所决定的, 当然PHP在这方面是可以改进的, 后续的版本中可能会允许这样的调用,这样的话就更容易灵活的实现一些功能了。目前想要实现这样的效果也是有方法的: 使用另外一个魔幻方法call(),至于怎么实现就留给各位读者当做习题吧。
PHP使用闭包(Closure)来实现匿名函数, 匿名函数最强大的功能也就在匿名函数所提供的一些动态特性以及闭包效果,匿名函数在定义的时候如果需要使用作用域外的变量需要使用如下的语法来实现:
<?php $name = 'TIPI Team'; $func = function() use($name) { echo "Hello, $name"; } $func(); // Hello TIPI Team
这个use语句看起来挺别扭的, 尤其是和Javascript比起来, 不过这也应该是PHP-Core综合考虑才使用的语法, 因为和Javascript的作用域不同, PHP在函数内定义的变量默认就是局部变量, 而在Javascript中则相反,除了显式定义的才是局部变量, PHP在变异的时候则无法确定变量是局部变量还是上层作用域内的变量, 当然也可能有办法在编译时确定,不过这样对于语言的效率和复杂性就有很大的影响。
这个语法比较直接,如果需要访问上层作用域内的变量则需要使用use语句来申明, 这样也简单易读,说到这里, 其实可以使用use来实现类似global语句的效果。
匿名函数在每次执行的时候都能访问到上层作用域内的变量, 这些变量在匿名函数被销毁之前始终保存着自己的状态,例如如下的例子:
<?php function getCounter() { $i = 0; return function() use($i) { // 这里如果使用引用传入变量: use(&$i) echo ++$i; }; } $counter = getCounter(); $counter(); // 1 $counter(); // 1
和Javascript中不同,这里两次函数调用并没有使$i变量自增,默认PHP是通过拷贝的方式传入上层变量进入匿名函数,如果需要改变上层变量的值则需要通过引用的方式传递。所以上面得代码没有输出1, 2
而是1,1
。
前面提到匿名函数是通过闭包来实现的, 现在我们开始看看闭包(类)是怎么实现的。匿名函数和普通函数除了是否有变量名以外并没有区别,闭包的实现代码在$PHP_SRC/Zend/zend_closure.c。匿名函数"对象化"的问题已经通过Closure实现, 而对于匿名是怎么样访问到创建该匿名函数时的变量的呢?
例如如下这段代码:
<?php $i=100; $counter = function() use($i) { debug_zval_dump($i); }; $counter();
通过VLD来查看这段编码编译什么样的opcode了
$ php -dvld.active=1 closure.php vars: !0 = $i, !1 = $counter # * op fetch ext return operands ------------------------------------------------------------------------ 0 > ASSIGN !0, 100 1 ZEND_DECLARE_LAMBDA_FUNCTION '%00%7Bclosure 2 ASSIGN !1, ~1 3 INIT_FCALL_BY_NAME !1 4 DO_FCALL_BY_NAME 0 5 > RETURN 1 function name: {closure} number of ops: 5 compiled vars: !0 = $i line # * op fetch ext return operands -------------------------------------------------------------------------------- 3 0 > FETCH_R static $0 'i' 1 ASSIGN !0, $0 4 2 SEND_VAR !0 3 DO_FCALL 1 'debug_zval_dump' 5 4 > RETURN null
上面根据情况去掉了一些无关的输出, 从上到下, 第1开始将100赋值给!0也就是变量$i, 随后执行ZEND_DECLARE_LAMBDA_FUNCTION,那我们去相关的opcode执行函数中看看这里是怎么执行的, 这个opcode的处理函数位于$PHP_SRC/Zend/zend_vm_execute.h中:
static int ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS) { zend_op *opline = EX(opline); zend_function *op_array; if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRLEN(opline->op1.u.constant), Z_LVAL(opline->op2.u.constant), (void *) &op_arra y) == FAILURE || op_array->type != ZEND_USER_FUNCTION) { zend_error_noreturn(E_ERROR, "Base lambda function for closure not found"); } zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_CC); ZEND_VM_NEXT_OPCODE(); }
该函数调用了zend_create_closure()函数来创建一个闭包对象, 那我们继续看看位于$PHP_SRC/Zend/zend_closures.c的zend_create_closure()函数都做了些什么。
ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC) { zend_closure *closure; object_init_ex(res, zend_ce_closure); closure = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC); closure->func = *func; if (closure->func.type == ZEND_USER_FUNCTION) { // 如果是用户定义的匿名函数 if (closure->func.op_array.static_variables) { HashTable *static_variables = closure->func.op_array.static_variables; // 为函数申请存储静态变量的哈希表空间 ALLOC_HASHTABLE(closure->func.op_array.static_variables); zend_hash_init(closure->func.op_array.static_variables, zend_hash_num_elements(static_variables), NULL, ZVAL_PTR_DTOR, 0); // 循环当前静态变量列表, 使用zval_copy_static_var方法处理 zend_hash_apply_with_arguments(static_variables TSRMLS_CC, (apply_func_args_t)zval_copy_static_var, 1, closure->func.op_array.static_variables); } (*closure->func.op_array.refcount)++; } closure->func.common.scope = NULL; }
如上段代码注释中所说, 继续看看zval_copy_static_var()函数的实现:
static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_list args, zend_hash_key *key) { HashTable *target = va_arg(args, HashTable*); zend_bool is_ref; // 只对通过use语句类型的静态变量进行取值操作, 否则匿名函数体内的静态变量也会影响到作用域之外的变量 if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) { is_ref = Z_TYPE_PP(p) & IS_LEXICAL_REF; if (!EG(active_symbol_table)) { zend_rebuild_symbol_table(TSRMLS_C); } // 如果当前作用域内没有这个变量 if (zend_hash_quick_find(EG(active_symbol_table), key->arKey, key->nKeyLength, key->h, (void **) &p) == FAILURE) { if (is_ref) { zval *tmp; // 如果是引用变量, 则创建一个临时变量一边在匿名函数定义之后对该变量进行操作 ALLOC_INIT_ZVAL(tmp); Z_SET_ISREF_P(tmp); zend_hash_quick_add(EG(active_symbol_table), key->arKey, key->nKeyLength, key->h, &tmp, sizeof(zval*), (void**)&p); } else { // 如果不是引用则表示这个变量不存在 p = &EG(uninitialized_zval_ptr); zend_error(E_NOTICE,"Undefined variable: %s", key->arKey); } } else { // 如果存在这个变量, 则根据是否是引用, 对变量进行引用或者复制 if (is_ref) { SEPARATE_ZVAL_TO_MAKE_IS_REF(p); } else if (Z_ISREF_PP(p)) { SEPARATE_ZVAL(p); } } } if (zend_hash_quick_add(target, key->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) { Z_ADDREF_PP(p); } return ZEND_HASH_APPLY_KEEP; }
这个函数作为一个回调函数传递给zend_hash_apply_with_arguments()
函数, 每次读取到hash表中的值之后由这个函数进行处理,而这个函数对所有use语句定义的变量值赋值给这个匿名函数的静态变量, 这样匿名函数就能访问到use的变量了。
Das obige ist der detaillierte Inhalt vonDetaillierte Erklärung anonymer Funktionen und Schließungen in PHP. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!