ホームページ  >  記事  >  バックエンド開発  >  PHPでforeachを使用および参照した後の例外処理

PHPでforeachを使用および参照した後の例外処理

小云云
小云云オリジナル
2018-03-16 11:40:511604ブラウズ

可能在PHP编码中使用&引用变量或者对象或者方法的人不多,但是&引用可以让你的代码变的简单而且节省资源消耗。在这篇文章中我们重点讨论的是foreach中使用&时出现的异常以及解决办法。

$exp = [
            [                'name' => 'test1',                'age' => 15,                'extension' => 'a:3:{s:4:"nose";s:4:"long";s:5:"mouth";s:3:"big";s:3:"eye";s:5:"small";}'
            ],
            [                'name' => 'test2',                'age' => 25,                'extension' => 'a:3:{s:4:"nose";s:5:"long2";s:5:"mouth";s:4:"big2";s:3:"eye";s:6:"small2";}'
            ],
            [                'name' => 'test4',                'age' => 18,                'extension' => 'a:3:{s:4:"nose";s:5:"long2";s:5:"mouth";s:4:"big2";s:3:"eye";s:6:"small2";}'
            ],
            [                'name' => 'test3',                'age' => 20,                'extension' => 'a:3:{s:4:"nose";s:5:"long3";s:5:"mouth";s:4:"big3";s:3:"eye";s:6:"small3";}'
            ],
        ];        foreach ($exp as &$v) {            $extension = @unserialize($v['extension']);            $v['nose'] = $extension['nose'] ?? "";            $v['mouth'] = $extension['mouth'] ?? "";            $v['eye'] = $extension['eye'] ?? "";
        }        $newExp = [];        foreach ($exp as $v) {            if ($v['mouth'] == "big3"){                $newExp[] = $v;
            }
        }
        dump($newExp);        exit;

这部分代码的功能描述如下:

1.将exp中的扩展字段混入到exp中2.如果exp中mouth为big3则赋值给新数组newExp3.输出newExp

从简单的表象来分析貌似以上逻辑并没有错,而且我们预测输出的结果应该为

...0 => array:6 [▼    "name" => "test3"
    "age" => 20
    "extension" => "a:3:{s:4:"nose";s:5:"long3";s:5:"mouth";s:4:"big3";s:3:"eye";s:6:"small3";}"
    "nose" => "long3"
    "mouth" => "big3"
    "eye" => "small3"
  ]
  ...
  但是结果并不是我们所预测的那样,程序输出的结果为:
  []
  这是为什么呢,我们来逐一分析

foreach引用引发的异常

第一个foreach是以下的代码块

foreach ($exp as &$v) {     $extension = @unserialize($v['extension']);     $v['nose'] = $extension['nose'] ?? "";     $v['mouth'] = $extension['mouth'] ?? "";     $v['eye'] = $extension['eye'] ?? "";
}

,在该代码块中使用了&v。因为我们这一步要做的事情是处理数组本身的数据所以使用引用对于内存消耗较少。在程序执行中

v应该就是exp最后一个元素的引用。
那么当我们修改$v的值应该exp的最后一个元素会变化。而且还有一个非常重要的问题就是foreach中使用了引用后引用在foreach结束后任然是存在的。也就是在以上的foreach之外$v依旧引用exp最后一个元素

在foreach后$v是否还存在

...foreach ($exp as &$v) {    $extension = @unserialize($v['extension']);    $v['nose'] = $extension['nose'] ?? "";    $v['mouth'] = $extension['mouth'] ?? "";    $v['eye'] = $extension['eye'] ?? "";
}  
dump($v);
输出结果为:array:6 [▼  "name" => "test3"
  "age" => 20
  "extension" => "a:3:{s:4:"nose";s:5:"long3";s:5:"mouth";s:4:"big3";s:3:"eye";s:6:"small3";}"
  "nose" => "long3"
  "mouth" => "big3"
  "eye" => "small3"]

第二个循环分析

$newExp = []; foreach ($exp as $v) {     if ($v['mouth'] == "big3"){         $newExp[] = $v;
     }
 }
 dump($newExp);

在这儿我们是做了一个常规的循环来循环exp而且在该循环中我们使用的是变量并没有使用引用。差别就是$v&$v请仔细看。
在这个循环中其实$v依旧是exp最后一个元素的引用。那么在循环中其实每次都是奖exp当前(current)的值赋值给$v因为引用关系最终改变的是exp最后一个元素的值。那么在foreach中exp最后子元素的值一直是变的。演变过程如下

//为了篇幅简略表示//第一次循环exp变为:也就是第一个元素赋值给了最后一个元素[
    [        'name' => 'test1',
        ...
    ],
    [        'name' => 'test2',
        ...
    ],
    [        'name' => 'test4',
        ...
    ],
    [        'name' => 'test1',
        ...
    ],
]//第二次循环exp变为:也就是第二个元素赋值给了最后一个元素[
    [        'name' => 'test1',
        ...
    ],
    [        'name' => 'test2',
        ...
    ],
    [        'name' => 'test4',
        ...
    ],
    [        'name' => 'test2',
        ...
    ],
]//第三次循环exp变为:也就是第三个元素赋值给了最后一个元素[
    [        'name' => 'test1',
        ...
    ],
    [        'name' => 'test2',
        ...
    ],
    [        'name' => 'test4',
        ...
    ],
    [        'name' => 'test4',
        ...
    ],
]//第四次循环exp变为:也就是第四个元素赋值给了最后一个元素 循环完毕[
    [        'name' => 'test1',
        ...
    ],
    [        'name' => 'test2',
        ...
    ],
    [        'name' => 'test4',
        ...
    ],
    [        'name' => 'test4',
        ...
    ],
]

原因分析

从上可以看出虽然本来exp的最后一个元素复合if条件中的 $v['mouth'] == "big3",但是在循环最后一个元素时其本身已经变成了第三个元素,所以mouth=big3的元素不存在了。这个流程有点儿绕,多看几遍就能看得懂。当然你也可以看看PHP的zend引擎中关于foreach的实现以及查看VLD中间代码,例如简单循环的VLD

number of ops:  16compiled vars:  !0 = $arr, !1 = $key, !2 = $rowline     # *  op                           fetch          ext  return  operands---------------------------------------------------------------------------------   2     0  >   INIT_ARRAY                                       ~0      1
         1      ADD_ARRAY_ELEMENT                                ~0      2
         2      ADD_ARRAY_ELEMENT                                ~0      3
         3      ADD_ARRAY_ELEMENT                                ~0      4
         4      ADD_ARRAY_ELEMENT                                ~0      5
         5      ASSIGN                                                   !0, ~0
   4     6    > FE_RESET                                         $2      !0, ->14
         7  > > FE_FETCH                                         $3      $2, ->14
         8  >   ZEND_OP_DATA                                     ~5
         9      ASSIGN                                                   !2, $3
        10      ASSIGN                                                   !1, ~5
   5    11      ECHO                                                     !1
        12      ECHO                                                     !2
   6    13    > JMP                                                      ->7
        14  >   SWITCH_FREE                                              $2
   7    15    > RETURN                                                   1

以上がPHPでforeachを使用および参照した後の例外処理の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

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