PHP の匿名関数とクロージャ

WBOY
WBOYオリジナル
2016-08-08 09:26:101199ブラウズ

[iefreer] は、PHP クロージャー構文を詳しく説明した記事を再投稿し、これらの新しい構文を上手に適用する方法に関する記事も後で再投稿する予定です。

匿名関数は、プログラミング言語の比較的初期に登場しました。その後、多くのプログラミング言語がこの関数を持ち始めました。

現在広く使用されているのは、PHP ではありません。匿名関数は 5.3 まで実装される予定で、新しい C++ 標準 C++0x もサポートし始めています。

匿名関数は、識別子を指定せずに呼び出すことができる関数またはサブルーチンの一種で、最も一般的なアプリケーションはコールバック関数としてパラメータとして渡すことができます。

クロージャ

匿名関数に関して言えば、クロージャは字句クロージャ (Lexical Closure) の略であり、この自由変数が適用される場合でもこの関数に存在します。クロージャは作成された環境を離れるため、クロージャは関数とその関連参照で構成されるエンティティと考えることもできます。一部の言語では、関数内で別の関数を定義するときに、内部関数が外部関数の変数を参照すると、クロージャが発生することがあります。外部関数を実行すると、クロージャが形成されます。

という言葉は匿名関数と混同されやすいですが、実際には、これらは 2 つの異なる概念であるため、多くの言語では匿名関数の実装時にクロージャの形成が許可されている可能性があります。

「匿名」関数を作成するには create_function() を使用します

