PHP Foreach Pass by Reference: Last Element trùng lặp? (Bọ cánh cứng?)


159

Tôi chỉ có một số hành vi rất lạ với một tập lệnh php đơn giản mà tôi đang viết. Tôi đã giảm nó đến mức tối thiểu cần thiết để tạo lại lỗi:

<?php

$arr = array("foo",
             "bar",
             "baz");

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

?>

Kết quả này:

Array
(
    [0] => foo
    [1] => bar
    [2] => baz
)
Array
(
    [0] => foo
    [1] => bar
    [2] => bar
)

Đây có phải là một lỗi hoặc một số hành vi thực sự kỳ lạ được cho là xảy ra?


Làm điều đó theo giá trị một lần nữa, xem nếu thay đổi lần thứ 3 ...?
Shackrock

1
@Shackrock, nó dường như không thay đổi nữa với các vòng lặp lặp theo giá trị.
vương giả

1
Thật thú vị nếu bạn thay đổi vòng lặp thứ hai để sử dụng một thứ khác ngoài $ item thì nó hoạt động như mong đợi.
Steve Claridge

9
luôn luôn bỏ đặt mục ở phần cuối của vòng lặp: foreach($x AS &$y){ ... unset($y); }- thực sự là trên php.net (không biết ở đâu) vì đó là một lỗi rất nhiều.
Rudie

Câu trả lời:


170

Sau vòng lặp foreach đầu tiên, $itemvẫn là một tham chiếu đến một số giá trị cũng đang được sử dụng $arr[2]. Vì vậy, mỗi foreach gọi trong vòng lặp thứ hai, không gọi bằng tham chiếu, thay thế giá trị đó, và do đó $arr[2], bằng giá trị mới.

Vì vậy, vòng 1, giá trị và $arr[2]trở thành $arr[0], đó là 'foo'.
Vòng 2, giá trị và $arr[2]trở thành $arr[1], đó là 'thanh'.
Vòng 3, giá trị và $arr[2]trở thành $arr[2], đó là 'thanh' (vì vòng 2).

Giá trị 'baz' thực sự bị mất ở lần gọi đầu tiên của vòng lặp foreach thứ hai.

Gỡ lỗi đầu ra

Đối với mỗi lần lặp của vòng lặp, chúng ta sẽ lặp lại giá trị $itemcũng như in đệ quy mảng $arr.

Khi vòng lặp đầu tiên được chạy qua, chúng ta sẽ thấy đầu ra này:

foo
Array ( [0] => foo [1] => bar [2] => baz )

bar
Array ( [0] => foo [1] => bar [2] => baz )

baz
Array ( [0] => foo [1] => bar [2] => baz )

Ở cuối vòng lặp, $itemvẫn chỉ đến cùng một nơi với $arr[2].

Khi vòng lặp thứ hai chạy qua, chúng ta sẽ thấy đầu ra này:

foo
Array ( [0] => foo [1] => bar [2] => foo )

bar
Array ( [0] => foo [1] => bar [2] => bar )

bar
Array ( [0] => foo [1] => bar [2] => bar )

Bạn sẽ nhận thấy mỗi mảng thời gian đưa một giá trị mới vào $item, nó cũng được cập nhật $arr[3]với cùng một giá trị đó, vì cả hai vẫn đang trỏ đến cùng một vị trí. Khi vòng lặp đến giá trị thứ ba của mảng, nó sẽ chứa giá trị barvì nó chỉ được đặt bởi lần lặp trước của vòng lặp đó.

Có phải là một lỗi?

Không. Đây là hành vi của một mục được tham chiếu và không phải là lỗi. Nó sẽ tương tự như chạy một cái gì đó như:

for ($i = 0; $i < count($arr); $i++) { $item = $arr[$i]; }

Một vòng lặp foreach không đặc biệt về bản chất trong đó nó có thể bỏ qua các mục được tham chiếu. Nó chỉ đơn giản là đặt biến đó thành giá trị mới mỗi lần như bạn sẽ ở ngoài vòng lặp.


4
Tôi có một sự điều chỉnh pedantic nhẹ. $itemkhông phải là một tham chiếu đến $arr[2], giá trị được chứa bởi $arr[2]là một tham chiếu đến giá trị được đề cập bởi $item. Để minh họa cho sự khác biệt, bạn cũng có thể bỏ đặt $arr[2], và $itemsẽ không bị ảnh hưởng, và viết để $itemkhông ảnh hưởng đến nó.
Paul Biggar

