招聘

标签(空格分隔): 招聘 PHP 国贸


语言基础

foreach 语法结构提供了遍历数组的简单方式。

php5之前, foreach仅能用于数组
php5+, 利用foreach可以遍历对象

foreach仅能够应用于数据和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。

有两种语法:

  1. /*
  2. 遍历给定的 array_expression 数据。每次循环中, 当前单元的值被赋给$value并且数组内部的指针向前移一步(因此下次循环中将会得到下一个单元)
  3. */
  4. foreach (array_expression as $value) {
  5. // statement
  6. }
  7. foreach (array_expression as $value) :
  8. // statement
  9. endforeach;
  1. /*
  2. 同上,只除了当前单元格的键名也会在每次循环中被赋给变量$key
  3. */
  4. foreach (array_expression as $key => $value) {
  5. // statement
  6. }
  7. foreach (array_expression as $key => $value) :
  8. // statement
  9. endforeach;

还能够自定义遍历对象!

foreach开始执行时, 数组内部的指针会自动指向第一个单元. 这意味着不需要在foreach循环之前调用reset()
由于foreach依赖内部数组指针, 在循环中修改其值将可能导致意外的行为

可以很容易通过在 $value 之前加上 & 来修改数组元素. 此方法将以引用 赋值, 而不是拷贝一个值.

  1. <?php
  2. $arr = [1, 2, 3, 4];
  3. foreach($arr as &$value) {
  4. $value = $value * 2;
  5. }
  6. // $arr is now [2, 4, 6, 8]
  7. unset($value); // 最后取消掉引用

$value的引用仅在被遍历的数组可以被引用时才可用(例如是个变量)。

以下代码无法运行:


  1. <?php
  2. /*
  3. 此段代码可以运行
  4. 运行结果:
  5. 1-2
  6. 2-4
  7. 3-6
  8. 4-8
  9. */
  10. foreach (array(1, 2, 3, 4) as &$value) {
  11. echo $value, '-';
  12. $value = $value * 2;
  13. echo $value, PHP_EOL;
  14. }

Warning: 数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用 unset() 来将其销毁。

Note: foreach 不支持用 @ 来抑制错误信息的能力

foreach 虽然简单, 不过它可能出现一些意外行为, 特别是代码涉及到引用的时候。

问题研究

问题一: 如下代码运行结果为何不是 2/4/6 ?

  1. <?php
  2. $arr = [1, 2, 3];
  3. foreach ($arr as $k => &$v) {
  4. $v = $v * 2;
  5. }
  6. foreach ($arr as $k => $v) {
  7. echo $v, PHP_EOL;
  8. }
  9. /*
  10. 输出:
  11. 2
  12. 4
  13. 4
  14. */

我们可以认为 foreach($arr as &$v) 结构隐含了如下操作, 分别将数组当前的 赋值给 $k$v. 具体展开形如:


  1. <?php
  2. foreach ($arr as $k => $v) {
  3. $k = currentKey();
  4. $v = currentVal();
  5. // 继续运行用户代码
  6. }

根据上述理论, 现在我们重新来分析下第一个foreach:

循环 备注 $arr值
循环 1-1 由于$v是一个引用, 因此 $v = &$arr[0], $v = $v * 2 相当于 $arr[0] * 2 [2, 2, 3]
循环 1-2 $v = &$arr[1] [2, 4, 3]
循环 1-3 $v = &$arr[2] [2, 4, 6]
循环 2-1 隐含操作 $v = $arr[0] 被触发, 由于此时 $v 仍是 $arr[2] 的引用, 相当于 $arr[2] = $arr[0] [2, 4, 2]
循环 2-2 $v = $arr[1], 即$arr[2] = $arr[1] [2, 4, 4]
循环 2-3 $v = $arr[2], 即$arr[2] = $arr[2] [2, 4, 4]

如何解决此类问题呢? PHP手册上有一段提醒:

Warning: 数组最后一个元素的 $value 引用在 foreach 循环之后仍会保留。建议使用 unset() 来将其销毁。

  1. <?php
  2. $arr = [1, 2, 3];
  3. foreach ($arr as $k => &$v) {
  4. $v = $v * 2;
  5. }
  6. unset($v);
  7. foreach ($arr as $k => $v) {
  8. echo $v, PHP_EOL;
  9. }
  10. /*
  11. 输出:
  12. 2
  13. 4
  14. 6
  15. */

从这个问题可以看出, 引用很可能会伴随副作用。如果不希望无意识的修改导致数据内容变更, 最好及时unset掉这些引用。