前述したように、匿名関数は PHP 5.3 でのみ正式にサポートされました。現時点では、匿名関数を生成できる関数があるため、注意深い読者は意見があるかもしれません。 : create_function 関数。この関数は PHP4.1 および PHP5 のマニュアルにあります。この関数は通常、次のように匿名コールバック関数としても使用できます。 plaincopy を表示

  • $array
  • = array(1, 2, 3, 4 ); array_walk
  • ($array, create_function('$value', 'echo $value')); このコードは配列内の値を出力するだけですもちろん、それ以上のことも可能です。 では、なぜこれが真の匿名関数ではないのでしょうか? まず、この関数の戻り値を見てみましょう。通常、この関数は次のように関数を呼び出すことができます。 [php] plaincopy を表示
    1. 関数 a() {
    2. echo
    3. '関数;
    4. }
    5. $a = 'a'
    6. $a
    7. () ;
    8. コールバック関数を実装しています。このメソッドは次のようになります: [php] plaincopy を表示

    function

    do_something(

    $callback

    ) {

    1. //やってます
    2. } これは関数で実装できます。 do_something() が実行された後、$callback で指定された関数が呼び出されます。 create_function 関数の戻り値に戻ると、この関数は一意の文字列関数名を返すか、エラーが発生した場合は FALSE を返します。したがって、この関数は関数を動的に作成するだけであり、この関数には関数名があります。つまり、実際には匿名ではありません。グローバルに一意な関数を作成するだけです。
    3. [php] プレーンコピーを見る
      1. $func = create_function('', 'echo "関数が動的に作成されました";');
      2. エコー
      3. $func; // lambda_1 $func
      4. ();関数が動的に作成されました
      5. $my_func =
      6. 'lambda_1';
      7. $my_func(); lambda_1(); / この関数は存在しません
      8. 上記のコードの先頭は、このようにして create_function を使用する方法を理解するのが簡単ですが、後から関数名を使用して呼び出すと、少し難しくなります。 php はどのように機能するのでしょうか? lambda_1 は非常に一般的な関数名のようですが、ここで関数の戻り文字列が lambda_2 になるかどうかを確認します。関数を作成する場合は、関数が存在するかどうかに関係なく、適切な関数名を見つける必要がありますが、create_function の後に lambda_1 という関数を定義すると、関数の定義が繰り返されるという問題が発生します。実際、lambda_1 という名前の関数を実際に定義した場合、前述の問題は発生しません。何が起こっているのでしょうか? 上記のコードの最後の 2 行も、実際には、lambda_1 という名前の関数が定義されていないことを示しています。 つまり、私たちの lambda_1 と create_function によって返された lambda_1 は同じではありません!? これは、本質を見ていないだけであり、lambda_1 を出力したということを意味します。エコー時に lambda_1 を入力してみます。 debug_zval_dump 関数を使用して見てみましょう。
      9. [php] view plaincopy


      $func

      = create_function(

      ''

      ,

      'echo "Hello";'
      1. );
      2. $my_func_name = 'lambda_1'; // string(9) "lambda_1" ​​refcount(2)
      3. debug _zval_dump(
      4. $my_func_name
      5. ); // string(8) "lambda_1" ​​refcount(2)
      6. ほら、彼らの長さは実際には異なります、長さが異なるということは、それが同じ関数ではないことを意味します、ですから当然関数は私たちが呼び出す関数は存在しません。create_function 関数が何をするかを見てみましょう。実装を参照してください: $PHP_SRC/Zend/zend_builtin_functions.c
      7. [php] プレーンコピーを見る
        1. #define LAMBDA_TEMP_FUNCNAME "__lambda_func"
        2. ZEND_FUNCTION(create_function)
        3. {
        4. // ... 省去无关代码
        5. function_name = (char *) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG);  
        6. 関数名[0] = ' ';  // <--- 这里
        7. do {
        8. function_name_length = 1 + sprintf(function_name + 1, "lambda_%d", ++EG(ラムダ_カウント));  
        9. } while (zend_hash_add(EG(function_table), function_name, function_name_length+1, &new_function, sizeof(zend_function), NULL)==FAILURE);  
        10. zend_hash_del(EG(function_table), LAMBDA_TEMP_FUNCNAME, sizeof(LAMBDA_TEMP_FUNCNAME));  
        11. RETURN_STRINGL(関数名, 関数名の長さ, 0);  
        12. }


        该関数在定义了一関数数之後,给関数起了个名字,它将関数名の第一文字符变のために了' '也就是空字符,その後関数数表中この関数がすでに定義されているかどうか、すでに定義されている場合は新しい関数名を生成します、最初の文字は空文字の定型比较特殊です、用户代コード内で無法にこの関数を定義しているため、つまり存在しない命名冲突発です問題を解決しました。これも巧妙な計算です。この特殊な関数を理解した後、私はこの関数を使用することもできます。ただ必要な関数名を追加すれば、chr()関数はこのような文字列を生成できます。例: 前述の作成された関数は、次のような方法で次のようにアクセスできます:

        [php] plaincopy を表示

        1. $my_func = chr(0) 。 「lambda_1」;  
        2. $my_func(); // Hello


        この「匿名関数」の方式にはいくつかの利点があります:

        1. 関数の定義は、字句通りに実行されます。基本的な语法検査。
        2. この类関数と普通関数数は本质区别、無法实现闭包的効果。

        真正の秘密関数数

        PHP5.3導入の多機能中、除了秘密関数数还有一特性值得讲讲: 新取り込まれた__invoke

        __invoke魔幻メソッド

        この魔幻メソッドが使用されるタイムマシンは次のとおりです: オブジェクトが関数を実行しているときに、オブジェクトが__invoke魔幻メソッドを定義している場合、この関数が使用され、これと C++ 内のオペランドが重畳されます。同様のものがいくつかあります。例:

        [php] plaincopy を表示

        1. class Callme {
        2. public function __invoke($phone_num) {
        3. echo 「こんにちは: $phone_num」;  
        4. }
        5. }
        6. $call = new Callme();  
        7. $call(13810688888); //「こんにちは: 13810688888


        秘密関数数の实现

        前面介绍了オブジェクトを関数数として使用するための方法、明白な方法は PHP に到達可能です」匿名関数数的方法了,PHP中の秘密関数はこの方法で実現されます。 プレーンコピーを見る

        1. $func = function() {
        2. "こんにちは、匿名関数" ;
        3. }
        4. echo gettype($func); // オブジェクト
        5. echoget_class($ func ); // Closure

        この匿名関数は単なる通常のクラスであることがわかります。 Javascript に慣れている学生は、PHP も Javascript に似た構文を使用して、変数に割り当てることができます。匿名関数は実際にはクラス インスタンスであるため、簡単に定義できます。コピーできることを理解してください。JavaScript では、オブジェクトのプロパティに匿名関数を割り当てることができます。例:

        [php] ビュー plaincopy

        1. var a = {};
        2. a.call =
        3. function() {alert("という");}
        4. あ.call();
        5. // アラートが呼び出されます

        これは Javascript では非常に一般的ですが、PHP ではこれは不可能なので、これを使用すると、 PHP では、属性名と定義されたメソッド名を繰り返すことができます。これは PHP のクラス モデルによって決まります。もちろん、この点は PHP で改善される可能性があります。このような呼び出しを許可すると、一部の機能を柔軟に実装することが容易になります。現時点では、この効果を実現する方法があります。それは、別の魔法のメソッド __call() を使用することです。これを実現する方法については、読者の演習として残しておきます。

        クロージャの使用

        PHP は、匿名関数を定義するときに、匿名関数の最も強力な機能とクロージャ効果を使用します。スコープ外の変数を使用する場合は、次の構文を使用する必要があります:

        [php] plaincopy を表示

        1. $name = 'TIPI チーム'
        2. $func = 関数() use($name) {
        3. } $ func
        4. (); // こんにちは、TIPI チーム
        5. この use ステートメントは、特に Javascript と比較すると、非常にぎこちないように見えますが、これは、JavaScript のスコープとは異なり、PHP 関数で定義された変数は、明示的な定義を除いて、PHP は変数が変更時にローカル変数であるかどうかを判断できません。もちろん、コンパイル時にローカル変数が依然として上位スコープ内の変数であるかどうかを判断する方法があるかもしれません。しかし、これは言語の効率と複雑さに大きな影響を与えます。 この構文は比較的簡単で、上位スコープの変数にアクセスする必要がある場合は、 use ステートメントを使用して宣言する必要があります。これもシンプルで読みやすいです。グローバルステートメントと同様の効果があります。 匿名関数は、実行されるたびに上位スコープの変数にアクセスできます。これらの変数は、次の例のように、匿名関数が破棄される前に常に独自の状態を保存します。 プレーンコピーを見る
          1. function getCounter() {
          2. $i = 0; リターン
          3. 関数() use($i) { // 参照を使用して変数を渡す場合: use(&$i) ++
          4. $i
          5. ; $counter(); // 1
          6. $counter
          7. ();
          8. はJavaScriptとは異なりますここでの 2 つの関数呼び出しでは、$i 変数はインクリメントされません。 デフォルトの PHP では、上位層の変数はコピーによって匿名関数に渡されます。上位層の変数の値を変更する必要がある場合は、その値を渡す必要があります。参考までに。したがって、上記のコードには出力がありませんクロージャの実装
          9. 匿名関数はクロージャを通じて実装されると前述しました。次に、クロージャ (クラス) がどのように実装されるかを見てみましょう。無名関数と通常の関数には、変数名の有無以外に違いはありません。クロージャの実装コードは $PHP_SRC/Zend/zend_closure.c にあります。無名関数の「オブジェクト化」の問題は、無名関数を作成するときに変数にアクセスする方法
          10. たとえば、次のコード: plaincopy を表示
          11. $i=100;
          $counter


          =

          1, 2而是1,1関数

          ()

          使用

          (

          $i

          ) {

          1. debug_zval_dump($i)
          2. $カウンター
          3. (); VLD を使用して、このエンコーディングによってどのような種類のオペコードがコンパイルされるかを確認します[php] プレーンコピーを見る
            1. $ php -dvld.active=1 closure.php
            2. vars: !0 = $i, !1 = $counter
            3. # * op fetch ext return operands
            4. -------------------------------------- ------------------------------------
            5. 0 >   ASSIGN !0, 100
            6. 1 ZEND_DECLARE_LAMBDA_FUNCTION '%00%7Bclosure
            7. 2 ASSIGN !1, ~1
            8. 3 INIT_FCALL_BY_NAME !1
            9. 4 DO_FCALL_BY_NAME 0 0
            10. 5 > RETURN 1
            11. function name: {closure}  
            12. 演算数: 5
            13. コンパイル済み変数: !0 = $i
            14. line # * op fetch ext return オペランド
            15. ---- -------------------------------------------------- ------------------------
            16. 3 0 >   FETCH_R static $0 'i'
            17. 1 割り当て !0, $0
            18. 4 2 SEND_ VAR !0
            19. 3 DO_FCALL 1 」 debug_zval_dump'
            20. 5 4 > RETURN null

            21. 上面状況に応じていくつかの無出力、上から下、第1開始将1 00赋值给!0也就是变量$i,続いて実行ZEND_DECLARE_LAMBDA_FUNCTION,那我们去相关的オペコード执行関数数このオペコードの処理関数は $PHP_SRC/Zend/zend_vm_execute.h 中:

              [php] にあります。 plaincopy を表示

              1. static int ZEND_FASTCALL ZEND_DECLARE_LAMBDA_FUNCTION_SPEC_CONST_CONST_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
              2. {
              3. zend_op *opline = EX(opline);  
              4. zend_function *op_array;  
              5. if (zend_hash_quick_find(EG(function_table), Z_STRVAL(opline->op1.u.constant), Z_STRLEN(opline->op1.u.constant), Z_LVAL(オフライン- >op2.u.constant),(void *) &op_arra
              6. y) == FAILURE ||
              7. op_array->type != ZEND_USER_FUNCTION) {
              8. zend_error_noreturn(E_ERROR, "クロージャのベースラムダ関数が見つかりません);  
              9. }
              10. zend_create_closure(&EX_T(opline->result.u.var).tmp_var, op_array TSRMLS_ CC);  
              11. ZEND_VM_NEXT_OPCODE();  
              12. }

              この関数は zend_create_closure() 関数を呼び出してクロージャ オブジェクトを作成します。それでは、$PHP_SRC/Zend/zend_closures.c にある zend_create_closure() 関数が何をするのか見ていきましょう。

              [php] plaincopy を表示

              1. ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC)
              2. {
              3. zend_closure *closure;
              4. Object_init_ex(res, zend_ce_closure);
              5. クロージャ = (zend_closure *)zend_object_store_get_object(res TSRMLS_CC);
              6. クロージャ->func = *func;
              7. if
              8. (閉鎖-> func.type == ZEND_USER_FUNCTION) { // ユーザー定義の匿名関数の場合
              9. (closure->func.op_array.static_variables) { *static_variables Alloc_hashtable(closure-&gt; func.op_array.static_variables); static_variables);
              10. } (*closure-> ;func.op_array.refcount)++ }
              11. クロージャ- >func.common.scope = NULL;
              12. 上記のコードコメントと同様に、zval_copy_static_var() 関数の実装を見ていきます:
              13. [php] プレーンコピーを見る
                1. static int zval_copy_static_var(zval **p TSRMLS_DC, int num_args, va_​​list args, zend_hash_key *key)
                2. {
                3. HashTable *target = va_arg(args, ハッシュテーブル*);
                4. zend_bool is_ref;
                5. // use ステートメント型の静的変数に対してのみ値操作を実行します。それ以外の場合、匿名関数本体の静的変数はスコープ外の変数にも影響します
                6. if (Z_TYPE_PP(p) & (IS_LEXICAL_VAR|IS_LEXICAL_REF)) {
                7. is_ref = Z_TYPE_PP(p) & IS_LEX ICAL_REF;
                8. if
                9. (!EG ( active_symbol_table)) {
                10. zend_rebuild_symbol_table(TSRMLS_C);現在のスコープにそのような変数がない場合
                11. if
                12. (zend_hash_quick_find(EG( active_symbol_table), key->arKey, key ->nKeyLength, key->h, (void **) &p) == FAILURE) {
                13. (is_ref) { *ZVAL*TMP
                14. ; // 参照変数の場合は、匿名関数定義後に変数を操作するときに一時変数を作成します alloc_init_zval (TMP) ; &tmp, sizeof(zval*), (void) * *)&p); }
                15. {
                16. //この変数が存在する場合、それが参照であるかどうかに応じて、変数を参照またはコピーします
                17. if (is_ref) { _MAKE_IS_REF(p);elseif (Z_ISREF_PP(p)) {
                18. }
                19. }
                20. if
                21. ( zend_hash_quick_add(target, key->arKey 、key->nKeyLength、key->h、p、sizeof(zval*)、NULL) == 成功) { }
                22. returnZEND_HASH_APPLY_KEEP
                23. ; }
                24. この関数は関数にコールバック関数として渡され、ハッシュテーブルの値を読み込む処理のたびにこの関数が使用され、定義された変数値を代入します。この匿名関数の静的変数に対するすべての use ステートメントによって、匿名関数が use 変数にアクセスできるようになります。 元のリンク:http://www.php-internals.com/book/?p=chapt04/04-04-anonymous-function 参考資料:http://php.net/manual/zh/functions.anonymous.php
                25. 上記は、PHP の匿名関数とクロージャーを内容も含めて紹介しましたが、PHP チュートリアルに興味のある友人の参考になれば幸いです。
    声明:
    この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。