深入理解PHP之foreach
招聘
标签(空格分隔): 招聘 PHP 国贸
语言基础
foreach 语法结构提供了遍历数组的简单方式。
php5之前, foreach仅能用于数组
php5+, 利用foreach可以遍历对象
foreach仅能够应用于数据和对象,如果尝试应用于其他数据类型的变量,或者未初始化的变量将发出错误信息。
有两种语法:
/*
遍历给定的 array_expression 数据。每次循环中, 当前单元的值被赋给$value并且数组内部的指针向前移一步(因此下次循环中将会得到下一个单元)
*/
foreach (array_expression as $value) {
// statement
}
foreach (array_expression as $value) :
// statement
endforeach;
/*
同上,只除了当前单元格的键名也会在每次循环中被赋给变量$key
*/
foreach (array_expression as $key => $value) {
// statement
}
foreach (array_expression as $key => $value) :
// statement
endforeach;
还能够自定义遍历对象!
当
foreach
开始执行时, 数组内部的指针会自动指向第一个单元. 这意味着不需要在foreach
循环之前调用reset()
由于foreach
依赖内部数组指针, 在循环中修改其值将可能导致意外的行为
可以很容易通过在 $value 之前加上 & 来修改数组元素. 此方法将以引用
赋值, 而不是拷贝一个值.
<?php
$arr = [1, 2, 3, 4];
foreach($arr as &$value) {
$value = $value * 2;
}
// $arr is now [2, 4, 6, 8]
unset($value); // 最后取消掉引用
$value
的引用仅在被遍历的数组可以被引用时才可用(例如是个变量)。
以下代码无法运行:
<?php
/*
此段代码可以运行
运行结果:
1-2
2-4
3-6
4-8
*/
foreach (array(1, 2, 3, 4) as &$value) {
echo $value, '-';
$value = $value * 2;
echo $value, PHP_EOL;
}
Warning: 数组最后一个元素的
$value
引用在foreach
循环之后仍会保留。建议使用unset()
来将其销毁。Note:
foreach
不支持用@
来抑制错误信息的能力
foreach 虽然简单, 不过它可能出现一些意外行为, 特别是代码涉及到引用的时候。
问题研究
问题一: 如下代码运行结果为何不是 2/4/6 ?
<?php
$arr = [1, 2, 3];
foreach ($arr as $k => &$v) {
$v = $v * 2;
}
foreach ($arr as $k => $v) {
echo $v, PHP_EOL;
}
/*
输出:
2
4
4
*/
我们可以认为 foreach($arr as &$v)
结构隐含了如下操作, 分别将数组当前的 键
和 值
赋值给 $k
和 $v
. 具体展开形如:
<?php
foreach ($arr as $k => $v) {
$k = currentKey();
$v = currentVal();
// 继续运行用户代码
}
根据上述理论, 现在我们重新来分析下第一个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() 来将其销毁。
<?php
$arr = [1, 2, 3];
foreach ($arr as $k => &$v) {
$v = $v * 2;
}
unset($v);
foreach ($arr as $k => $v) {
echo $v, PHP_EOL;
}
/*
输出:
2
4
6
*/
从这个问题可以看出, 引用很可能会伴随副作用。如果不希望无意识的修改导致数据内容变更, 最好及时unset掉这些引用。
问题二: 如下代码运行结果为何不是 0=>a 1=>b 2=>c
<?php
$arr = ['a', 'b', 'c'];
foreach ($arr as $k => $v) {
echo key($arr), "=>", current($arr), PHP_EOL;
}
foreach ($arr as $k => &$v) {
echo key($arr), "=>", current($arr), PHP_EOL;
}
/*
#php5.6
1=>b 1=>b 1=>b
1=>b 2=>c =>
#php7
0=>a 0=>a 0=>a
0=>a 0=>a 0=>a
*/
按照手册中的说法, key和current分别是获取数据中当前元素的键值。
那为何 key($arr)
一直是0,current($arr)
一直是'a'呢?
先用vld查看编译后的 opcode
:
➜ demo /usr/local/Cellar/php/7.2.7/bin/php -dvld.active=1 a.php
Finding entry points
Branch analysis from position: 0
Jump found. (Code = 77) Position 1 = 2, Position 2 = 15
Branch analysis from position: 2
Jump found. (Code = 78) Position 1 = 3, Position 2 = 15
Branch analysis from position: 3
Jump found. (Code = 42) Position 1 = 2
Branch analysis from position: 2
Branch analysis from position: 15
Jump found. (Code = 62) Position 1 = -2
Branch analysis from position: 15
filename: /Users/jianyong/demo/a.php
function name: (null)
number of ops: 17
compiled vars: !0 = $arr, !1 = $v, !2 = $k
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
2 0 E > ASSIGN !0, <array>
4 1 > FE_RESET_R $4 !0, ->15
2 > > FE_FETCH_R ~5 $4, !1, ->15
3 > ASSIGN !2, ~5
5 4 INIT_FCALL 'key'
5 SEND_VAR !0
6 DO_ICALL $7
7 ECHO $7
8 ECHO '%3D%3E'
9 INIT_FCALL 'current'
10 SEND_VAR !0
11 DO_ICALL $8
12 ECHO $8
13 ECHO '%0A'
14 > JMP ->2
15 > FE_FREE $4
7 16 > RETURN 1
branch: # 0; line: 2- 4; sop: 0; eop: 1; out1: 2; out2: 15
branch: # 2; line: 4- 4; sop: 2; eop: 2; out1: 3; out2: 15
branch: # 3; line: 4- 5; sop: 3; eop: 14; out1: 2
branch: # 15; line: 5- 7; sop: 15; eop: 16; out1: -2
path #1: 0, 2, 3, 2, 15,
path #2: 0, 2, 15,
path #3: 0, 15,
0=>a
0=>a
0=>a
PHP7新特性之foreach
- [x]
foreach
循环对数组内部指针不再起作用, 在PHP7之前, 当数据通过foreach迭代时, 数组指针会移动。
<?php
$array = [0, 1, 2];
foreach ($array as &$val) {
var_dump(current($array));
}
版本 | 结果 | 说明 |
---|---|---|
PHP5 | int(1) int(2) bool(false) | 数组指针会移动 |
PHP7 | int(0) int(0) int(0) | 数据指针不再移动 |
- [x] 按照值进行循环时, 对数组的修改是不会影响循环。
foreach
按照值进行循环的时候(by-value), foreach是对该数组的一个拷贝进行操作. 所以在循环过程中修改不影响循环结果
<?php
$arr = [0, 1, 2];
$ref = &$arr;
foreach ($arr as $val) {
var_dump($val);
unset($arr[1]);
}
版本 | 结果 | 说明 |
---|---|---|
PHP5 | int(0) int(2) | 会将unset的数据跳过 |
PHP7 | int(0) int(1) int(2) | 对数组的改动不影响循环 |
- [x] 按照引用进行循环的时候, 对数组的修改会影响循环
<?php
$arr = [0, 1, 2];
$ref = &$arr;
foreach ($arr as &$val) {
var_dump($val);
unset($arr[1]);
}
版本 | 结果 |
---|---|
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的更多相关文章
- IL角度理解for 与foreach的区别——迭代器模式
IL角度理解for 与foreach的区别--迭代器模式 目录 IL角度理解for 与foreach的区别--迭代器模式 1 最常用的设计模式 1.1 背景 1.2 摘要 2 遍历元素 3 删除元素 ...
- Lambda表达式和Java集合框架
本文github地址 前言 我们先从最熟悉的Java集合框架(Java Collections Framework, JCF)开始说起. 为引入Lambda表达式,Java8新增了java.util. ...
- 初学Java Web(9)——学生管理系统(简易版)总结
项目开始时间:2018年4月8日14:37:47 项目完成时间:2018年4月9日10:03:30 技术准备 这个项目是自己用于巩固 J2EE 相关知识的练手项目,非常简单,但是相关的功能却非常实用, ...
- 学生管理系统(SSM简易版)总结
之前用 Servlet + JSP 实现了一个简易版的学生管理系统,在学习了 SSM 框架之后,我们来对之前写过的项目重构一下! 技术准备 为了完成这个项目,需要掌握如下技术: Java 基础知识 前 ...
- (js) 字符串和数组的常用方法
JS中字符串和数组的常用方法 JS中字符串和数组的常用方法 js中字符串常用方法 查找字符串 根据索引值查找字符串的值 根据字符值查找索引值 截取字符串的方法 字符串替换 字符串的遍历查找 字符串转化 ...
- Array常用方法总结
一.[常用语法] 1.1.数组的创建var arrayObj = new Array(); //创建一个数组 var arrayObj = new Array([size]); //创建一个数组并指定 ...
- Spring SSM 框架
IDEA 整合 SSM 框架学习 http://www.cnblogs.com/wmyskxz/p/8916365.html 认识 Spring 框架 更多详情请点击这里:这里 Spring 框架是 ...
- Lambda表达式你会用吗?
函数式编程 在正式学习Lambda之前,我们先来了解一下什么是函数式编程 我们先看看什么是函数.函数是一种最基本的任务,一个大型程序就是一个顶层函数调用若干底层函数,这些被调用的函数又可以调用其他函数 ...
- foreach的理解
foreach的两种写法的解读 一.常见 1.理解:将数组元素逐个赋值给变量V,然后将v输出 2.代码: $arr = array(1,2,3,4,5); foreach($arr as $a){ e ...
随机推荐
- 利用JFreeChart生成组合图表 (8) (转自 JSP开发技术大全)
利用JFreeChart生成组合图表 (8) (转自 JSP开发技术大全) 14.8 利用JFreeChart生成组合图表 实例位置:光盘\mingrisoft\14\dxyy\02 通过JFree ...
- Minimizing Maximizer
题意: 最少需要多少个区间能完全覆盖整个区间[1,n] 分析: dp[i]表示覆盖[1,i]最少需要的区间数,对于区间[a,b],dp[b]=min(dp[a...b-1])+1;用线段树来维护区间最 ...
- Groonga开源搜索引擎——列存储做聚合,没有内建分布式,分片和副本是随mysql或者postgreSQL作为存储引擎由MySQL自身来做分片和副本的
1. Characteristics of Groonga ppt:http://mroonga.org/publication/presentation/groonga-mysqluc2011.pd ...
- [Codeforces 449B] Jzzhu and Cities
[题目链接] https://codeforces.com/contest/449/problem/B [算法] 最短路 时间复杂度 : O(N ^ 2) [代码] #include<bits/ ...
- robotframework - Run标签
1.下面是Run标签的截图 2.Run 标签上的按钮和输入框的作用: 1) Execution Profile:选择运行方式,里面有 pybot.jybot 和 custom script.其中我们默 ...
- LOGO免费在线设计
http://www.logomaker.com.cn/ 藏经阁技术资料分享群二维码
- 洛谷P3366 【模板】最小生成树(Kruskal)
题目描述 如题,给出一个无向图,求出最小生成树,如果该图不连通,则输出orz 输入输出格式 输入格式: 第一行包含两个整数N.M,表示该图共有N个结点和M条无向边.(N<=5000,M<= ...
- WKWebView 和 UIWebView 允许背景音乐自动播放(记录)
WKWebView WKWebViewConfiguration *config = [[WKWebViewConfiguration alloc] init]; config.allowsInlin ...
- 二分+RMQ/双端队列/尺取法 HDOJ 5289 Assignment
题目传送门 /* 题意:问有几个区间最大值-最小值 < k 解法1:枚举左端点,二分右端点,用RMQ(或树状数组)求区间最值,O(nlog(n))复杂度 解法2:用单调队列维护最值,O(n)复杂 ...
- jquery实现文字自动向上滚动,鼠标放上去停止,移开继续滚动代码...
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...