今天的操作中出现了如下的问题,下面试着解释其中原因:
先看看代码:
复制代码
上面的代码的结果为:
'aa', 'bb', 'cc'
'bb'
上面的第一行结果没有问题,主要是考虑下面的结果,为什么会是'bb'呢?
下面就开始解释这个问题:
影响这个结果的应该有以下的原因:
1,foreach所操作的是指定数组的一个拷贝
2,PHP所采取的是写时复制(copy on write, COW)
3,数组在复制时数组指针的处理规则
1,什么叫 foreach所操作的是指定数组的一个拷贝?
这句话的意思是指,foreach结构所指定的数组$arr,并不是我们在语句上看到的$arr, 内部处理时 是 处理的$arr 的一个拷贝,也就是 = $arr;
复制代码
once once once array(6){'a','b', 'c', 'add', 'add', 'add'}
这个例子的结果是输出三个once 和 一个含义6个元素的数组。
说明我虽然对$arr增加了元素但是并没有在遍历时遍历到新增的元素,遍历时不是使用$arr这个元素,而是使用的$arr的一个拷贝-你看不到,我也看不到。
2什么是写时复制呢?
就是通过赋值的方式赋值给变量时,不会申请新的内存来存放新变量所保存的值,而是简单地通过一个计数器来公用内存,只有其中一个引用指向的变量的值发生变化时,才申请内存空间来保存新值,以达到节省内存空间的目的。例如:
$foo = 1;
$bar = $foo;
echo $bar + $foo; //2
上面的代码就不会开辟2个变量空间来保存相同的1 而是使用的是1个值空间。
如果接下来再执行:
$bar = 2;
此时$foo 与$bar的值发生了变化,需要为$bar开辟新的空间来保存这个值。
这个特性就是写时复制——在写数据的时候复制值空间。
再看例子:
复制代码
从上面的例子可以看到 复制一个变量$bar后 内存的分配并没有像分配$ffoo时增加那么多的空间,而是在对$bar重新赋值时 才分配的额外的空间
以上特性就是 php为了优化内存而采用的 COW策略,很多语言中都有类似的优化,例如C++的STL中等等
注意一点,如果在foreach时候使用的是引用传值,则不会出现拷贝,因而不会出现这个状况。
3,在数组之间复制是 数组指针是怎样的规律
例如$copy_arr = $arr;中
如果$arr的数组指针处于$arr数组内部(指向任意的一个元素),那么在赋值时,$arr的指针位置不变,而$copy_arr的指针与$arr的指针位置相同
如果$arr的数组指针处于数组末端(最后一个元素后,超出数组),那么在赋值时,$arr的指针会被初始化(指向第一个元素),而$copy_arr的指针位置还是处于数组的末端。
例如:
复制代码
看以上的输出 体会指针赋值时的规律
好了 解释了 以上三个知识点后,下面开始试着解释最开始的问题了。
复制代码
原因如下:
当执行foreach这行语句时,应该使用$arr的副本来进行遍历(假设副本为$copy_arr),但由于是写时复制,直到第一次写操作时,$arr与$copy_arr才指向不同的值。当第一次执行到foreach 为$value 和$key 变量赋值数据时,没有发生写操作,因此不会产生新的值空间,此时就是操作的$arr数组,为$value和$key分配完毕值之后,会将数组指针向后移动一个单元,$arr的指针向后移动一个单元,指向第二个元素单元。接着马上执行了对$arr[$key]重新复制的操作,发生了写操作,写时复制,发生了复制。复制操作后$copy_arr与$arr是两个空间,因此遍历时使用$copy_arr 会移动$copy_arr 而不再会继续移动$arr。 $arr指针固定在第二个元素上。 所以输出current($arr)是获得的是第二个元素的值。
以下的例子加以说明:
复制代码
再看:
复制代码
以上两个例子 说明 只要没有写操作,那么foreach使用的就是$arr,如果发生写操作将使用$copy_arr = $arr
最后一个例子:
复制代码
注意第三个例子的注释部分,根据数组指针复制时的规律,输出的结果为第一个元素a
先看看代码:
-
$arr = array('a', 'b', 'c');
-
foreach($arr as $key=>$item) {
-
$arr[$key] = $item . $item;//修改元素的值,只要是写操作即可,无论什么
-
}
-
var_dump($arr);
- var_dump(current($arr));
'aa', 'bb', 'cc'
'bb'
上面的第一行结果没有问题,主要是考虑下面的结果,为什么会是'bb'呢?
下面就开始解释这个问题:
影响这个结果的应该有以下的原因:
1,foreach所操作的是指定数组的一个拷贝
2,PHP所采取的是写时复制(copy on write, COW)
3,数组在复制时数组指针的处理规则
1,什么叫 foreach所操作的是指定数组的一个拷贝?
这句话的意思是指,foreach结构所指定的数组$arr,并不是我们在语句上看到的$arr, 内部处理时 是 处理的$arr 的一个拷贝,也就是 = $arr;
-
$arr = array('a', 'b', 'c');
-
foreach($arr as $value) 此处用来获得$value的,并不是语法上$arr 而是看不到的$arr的一个拷贝。
-
来个简单证明:
-
foreach($arr as $item) {
-
$arr[] = 'add';//每次循环增加一个$arr的元素
-
echo 'once', ' ';//输出一个单词和空格
-
}
- var_dump($arr);
这个例子的结果是输出三个once 和 一个含义6个元素的数组。
说明我虽然对$arr增加了元素但是并没有在遍历时遍历到新增的元素,遍历时不是使用$arr这个元素,而是使用的$arr的一个拷贝-你看不到,我也看不到。
2什么是写时复制呢?
就是通过赋值的方式赋值给变量时,不会申请新的内存来存放新变量所保存的值,而是简单地通过一个计数器来公用内存,只有其中一个引用指向的变量的值发生变化时,才申请内存空间来保存新值,以达到节省内存空间的目的。例如:
$foo = 1;
$bar = $foo;
echo $bar + $foo; //2
上面的代码就不会开辟2个变量空间来保存相同的1 而是使用的是1个值空间。
如果接下来再执行:
$bar = 2;
此时$foo 与$bar的值发生了变化,需要为$bar开辟新的空间来保存这个值。
这个特性就是写时复制——在写数据的时候复制值空间。
再看例子:
-
//未使用变量的内存
-
var_dump(memory_get_usage());//327768
-
$foo = str_repeat('itcast-php', 10000);
-
//使用变量$foo之后的内存
-
var_dump(memory_get_usage());//424864
-
$bar = $foo;
-
//将变量$foo 复制给$bar后的内存
-
var_dump(memory_get_usage());//427912
-
$bar = str_repeat('itcast-kang', 10000);
-
//对$bar值修改后使用的内存
- var_dump(memory_get_usage());//537960
以上特性就是 php为了优化内存而采用的 COW策略,很多语言中都有类似的优化,例如C++的STL中等等
注意一点,如果在foreach时候使用的是引用传值,则不会出现拷贝,因而不会出现这个状况。
3,在数组之间复制是 数组指针是怎样的规律
例如$copy_arr = $arr;中
如果$arr的数组指针处于$arr数组内部(指向任意的一个元素),那么在赋值时,$arr的指针位置不变,而$copy_arr的指针与$arr的指针位置相同
如果$arr的数组指针处于数组末端(最后一个元素后,超出数组),那么在赋值时,$arr的指针会被初始化(指向第一个元素),而$copy_arr的指针位置还是处于数组的末端。
例如:
-
$arr = array('a', 'b', 'c');
-
-
next($arr);//移动一次指向第二个元素
-
$copy_arr1 = $arr;
-
var_dump(current($arr), current($copy_arr1));//b, b
-
echo '
-
';
-
next($arr);//再次移动 指向第三个元素
-
$copy_arr2 = $arr;
-
var_dump(current($arr), current($copy_arr2));//c c
-
echo '
-
';
-
next($arr);//再次移动 出去了
-
$copy_arr3 = $arr;
- var_dump(current($arr), current($copy_arr3));//a false
好了 解释了 以上三个知识点后,下面开始试着解释最开始的问题了。
-
$arr = array('a', 'b', 'c');
-
foreach($arr as $key => $value) {
-
$arr[$key] = $value . $value;
-
}
- var_dump(current($arr));//'bb'
当执行foreach这行语句时,应该使用$arr的副本来进行遍历(假设副本为$copy_arr),但由于是写时复制,直到第一次写操作时,$arr与$copy_arr才指向不同的值。当第一次执行到foreach 为$value 和$key 变量赋值数据时,没有发生写操作,因此不会产生新的值空间,此时就是操作的$arr数组,为$value和$key分配完毕值之后,会将数组指针向后移动一个单元,$arr的指针向后移动一个单元,指向第二个元素单元。接着马上执行了对$arr[$key]重新复制的操作,发生了写操作,写时复制,发生了复制。复制操作后$copy_arr与$arr是两个空间,因此遍历时使用$copy_arr 会移动$copy_arr 而不再会继续移动$arr。 $arr指针固定在第二个元素上。 所以输出current($arr)是获得的是第二个元素的值。
以下的例子加以说明:
-
$arr = array('a', 'b', 'c');
-
$i = 0;
-
foreach($arr as $key => $value) {
-
if($i == 0) {
-
//第一次就执行了写操作
-
$arr[$key] = $value . $value;
-
}
-
$i++;
-
}
-
//$arr的指针被移动了一次,指向第二个元素b上。
- var_dump(current($arr));//b
-
$arr = array('a', 'b', 'c');
-
$i = 0;
-
foreach($arr as $key => $value) {
-
if($i == 1) {
-
//第二次执行了写操作
-
$arr[$key] = $value . $value;
-
}
-
$i++;
-
}
-
//$arr的指针被移动了二次,指向第三个元素c上。
- var_dump(current($arr));//c
最后一个例子:
-
$arr = array('a', 'b', 'c');
-
$i = 0;
-
foreach($arr as $key => $value) {
-
if($i == 3) {
-
//第三次执行了写操作
-
$arr[$key] = $value . $value;
-
}
-
$i++;
-
}
-
//$arr的指针被移动了三次,移到的数组外部。
-
//但是在$copy_arr = $arr时,按照数组指针复制规律,
-
//由于$arr指针在数组末端,则会重置指针指向第一个元素 a
- var_dump(current($arr));//a
38

被折叠的 条评论
为什么被折叠?



