PHPクロージャ関数

巴扎黑
巴扎黑オリジナル
2016-11-11 13:41:151556ブラウズ

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

現在、匿名関数は 5.3 まではサポートされていませんでした。関数では、C++の新しい標準C++0xもサポートされ始めました。

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

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

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

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

$ array = array(1, 2, 3, 4);
array_walk($array, create_function('$value', 'echo $value'));
このコードは配列内の値を出力するだけですもちろん、順番に、さらに多くのことができます。 では、なぜこれが真の匿名関数ではないのでしょうか? まず、この関数の戻り値を見てみましょう。通常、この関数は次のような関数を呼び出すことができます。 () {
echo 'function a'
}

$a = 'a';
例:

ビューを実装するときにこのメソッドを使用することもできます。 plaincopy

function do_something($callback) {
// 実行中
# ...

// 完了
$callback()
}
これは実行後に関数 do_something() で実装できます。完了すると、$callback で指定された関数が呼び出されます。 create_function 関数の戻り値に戻ると、この関数は一意の文字列関数名を返すか、エラーが発生した場合は FALSE を返します。したがって、この関数は関数を動的に作成するだけであり、この関数には関数名があります。つまり、実際には匿名ではありません。グローバルに一意な関数を作成するだけです。

[php] view plaincopy
$func = create_function('', 'echo "関数が動的に作成されました";');
echo $func; // lambda_1

$func(); Dynamic

$my_func = 'lambda_1';
$my_func(); // この関数は存在しません
lambda_1(); // この関数は存在しません
上記のコードの先頭は理解しやすいです。は 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 関数を使用して見てみましょう。

[php] ビュー plaincopy
$func = create_function('', 'echo "Hello";');
$my_func_name = 'lambda_1' // string(9); lambda_1" ​​refcount(2)
debug_zval_dump($my_func_name); // string(8) "lambda_1" ​​refcount(2)

ほら、実際には長さが異なります。長さが異なるということは、同じではないということです。もちろん、私たちが呼び出す関数は存在しません。create_function 関数が何をするのか見てみましょう。実装を参照してください: $PHP_SRC/Zend/zend_builtin_functions.c

[php] view plaincopy
#define LAMBDA_TEMP_FUNCNAME "__lambda_func"

