php的foreach深度剖析

2021-07-10 04:01:50 字數 4029 閱讀 3860

今天的操作中出現了如下的問題,下面試著解釋其中原因:

先看看**:

$arr = array('a', 'b', 'c');

foreach($arr as $key=>$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)

var_dump($arr);

複製**

once once once array(6)

這個例子的結果是輸出三個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

複製**

從上面的例子可以看到 複製乙個變數$bar後 記憶體的分配並沒有像分配$ffoo時增加那麼多的空間,而是在對$bar重新賦值時 才分配的額外的空間

以上特性就是 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)

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)

$i++;

}//$arr的指標被移動了一次,指向第二個元素b上。

var_dump(current($arr));//b

複製**

再看:$arr = array('a', 'b', 'c');

$i = 0;

foreach($arr as $key => $value)

$i++;

}//$arr的指標被移動了二次,指向第三個元素c上。

var_dump(current($arr));//c

複製**

以上兩個例子 說明 只要沒有寫操作,那麼foreach使用的就是$arr,如果發生寫操作將使用$copy_arr = $arr

最後乙個例子:

$arr = array('a', 'b', 'c');

$i = 0;

foreach($arr as $key => $value)

$i++;

}//$arr的指標被移動了三次,移到的陣列外部。

//但是在$copy_arr = $arr時,按照陣列指標複製規律,

//由於$arr指標在陣列末端,則會重置指標指向第乙個元素 a

var_dump(current($arr));//a

複製**

注意第三個例子的注釋部分,根據陣列指標複製時的規律,輸出的結果為第乙個元素a

forEach深度理解

摘自ecma 262標準 15.4.4.18 陣列原型的foreach 22.1.3.10 陣列原型的foreach 23.1.3.12 陣列原型的foreach 以下內容是我將英文翻譯為中文並用自己的理解解釋 步驟部分為原版翻譯 僅供參考 foreach方法接收兩個引數,第乙個引數是最多接收三個引...

define 的深度剖析

1.預處理識別符號的使用 include define file 進行編譯的原始檔 define line 檔案當前的行號 define date 檔案被編譯的日期 define time 檔案被編譯的時間 int main 執行結果 這樣可以輸出當前編譯檔案的相關資訊,使用非常方便。2.了解巨集和...

指標的深度剖析

在c語言中,指標 pointer 是程式語言中的乙個物件,利用位址,它的值直接指向存在電腦儲存器中另乙個地方的值。由於通過位址能找到所需的變數單元,可以說,位址指向該變數單元。因此,將位址形象化的稱為 指標 意思是通過它能找到以它為位址的記憶體單元。指標變數和指標 將乙個變數的位址稱為變數的指標,存...