问题二: 如下代码运行结果为何不是 0=>a 1=>b 2=>c

  1. <?php
  2. $arr = ['a', 'b', 'c'];
  3. foreach ($arr as $k => $v) {
  4. echo key($arr), "=>", current($arr), PHP_EOL;
  5. }
  6. foreach ($arr as $k => &$v) {
  7. echo key($arr), "=>", current($arr), PHP_EOL;
  8. }
  9. /*
  10. #php5.6
  11. 1=>b 1=>b 1=>b
  12. 1=>b 2=>c =>
  13. #php7
  14. 0=>a 0=>a 0=>a
  15. 0=>a 0=>a 0=>a
  16. */

按照手册中的说法, key和current分别是获取数据中当前元素的键值。
那为何 key($arr) 一直是0,current($arr) 一直是'a'呢?

先用vld查看编译后的 opcode:


  1. demo /usr/local/Cellar/php/7.2.7/bin/php -dvld.active=1 a.php
  2. Finding entry points
  3. Branch analysis from position: 0
  4. Jump found. (Code = 77) Position 1 = 2, Position 2 = 15
  5. Branch analysis from position: 2
  6. Jump found. (Code = 78) Position 1 = 3, Position 2 = 15
  7. Branch analysis from position: 3
  8. Jump found. (Code = 42) Position 1 = 2
  9. Branch analysis from position: 2
  10. Branch analysis from position: 15
  11. Jump found. (Code = 62) Position 1 = -2
  12. Branch analysis from position: 15
  13. filename: /Users/jianyong/demo/a.php
  14. function name: (null)
  15. number of ops: 17
  16. compiled vars: !0 = $arr, !1 = $v, !2 = $k
  17. line #* E I O op fetch ext return operands
  18. -------------------------------------------------------------------------------------
  19. 2 0 E > ASSIGN !0, <array>
  20. 4 1 > FE_RESET_R $4 !0, ->15
  21. 2 > > FE_FETCH_R ~5 $4, !1, ->15
  22. 3 > ASSIGN !2, ~5
  23. 5 4 INIT_FCALL 'key'
  24. 5 SEND_VAR !0
  25. 6 DO_ICALL $7
  26. 7 ECHO $7
  27. 8 ECHO '%3D%3E'
  28. 9 INIT_FCALL 'current'
  29. 10 SEND_VAR !0
  30. 11 DO_ICALL $8
  31. 12 ECHO $8
  32. 13 ECHO '%0A'
  33. 14 > JMP ->2
  34. 15 > FE_FREE $4
  35. 7 16 > RETURN 1
  36. branch: # 0; line: 2- 4; sop: 0; eop: 1; out1: 2; out2: 15
  37. branch: # 2; line: 4- 4; sop: 2; eop: 2; out1: 3; out2: 15
  38. branch: # 3; line: 4- 5; sop: 3; eop: 14; out1: 2
  39. branch: # 15; line: 5- 7; sop: 15; eop: 16; out1: -2
  40. path #1: 0, 2, 3, 2, 15,
  41. path #2: 0, 2, 15,
  42. path #3: 0, 15,
  43. 0=>a
  44. 0=>a
  45. 0=>a

PHP7新特性之foreach

  • [x] foreach 循环对数组内部指针不再起作用, 在PHP7之前, 当数据通过foreach迭代时, 数组指针会移动。
  1. <?php
  2. $array = [0, 1, 2];
  3. foreach ($array as &$val) {
  4. var_dump(current($array));
  5. }
版本 结果 说明
PHP5 int(1) int(2) bool(false) 数组指针会移动
PHP7 int(0) int(0) int(0) 数据指针不再移动
  • [x] 按照值进行循环时, 对数组的修改是不会影响循环。

foreach按照值进行循环的时候(by-value), foreach是对该数组的一个拷贝进行操作. 所以在循环过程中修改不影响循环结果

  1. <?php
  2. $arr = [0, 1, 2];
  3. $ref = &$arr;
  4. foreach ($arr as $val) {
  5. var_dump($val);
  6. unset($arr[1]);
  7. }
版本 结果 说明
PHP5 int(0) int(2) 会将unset的数据跳过
PHP7 int(0) int(1) int(2) 对数组的改动不影响循环
  • [x] 按照引用进行循环的时候, 对数组的修改会影响循环
  1. <?php
  2. $arr = [0, 1, 2];
  3. $ref = &$arr;
  4. foreach ($arr as &$val) {
  5. var_dump($val);
  6. unset($arr[1]);
  7. }
版本 结果
PHP5 int(0) int(2)
PHP7 int(0) int(2)
  • [x] 对简单对象plain(non-Traversable)的循环

在简单对象的循环, 不管是按照值循环还是引用循环, 和按照引用对数组循环的行为是一样的, 不过对位置的管理会更加精确

  • [x] 对迭代对象(Traversable objects)对象行为和之前一致

stackoverflow 上面的解释, Traversable objects is one that implements Iterator or IteratorAggregate interface

如果一个对象实现了 Iterator 或者 IteratorAggregate 接口, 即可称之为迭代对象

参考

原文地址:https://segmentfault.com/a/1190000015753252