ZEND_FUNCTION(create_function)
{
// ... 無関係なコードを省略します
Function_name = (char * ) emalloc(sizeof("0lambda_")+MAX_LENGTH_OF_LONG);
function_name[0] = 'これは Javascript では非常に一般的ですが、PHP では不可能です。オブジェクトのプロパティをコピーすると、PHP で定義されたメソッドが検索されます。これは PHP のクラス モデルによって決定されます。もちろん、PHP ではこの点が改善される可能性があり、これにより一部の関数を柔軟に実装できるようになります。現時点では、この効果を実現する方法があります。それは、別の魔法のメソッド __call() を使用することです。これを実現する方法については、読者の演習として残しておきます。

クロージャの使用
PHP は、匿名関数を実装するためにクロージャ (Closure) を使用します。匿名関数の最も強力な機能は、匿名関数を定義するときに必要に応じて、匿名関数によって提供される動的機能とクロージャ効果の一部です。スコープを指定するには、次の構文を使用する必要があります:

[php] view plaincopy
$name = 'TIPI Team';
$func = function() use($name) {
echo "こんにちは、$name ";
}

$func(); // TIPI チーム、こんにちは

この use ステートメントは、特に Javascript と比較すると非常にぎこちないように見えますが、これは総合的な検討の後、PHP-Core でも使用されるべきです。構文が異なるため、 Javascript のスコープでは、PHP 関数で定義された変数はデフォルトでローカル変数ですが、JavaScript ではその逆で、明示的に定義された変数を除いて、変数が変更されるときにその変数がローカル変数であるかどうかを判断できません。もちろん、上位スコープについてはコンパイル時に決定する方法があるかもしれませんが、これは言語の効率と複雑さに大きな影響を与えます。

この構文は比較的簡単で、上位スコープの変数にアクセスする必要がある場合は、 use ステートメントを使用して宣言する必要があります。これもシンプルで読みやすいです。グローバルステートメントと同様の効果があります。

匿名関数は、実行されるたびに上位スコープの変数にアクセスできます。これらの変数は、次の例のように、匿名関数が破棄される前に常に独自の状態を保存します。
function getCounter() {
$i = 0;
return function() use($i) { // ここで参照を使用して変数を渡す場合: use(&$i) echo ++$i;
};

$counter();
$counter(); // 1

は、$i をインクリメントしません。デフォルトでは、PHP は上位レベルの変数をコピーして匿名関数に渡します。上位レベルの変数の値を変更する必要がある場合は、それらを参照によって渡す必要があります。したがって、上記のコードは 1, 2 を出力せず、1, 1 を出力します。

クロージャの実装
前述したように、匿名関数はクロージャによって実装されます。ここで、クロージャ (クラス) がどのように実装されるかを見ていきます。無名関数と通常の関数には、変数名の有無以外に違いはありません。クロージャの実装コードは $PHP_SRC/Zend/zend_closure.c にあります。匿名関数の「オブジェクト化」の問題は Closure によって実現されており、匿名関数を作成するときに変数にアクセスする方法は次のとおりです。

たとえば、次のコード:

[php] view plaincopy
$i =100;
$counter = function() use($i) {
debug_zval_dump($i)
};
$counter();

このコードがどのような種類のオペコードをコンパイルするかを確認します

] view plaincopy
$ php -dvld.active=1 Closure.php

vars: !0 = $i, !1 = $counter
# * op fetch ext return オペランド
------ ----- -------------------------------------- ----- -----------
0 > ASSIGN '%00%7B閉鎖
2 ASSIGN !1, ~1
3 INIT_FCALL_BY_NAME !1
4 DO_FCALL_BY_NAME 0
5 > RETURN 1

関数名: {closure}
演算数: 5
コンパイル済み変数: !0 = $i
line # * op fetch ext return オペランド
---------------------------------------------- ----------------------------------
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 つまり量 $私、続いて実行しますZEND_DECLARE_LAMBDA_FUNCTION、那我们関連するオペコード実行関数中見ますここで実行されます、このオペコードの処理関数は$PHP_SRC/Zend/zend_vm_execute.h中にあります:

[php] ビュー静的整数 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, "クロージャのベースラムダ関数が見つかりません");  
}

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() 関数が行います。

[php] プレーンコピーを表示します
ZEND_API void zend_create_closure(zval *res, zend_function *func TSRMLS_DC)
{
zend_closure *closure;

クロージャー = (zend_clo)確かに *)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; s t ‐ ‐ using with using (*closure->func.op_array.refcount)++; :

[php] view plaincopy
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_参照

//使用ステートメントタイプの静的変数で値操作のみを実行します。そうしないと、匿名関数本文の静的変数はスコープ外の変数にも影響します。この変数が現在のスコープに存在しない場合、 key->arKey、key->nKeyLength、key->h、(void **) &p) == FAILURE) {
その後、一時変数を作成します。匿名関数が定義されています。 Z_SET_ISREF_P(tmp); key->arKey、key->nKeyLength、&tmp 、sizeof(zval*)、(void**)&p); } その他 {
//それが参照ではない場合、それは変数が存在しないことを意味します->arKey, key->nKeyLength, key->h, p, sizeof(zval*), NULL) == SUCCESS) {


この関数はコールバック関数として zend_hash_apply_with_arguments() 関数に渡されます。ハッシュ テーブル内の値が読み取られ、この関数によって処理され、この関数が use ステートメントで定義された変数値をこの匿名関数の静的な値に代入することで、匿名関数が use 変数にアクセスできるようになります。 。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。