2
Hành vi này là phức tạp để hiểu và có thể dẫn đến các vấn đề. Tôi giữ điều này như một trong những mục yêu thích của tôi để cho học sinh của mình thấy tại sao họ nên tránh (miễn là họ có thể) những điều "bằng cách tham khảo".
Olivier Pons

1
Tại sao $itemkhông đi ra khỏi phạm vi khi vòng lặp foreach được thoát? Đây có vẻ như là một vấn đề đóng cửa?
vui vẻ

6
@jocull: IN PHP, foreach, for, while, v.v. không tạo phạm vi riêng của họ.
phim hoạt hình

1
@jocull, PHP không có (chặn) các biến cục bộ. Một trong những lý do nó làm tôi khó chịu.
Qtax

29

$itemlà một tài liệu tham khảo $arr[2]và đang bị ghi đè bởi vòng lặp foreach thứ hai như hoạt hình đã chỉ ra.

foreach ($arr as &$item) { /* do nothing by reference */ }
print_r($arr);

unset($item); // This will fix the issue.

foreach ($arr as $item) { /* do nothing by value */ }
print_r($arr); // $arr has changed....why?

3

Trong khi điều này có thể không chính thức là một lỗi, theo ý kiến ​​của tôi nó là. Tôi nghĩ rằng vấn đề ở đây là chúng ta có kỳ vọng $itemsẽ vượt ra khỏi phạm vi khi vòng lặp được thoát như trong nhiều ngôn ngữ lập trình khác. Tuy nhiên, đó dường như không phải là trường hợp ...

Mã này ...

$arr = array('one', 'two', 'three');
foreach($arr as $item){
    echo "$item\n";
}    
echo $item;

Cung cấp đầu ra ...

one
two
three
three

Như những người khác đã nói, bạn ghi đè lên biến được tham chiếu trong $arr[2]vòng lặp thứ hai của mình, nhưng nó chỉ xảy ra vì $itemkhông bao giờ vượt quá phạm vi. Các bạn nghĩ sao ... lỗi?


4
1) Không phải là một lỗi. Nó đã được gọi ra trong hướng dẫn và bị loại bỏ trong một số báo cáo lỗi như dự định. 2) Không thực sự trả lời câu hỏi ...
BoltClock

Nó khiến tôi phát hiện ra không phải vì vấn đề phạm vi, tôi dự kiến ​​$ item vẫn tồn tại sau lần đầu tiên, nhưng tôi không nhận ra rằng CẬP NHẬT biến đó thay vì thay thế nó. ví dụ như chạy unset ($ item) trước vòng lặp thứ hai. Lưu ý rằng unset không xóa giá trị (và do đó phần tử cuối cùng trong mảng) nó chỉ đơn giản là loại bỏ biến.
Chương trình

Thật không may, PHP không tạo ra một phạm vi mới cho các vòng lặp hoặc {}các khối nói chung. Đây là cách ngôn ngữ hoạt động
Fabian Schmengler


0

Hành vi đúng của PHP có thể là một lỗi THÔNG BÁO trong ý kiến ​​của tôi. Nếu một biến tham chiếu được tạo trong vòng lặp foreach được sử dụng bên ngoài vòng lặp thì nó sẽ gây ra thông báo. Rất dễ rơi vào hành vi này, rất khó để phát hiện ra nó khi nó xảy ra. Và không có nhà phát triển nào sẽ đọc trang tài liệu foreach, nó không phải là một trợ giúp.

Bạn nên unset()tham khảo sau vòng lặp của mình để tránh loại vấn đề này. unset () trên một tham chiếu sẽ chỉ xóa tham chiếu mà không làm hại dữ liệu gốc.


0

đó là bởi vì bạn sử dụng bởi chỉ thị ref (&). giá trị cuối cùng sẽ được thay thế bởi vòng lặp thứ hai và nó làm hỏng mảng của bạn. giải pháp đơn giản nhất là sử dụng tên khác cho vòng lặp thứ hai:

foreach ($arr as &$item) { ... }

foreach ($arr as $anotherItem) { ... }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.