深入理解PHP之foreach的更多相关文章

  1. IL角度理解for 与foreach的区别——迭代器模式

    IL角度理解for 与foreach的区别--迭代器模式 目录 IL角度理解for 与foreach的区别--迭代器模式 1 最常用的设计模式 1.1 背景 1.2 摘要 2 遍历元素 3 删除元素 ...

  2. Lambda表达式和Java集合框架

    本文github地址 前言 我们先从最熟悉的Java集合框架(Java Collections Framework, JCF)开始说起. 为引入Lambda表达式,Java8新增了java.util. ...

  3. 初学Java Web(9)——学生管理系统(简易版)总结

    项目开始时间:2018年4月8日14:37:47 项目完成时间:2018年4月9日10:03:30 技术准备 这个项目是自己用于巩固 J2EE 相关知识的练手项目,非常简单,但是相关的功能却非常实用, ...

  4. 学生管理系统(SSM简易版)总结

    之前用 Servlet + JSP 实现了一个简易版的学生管理系统,在学习了 SSM 框架之后,我们来对之前写过的项目重构一下! 技术准备 为了完成这个项目,需要掌握如下技术: Java 基础知识 前 ...

  5. (js) 字符串和数组的常用方法

    JS中字符串和数组的常用方法 JS中字符串和数组的常用方法 js中字符串常用方法 查找字符串 根据索引值查找字符串的值 根据字符值查找索引值 截取字符串的方法 字符串替换 字符串的遍历查找 字符串转化 ...

  6. Array常用方法总结

    一.[常用语法] 1.1.数组的创建var arrayObj = new Array(); //创建一个数组 var arrayObj = new Array([size]); //创建一个数组并指定 ...

  7. Spring SSM 框架

    IDEA 整合 SSM 框架学习 http://www.cnblogs.com/wmyskxz/p/8916365.html 认识 Spring 框架 更多详情请点击这里:这里 Spring 框架是 ...

  8. Lambda表达式你会用吗?

    函数式编程 在正式学习Lambda之前,我们先来了解一下什么是函数式编程 我们先看看什么是函数.函数是一种最基本的任务,一个大型程序就是一个顶层函数调用若干底层函数,这些被调用的函数又可以调用其他函数 ...

  9. foreach的理解

    foreach的两种写法的解读 一.常见 1.理解:将数组元素逐个赋值给变量V,然后将v输出 2.代码: $arr = array(1,2,3,4,5); foreach($arr as $a){ e ...

随机推荐

  1. 【BZOJ 3732】 Network

    [题目链接] 点击打开链接 [算法] 求出这个图的最小生成树,对于每次询问,用倍增法求出最近公共祖先,查询最小生成树上两点路径上的最大值 算法的正确性?            假设x和y在最小生成树中 ...

  2. ASP.Net 下载大文件的实现 (转)

    原文:http://www.cnblogs.com/luisliu/p/4253815.html 当我们的网站需要支持下载大文件时,如果不做控制可能会导致用户在访问下载页面时发生无响应,使得浏览器崩溃 ...

  3. E20170531-hm

    passage  n.     通路; 通道 discrete   adj. 分离的,不相关联的; 分立式; 非连续; alternative   替代的; 另类的; 备选的; 其他的; intent ...

  4. P2597 [ZJOI2012]灾难(倍增LCA+拓扑排序)

    传送门 据大佬说这玩意儿好像叫灾难树还是灭绝树? 我们先按建图,设点$u$的食物有$x[1]...x[k]$,即在图中这些点都有一条指向它的边 以样例来说,对于人,羊和牛都有一条指向它的边,然而不管是 ...

  5. C#命名空间 using的用法

    using的用法: 1. using指令:引入命名空间 这是最常见的用法,例如: using System; using Namespace1.SubNameSpace; 2. using stati ...

  6. 递推DP Codeforces Round #260 (Div. 1) A. Boredom

    题目传送门 /* DP:从1到最大值,dp[i][1/0] 选或不选,递推更新最大值 */ #include <cstdio> #include <algorithm> #in ...

  7. java excel poi导入 过滤空行的方法 判断是否是空行

    private boolean isRowEmpty(Row row){ for (int c = row.getFirstCellNum(); c < row.getLastCellNum() ...

  8. duilib属性

    原文转载自:http://blog.csdn.net/lixiang987654321/article/details/45008441 这里我想讲解一下duilib中的一些属性的理解,当然这是一篇永 ...

  9. 使用_CRTDBG_LEAK_CHECK_DF检查VC程序的内存泄漏(转)

    我们知道,MFC程序如果检测到存在内存泄漏,退出程序的时候会在调试窗口提醒内存泄漏.例如: class CMyApp : public CWinApp{public:BOOL InitApplicat ...

  10. 往文件内写入内容(java)

    新建个工具类,并标记成静态的,方便调用. package util; import java.io.File;import java.io.FileOutputStream; public class ...