前書き:
foreach 構造は php4 で導入され、配列を走査する簡単な方法です。従来の for ループと比較して、foreach はキーと値のペアをより簡単に取得できます。 php5 より前は、foreach は配列にのみ使用できましたが、php5 以降は、オブジェクトの走査にも使用できるようになりました (詳細については、「オブジェクトの走査」を参照)。この記事では、配列のトラバーサルについてのみ説明します。
foreach は単純ですが、特にコードに参照が含まれる場合、予期しない動作が発生する可能性があります。
foreach の本質をさらに理解するために、いくつかのケースを以下に示します。
質問 1:
$arr = array(1,2,3);
foreach($arr as $k => &$v ) {
v = $v * 2;
}
// 現在 $arr は array(2, 4, 6)
foreach($arr as $k => $v) {
echo "$k", " => ", "$v";
}
簡単に始めましょう。上記のコードを実行してみると、最終的な出力は 0=>2 1=>4 2=> であることがわかります。 4.
なぜ 0=>2 1=>4 2=>6 ではないのでしょうか?
実際、foreach($arr as $k => $v) 構造は、配列の現在の 'key' と現在の 'value' をそれぞれ変数 $k と $v に割り当てる次の操作を暗示していると考えることができます。具体的な展開は次のとおりです:
foreach($arr as $k => $v){
// 前に暗黙的に 2 つの代入演算があります。ユーザーコードが実行される
$ v = currentVal();
$k = currentKey();
//ユーザーコードを実行し続ける
……
}
上記の理論に従って、最初のコードを再分析しますforeach:
最初のループ、$v は参照なので、$v = &$arr[0]、$v=$v*2 は $arr[0]*2 と同等なので、$arr は 2,2 になります。 3
2 番目のループ、$v = &$arr[1]、$arr は 2,4,3 になります
3 番目のループ、$v = &$arr[2]、$arr は 2,4,6 になります
その後、コードが入力されます 2 番目の foreach:
最初のループ、暗黙的な操作 $v=$arr[0] がトリガーされます。これは、この時点では $v がまだ $arr[2] への参照であるためであり、これは $arr[ と同等です。 2] =$arr[0]、$arr は 2,4,2 になります
2 番目のループ、$v=$arr[1]、つまり、$arr[2]=$arr[1]、$arr は 2 になります, 4,4
3回目のループ、$v=$arr[2]、つまり$arr[2]=$arr[2]、$arrが2,4,4
となりOK、解析は完了です。
同様の問題を解決するにはどうすればよいですか? PHP マニュアルには注意事項があります:
警告: 配列の最後の要素の $value 参照は、foreach ループの後も保持されます。 unset() を使用して破棄することをお勧めします。
$arr = array(1,2,3);
foreach($arr as $k => &$v) {
$v = $v * 2;
}
unset($v);
foreach($arr as $k => $v) {
echo "$k", " => ", "$v";
}
// Output 0=>2 1=>4 2=>6
この質問から、参照には副作用が伴う可能性が高いことがわかります。意図しない変更によって配列の内容が変更されることを避ける場合は、時間内にこれらの参照の設定を解除することをお勧めします。
質問 2:
$arr = array('a','b','c');
foreach($arr as $k => $ v) {
echo key($arr), "=>", current($arr);
}
// print 1=>b 1=>b 1=>b
この問題はもっと奇妙です。マニュアルによると、keyとcurrentは配列内の現在の要素のキーの値です。
それでは、なぜ key($arr) が常に 1 で、current($arr) が常に b なのか?
まず vld を使用して、コンパイルされたオペコードを表示します。

配列 (' を表す 3 行目の ASSIGN 命令から開始します) a','b','c') は $arr に割り当てられます。
$arr は CV、array('a','b','c') は TMP であるため、見つかった ASSIGN 命令によって実際に実行される関数は ZEND_ASSIGN_SPEC_CV_TMP_HANDLER になります。ここで注意しなければならないのは、CV は PHP5.1 以降に追加された変数キャッシュであり、zval** を保存するために配列を使用します。キャッシュされた変数を再度使用する場合は、アクティブなシンボル テーブルを検索する必要はありません。配列からCVを取得するため、配列のアクセス速度がハッシュテーブルよりもはるかに速いため効率が向上します。
static int ZEND_FASTCALL ZEND_ASSIGN_SPEC_CV_TMP_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zend_free_op free_op2;
zval *value = _get_zval_ptr_tmp(& opline->op2 、EX(Ts)、&free_op2 TSRMLS_CC);
/ / CV 配列に $arr** ポインターを作成します
zval **variable_ptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC);
if (IS_CV == IS_VAR && !variable_ptr_ptr) {
…
}
else {
--'Assign-array------- value = zend_assign_to_variable(variable_ptr_ptr, value, 1 TSRMLS_CC); AI_SET_PTR(EX_T(opline->result .u.var).var, value);
PZVAL_LOCK(value);
}
}
ZEND_VM_NEXT_OPCODE();
}ASSIGN 命令が完了すると、zval** ポインタが CV 配列に追加され、ポインタは実際の配列を指します。これは、$arr がCVによってキャッシュされます。
次に、配列のループ操作を実行します。それに対応する実行関数は

static int ZEND_ファストコールZEND_FE_RESET_SPEC _CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{ … RMLS_CC);
•EX_T(opline->result.u.var).var ---- 配列へのポインタ
•に格納されているポインタEX_T(opline->result.u.var).fe. fe_pos ---- 配列の内部要素へのポインタ
FE_RESET 命令の実行後のメモリ内の実際の状況は次のとおりです。
次に、引き続き FE_FETCH を見ていきます。対応する実行関数は ZEND_FE_FETCH_SPEC_VAR_HANDLER です:
コードをコピーします
コードは次のとおりです:
Tataticint zend_fastcall zend_fe_fetch_spec_var_handler(zend_opcode_handler_args)
{
zend_op *opline = ex(opline); * array = EX_T(opline->op1.u.var).var.ptr; ……
switch (zend_iterator_unwrap(array, &iter TSRMLS_CC)) {
デフォルト:
case ZEND_ITER_INVALID:
……
case ZEND_ITER_PLAIN_OBJECT: { // FE_RESET 命令では、配列の内部要素へのポインタが EX_T(opline->op1.u.var) に保存されます
ここでポインターを取得します
zend_hash_set_pointer(fe_ht, &EX_T( opline->op1.u.var).fe.fe_pos); If (zend_hash_get_current_data(fe_ht, (void **) &value)==FAILURE) {
key_type = zend_hash_get_current_key_ex(fe_ht, &str_key, &str_key_len, &int_key, 1, NULL );
移動されたポインタは EX_T(opline->op1.u .var).fe.fe_pos
zend_hash_get_pointer(fe_ht, &EX_T(opline->op1.u.var).fe.fe_pos に保存されます); ……
}
……
}
FE_FETCH の実装に基づいて、foreach($arr as $k => $v) が何をするのかを大まかに理解しました。 zend_execute_data->Ts のポインタに基づいて配列要素を取得します。取得が成功すると、ポインタを次の位置に移動して再度保存します。
簡単に言えば、最初のループで配列の内部ポインタが FE_FETCH の 2 番目の要素に移動されているため、foreach 内で key($arr) と current($arr) が呼び出されるとき、実際に取得されるのは 1 と ' b'.
では、なぜ1=>bが3回出力されるのでしょうか?
引き続き、9 行目と 13 行目の SEND_REF 命令を見てみましょう。これは、$arr パラメーターをスタックにプッシュすることを意味します。次に、通常は DO_FCALL 命令を使用してキー関数と現在の関数を呼び出します。 PHP はネイティブ マシン コードにコンパイルされないため、PHP はそのようなオペコード命令を使用して、実際の CPU とメモリがどのように動作するかをシミュレートします。
PHP ソース コードの SEND_REF を確認します:
コードをコピーします
コードは次のとおりです:
static int ZEND_FASTCALL ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
……
// CV から $arr ポインタのポインタを取得
varptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX (Ts)、BP_VAR_W TSRMLS_CC);
…
// 変数の分離。これはキー関数専用の配列の新しいコピーです
SEPARATE_ZVAL_TO_MAKE_IS_REF(varptr_ptr);
varptr = *varptr_ptr;
Z_ADDREF_P(varptr);
// スタックをプッシュします
zend_ vm_stack_push(varptr) ;
ZEND_VM_NEXT_OPCODE();
}
SEPARATE_ZVAL_TO_MAKE_IS_REF はマクロです:
#define SEPARATE_ZVAL_TO_MAKE_IS _REF(ppzv)
if (!PZVAL_IS_REF( *ppzv)) {
{ SEPARATE_ZVAL(ppzv);主な機能は、変数が参照でない場合にメモリ内に新しいコピーをコピーすることです。この例では、array('a','b','c') をコピーします。したがって、変数分離後のメモリは次のようになります:
変数分離が完了した後、CV 配列内のポインターは新しくコピーされたデータを指し、古いデータは引き続き zend_execute_data->Ts 内のポインターを通じて取得できることに注意してください。

•foreach 構造は、以下の青い配列を使用し、a、b、c を順番に走査します
•キーと現在の用途は黄色です。上記の配列では、その内部ポインタは常に b を指します
これで、key と current が常に配列の 2 番目の要素を返す理由がわかりました。コピーされた配列には外部コードが作用しないため、その内部ポインタは決して移動しません。 質問 3:
コードをコピーします
コードは次のとおりです:
この質問と質問 2 の違いは 1 つだけです。この質問の foreach では参照が使用されています。
VLD を使用してこの質問を確認し、質問 2 のコードからコンパイルされたオペコードが同じであることを確認します。したがって、質問 2 の追跡方法を使用して、対応するオペコードの実装を徐々にチェックします。
コードをコピーします
コードは次のとおりです:
static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
……
if (opline->extended_value & ZEND_FE_RESET_VARIABLE) {
_get_z = val_ptr_ptr_cv(&opline->op1, EX(Ts), TSRMLS_CC);
R _ptr ) == IS_OBJECT) {
/ 配列を走査する場合
使用する 使用する 使用する 使用する 使用する 使用する 使用する 使用する 使用する を通じて を通じて を通じて を通じて を通じて を通じて を通じて を通じて ‐ ‐ ‐ ‐ ‐ に) array_ptr_ptr );
FE_RESET
この例では、foreach が参照を使用して値を取得するため、実行中に FE_RESET は前の質問とは別の分岐に入ります。
最後に、FE_RESET は配列の is_ref を true に設定します。この時点では、メモリ内には配列データのコピーが 1 つだけあります。
次に SEND_REF を分析します:
コードをコピーします
コードは次のとおりです:
static int ZEND_FASTCALL ZEND_SEND_REF_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
…
// CVから$arrポインタのポインタを取得する
varptr_ptr = _get_zval_ptr_ptr_cv (&opline->op1, EX(Ts), BP_VAR_W TSRMLS_CC);
……
SEPARATE_ZVAL_TO_MAKE_IS_REF (varptr_ptr); varptr = *varptr_ptr; Z_ADDREF_P(varptr);
// スタックをプッシュします zend_vm_stack_push(varptr TSRMLS_CC);
ZEND_VM_NEXT_OPCODE(); }
上の図は、最初の 2 つのループが 1=>b 2=>C を出力する理由を説明しています。 FE_FETCH の 3 番目のサイクル中に、ポインタを前進させ続けます。
コードをコピーします
コードは次のとおりです:
ZEND_API int zend_hash_move_forward_ex(HashTable *ht, HashPosition *pos)
{
HashPosition *current = pos ? : &ht->pInternalPointer;
IS_CONSISTENT( ht);
if (*現在) {
この時点で内部ポインタはすでに配列の最後の要素を指しているため、先に進むと NULL を指すことになります。内部ポインタを NULL に指定した後、配列の key と current を呼び出すと、それぞれ NULL と false が返され、この時点では文字はエコーされません。
質問 4:
$arr = array(1, 2, 3);
$tmp = $arr;
foreach($tmp as $ k => &$v){
$v *= 2;
}
var_dump($arr, $tmp); // 何を出力するか?
この質問は foreach とはあまり関係ありませんが、foreach に関係するので一緒に議論しましょう:)
コードでは、最初に配列 $arr が作成され、次にその配列が $tmp に割り当てられます。 foreach ループ内で $v を変更すると、配列 $tmp に影響しますが、$arr には影響しません。
なぜですか?
これは、PHP では代入操作によって 1 つの変数の値が別の変数にコピーされるため、一方を変更しても他方には影響しないからです。
余談: これはオブジェクト型には当てはまりません。PHP5 以降、オブジェクトはデフォルトで常に参照によって割り当てられます。 例:
class A{
public $foo = 1;
}
$a1 = $a2 = new A;
$a1->foo=100;
echo $a2->foo; // 出力 100、$a1 および $a2 は実際には同じオブジェクト参照
質問のコードに戻ると、$tmp=$arr が実際には値のコピーであり、$arr 配列全体が $tmp にコピーされることが確認できます。理論的には、代入ステートメントが実行された後、メモリ内に同じ配列のコピーが 2 つ存在します。
配列が大きい場合、この操作は非常に遅くなるのではないかと疑問に思う生徒もいるかもしれません。
幸いなことに、php にはこれを処理するより賢い方法があります。実際、$tmp=$arr が実行された後も、メモリには配列が 1 つだけ存在します。 PHP ソース コード (php5.3.26 から抽出) の zend_assign_to_variable 実装を表示します:
static inline zval* zend_assign_to_variable(zval **variable_ptr_ptr, zval *value, int is_tmp_var TSRMLS_DC)
{
zval *variable_ptr = *variable_ptr_ptr;
zval Garage;
…
// 左の値はオブジェクト型
if (Z_TYPE_P (variable_ptr)== is_object && z_obj_handler_p(variable_ptr、set)){
…
}} else {
// refcount__gc = 1
)&& z_refcount_p(value)>
/ / $ TMP = $ ARR がここで実行されます。
// value は、$ Arr を指す実際の Array データへのポインタです。 Variable_ptr_ptr は、$ TMP のデータ ポインタを指すポインタです
// ポインタをコピーするだけです。配列
* variable_ptr_ptr = 値; variable_ptr_ptr);
}
return *variable_ptr_ptr;
}
$tmp = $arr の本質は配列のポインタをコピーすることであることがわかります。このときのメモリを図で表現すると、配列は 1 つだけです:
配列は 1 つだけなので、foreach で $tmp が変更されると、ループに応じて $arr が変更されないのはなぜですか?
引き続き PHP ソース コードの ZEND_FE_RESET_SPEC_CV_HANDLER 関数を見てください。これは OPCODE HANDLER であり、対応する OPCODE は FE_RESET です。この関数は、foreach が開始される前に、配列の内部ポインタを最初の要素に設定する役割を果たします。
コードをコピーします
コードは次のとおりです:
static int ZEND_FASTCALL ZEND_FE_RESET_SPEC_CV_HANDLER(ZEND_OPCODE_HANDLER_ARGS)
{
zend_op *opline = EX(opline);
zval *array_ptr, **array_ptr_ptr;
HashTable *fe_ht;
zend_object_iterator *iter = NULL;
zend_class_entry *ce = NULL;
zend_bool is_empty = 0;
// 对变量行FE_RESET
if (opline->extended_value & ZEND_FE_RESET_VARIABLE) {
array_ptr_ptr = _get_zval_ptr_ptr_cv(&opline->op1, EX(T) s)、BP_VAR_R TSRMLS_CC);
if (array_ptr_ptr == NULL || array_ptr_ptr == &EG(uninitialized_zval_ptr)) {
……
}
// foreach一个オブジェクト
else if (Z_TYPE_PP(array_ptr_ptr) == IS_OBJECT) {
……
}
else {
// 本例会进入该分支
if (Z_TYPE_PP(array_ptr_ptr) == IS_ARRAY) {
// 注意此处的SEPARATE_ZVAL_IF_NOT_REF
// 它会重新复制一个数组出来
// 真正分离$tmpand$arr,变成了内存中的2个数组
SEPARATE_ZVAL_IF_NOT_REF(array_ptr_ptr);
if (opline->extended_value & ZEND_FE_FETCH_BYREF) {
Z _SET_ISREF_PP(array_ptr_ptr);
}
}
array_ptr = *array_ptr_ptr;
Z_ADDREF_P(array_ptr);
}
} else {
… …
}
// 重置数組内部指针
……
}
从代码中可以見出,真正行变量分离并不赋值语句行時候,而是了了使用推量時の、天気これも PHP での Copy On Write 機構の実現です。
FE_RESET 後の内部格納の変更は次のようになります。興味深い同学は、ZEND_FE_RESET_SPEC_CV_HANDLER と ZEND_SWITCH_FREE_SPEC_VAR_HANDLER の具体的な動作 (両方とも php-src/zend/zend_vm_execute.h にあります)、本文では説明しません:)

http://www.bkjia.com/PHPjc/327985.html
www.bkjia.com

PHP and Python each have their own advantages, and the choice should be based on project requirements. 1.PHPは、シンプルな構文と高い実行効率を備えたWeb開発に適しています。 2。Pythonは、簡潔な構文とリッチライブラリを備えたデータサイエンスと機械学習に適しています。

PHPは死にかけていませんが、常に適応して進化しています。 1)PHPは、1994年以来、新しいテクノロジーの傾向に適応するために複数のバージョンの反復を受けています。 2)現在、電子商取引、コンテンツ管理システム、その他の分野で広く使用されています。 3)PHP8は、パフォーマンスと近代化を改善するために、JITコンパイラおよびその他の機能を導入します。 4)Opcacheを使用してPSR-12標準に従って、パフォーマンスとコードの品質を最適化します。

PHPの将来は、新しいテクノロジーの傾向に適応し、革新的な機能を導入することで達成されます。1)クラウドコンピューティング、コンテナ化、マイクロサービスアーキテクチャに適応し、DockerとKubernetesをサポートします。 2)パフォーマンスとデータ処理の効率を改善するために、JITコンパイラと列挙タイプを導入します。 3)パフォーマンスを継続的に最適化し、ベストプラクティスを促進します。

PHPでは、特性は方法が必要な状況に適していますが、継承には適していません。 1)特性により、クラスの多重化方法が複数の継承の複雑さを回避できます。 2)特性を使用する場合、メソッドの競合に注意を払う必要があります。メソッドの競合は、代替およびキーワードとして解決できます。 3)パフォーマンスを最適化し、コードメンテナビリティを改善するために、特性の過剰使用を避け、その単一の責任を維持する必要があります。

依存関係噴射コンテナ(DIC)は、PHPプロジェクトで使用するオブジェクト依存関係を管理および提供するツールです。 DICの主な利点には、次のものが含まれます。1。デカップリング、コンポーネントの独立したもの、およびコードの保守とテストが簡単です。 2。柔軟性、依存関係を交換または変更しやすい。 3.テスト可能性、単体テストのために模擬オブジェクトを注入するのに便利です。

SplfixedArrayは、PHPの固定サイズの配列であり、高性能と低いメモリの使用が必要なシナリオに適しています。 1)動的調整によって引き起こされるオーバーヘッドを回避するために、作成時にサイズを指定する必要があります。 2)C言語アレイに基づいて、メモリと高速アクセス速度を直接動作させます。 3)大規模なデータ処理とメモリに敏感な環境に適していますが、サイズが固定されているため、注意して使用する必要があります。

PHPは、$ \ _ファイル変数を介してファイルのアップロードを処理します。セキュリティを確保するための方法には次のものが含まれます。1。アップロードエラー、2。ファイルの種類とサイズを確認する、3。ファイル上書きを防ぐ、4。ファイルを永続的なストレージの場所に移動します。

JavaScriptでは、nullcoalescingoperator(??)およびnullcoalescingsignmentoperator(?? =)を使用できます。 1.??最初の非潜水金または非未定されたオペランドを返します。 2.??これらの演算子は、コードロジックを簡素化し、読みやすさとパフォーマンスを向上させます。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

AI Hentai Generator
AIヘンタイを無料で生成します。

人気の記事

ホットツール

EditPlus 中国語クラック版
サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

SublimeText3 Linux 新バージョン
SublimeText3 Linux 最新バージョン

WebStorm Mac版
便利なJavaScript開発ツール

ゼンドスタジオ 13.0.1
強力な PHP 統合開発環境

AtomエディタMac版ダウンロード
最も人気のあるオープンソースエディター
