Hầu như mọi ngôn ngữ lập trình đều hỗ trợ các vòng lặp foreach và PHP cũng không ngoại lệ. Mình cá là bạn cũng đã sử dụng nó rất nhiều lần.
Về cơ bản, không có gì đặc biệt về vòng lặp foreach trong PHP, nhưng mình nghĩ có một vài sự thật mà bạn nên biết.
Bởi vì các cấu trúc điều khiển vòng lặp Foreach được sử dụng mọi lúc, do đó cần tránh mắc lỗi khi sử dụng chúng.
6 Sự thật về vòng lặp Foreach trong PHP
Trong bài viết hôm nay, mình sẽ chỉ cho bạn 6 Sự thật mà mọi Lập trình viên PHP nên biết về các vòng lặp foreach.
1. Tự động ép kiểu int của key
PHP có một số tính năng thân thiện với người dùng sẽ giúp người mới bắt đầu học lập trình và cả lập trình viên ít kinh nghiệp viết mã dễ dàng hơn.
Một trong số đó là kiểu "Tung hứng":
-
Trình thông dịch PHP tự động thiết lập các kiểu biến đổi tùy thuộc vào ngữ cảnh và dựa trên toán tử nào được sử dụng kèm theo.
Điều này không có nghĩa là PHP không có các kiểu dữ liệu. Trên thực tế, mỗi biến có kiểu dữ liệu riêng, nhưng nó có thể thay đổi theo thời gian.
$a = 1 làm cho kiểu của $a là số nguyên, và $a = "1" làm cho kiểu của $a là chuỗi.
Trong cả hai trường hợp, ngay cả khi kiểu dữ liệu khác nhau, một câu lệnh như:
... lại làm cho $a trở thành một số nguyên.
Mặc dù việc tung hứng kiểu dữ liệu có thể thuận tiện trong nhiều trường hợp, nhưng điều quan trọng là luôn luôn cần xem xét loại biến chúng ta đang xử lý, đặc biệt là khi sử dụng các toán tử so sánh nghiêm ngặt (như "===").
Các toán tử so sánh nghiêm ngặt rất hữu ích vì chúng có thể phân biệt giữa các giá trị như 0, FALSE hoặc NULL, trong khi các toán tử so sánh lỏng lẻo (như là "==") coi giá trị của chúng tương đương nhau.
Hãy để ý một điểm.
Bên trong các vòng lặp foreach bạn có thể truy cập mọi cặp key / value của mảng bạn đang lặp và đôi khi bạn có thể cần so sánh key với một số giá trị khác.
Ở đây, điều này có nghĩa là: Khi và chỉ khi, key là một chữ số, thì nó sẽ tự động được chuyển đổi thành một số nguyên ở đầu vòng lặp.
Do đó, việc so sánh chặt chẽ nó với một chuỗi sẽ luôn trả về FALSE.
Ví dụ sau đây cho bạn thấy rõ vấn đề:
/* Chúng ta định nghĩa một mảng 2 phần tử. Lưu ý rằng key thứ 2 là chuỗi chữ số */
$array = array(
'first' => 'first element',
'2' => 'second element'
);
/* Đây là những gì xảy ra */
foreach ($array as $key => $value)
{
echo 'Key is: ' . $key . '<br>';
echo 'Key type is: ' . gettype($key) . '<br>';
/* Hãy thử so sánh */
if ($key === '2')
{
echo 'Key is "2"!<br>';
}
else
{
echo 'Key is NOT "2"!<br>';
}
}
Kết quả chúng ta nhận được:
Key is: first
Key type is: string
Key is NOT “2”!
Key is: 2
Key type is: integer
Key is NOT “2”!
Như bạn có thể thấy trong kết quả nhận được, trong khi key thứ hai thực sự là một chuỗi ('2'), nó được tự động chuyển thành một số nguyên (integer) bên trong vòng lặp vì nó là một chữ số.
Nếu chúng ta cố gắng so sánh chặt chẽ với '2', thì biểu thức trả về false.
Một giải pháp cho vấn đề này là explicity cast key thành chuỗi ở đầu mỗi vòng lặp (trên dòng 13):
/* Chúng ta định nghĩa một mảng 2 phần tử. Lưu ý rằng key thứ 2 là chuỗi chữ số. */
$array = array(
'first' => 'first element',
'2' => 'second element'
);
/* Đây là những gì xảy ra */
foreach ($array as $key => $value)
{
$key = strval($key);
echo 'Key is: ' . $key . '<br>';
echo 'Key type is: ' . gettype($key) . '<br>';
/* Hãy thử so sánh */
if ($key === '2')
{
echo 'Key is "2"!<br>';
}
else
{
echo 'Key is NOT "2"!<br>';
}
}
Kết quả nhận được:
Key is: first
Key type is: string
Key is NOT “2”!
Key is: 2
Key type is: string
Key is “2”!
Bây giờ, sự so sánh nghiêm ngặt giữa key thứ hai và '2' trả về true, đúng như chúng ta mong đợi.
2. Sửa đổi một Mảng từ bên trong Vòng lặp
Nếu bạn đang sử dụng foreach để lặp đi lặp lại một mảng và bạn muốn sửa đổi chính mảng đó, thì có hai cách để làm điều đó.
Đầu tiên là trỏ đến phần tử mảng mà bạn muốn chỉnh sửa bằng cú pháp $Array[$key] (vì vậy bạn phải sử dụng cú pháp foreach $key => $ value trong phần đầu foreach).
Một cách khác là định nghĩa $value làm tham chiếu và chỉnh sửa trực tiếp.
Hai ví dụ sau đây cho thấy cách thực hiện:
Ví dụ 1:
$array = array(1, 2);
foreach ($array as $key => $value)
{
$array[$key] += 1;
}
print_r($array);
Ví dụ 2:
<?php
$array = array(1, 2);
foreach ($array as $key => &$value)
{
$value += 1;
}
print_r($array);
Trong cả hai trường hợp, đầu ra sẽ là:
Array
(
[0] => 2
[1] => 3
)
3. Ghi đè biến trong Foreach
Bạn phải nhận thức được rằng các vòng lặp foreach không có phạm vi riêng (scope).
Điều này có nghĩa là các biến được khai báo bên ngoài vòng lặp cũng có sẵn bên trong vòng lặp và bất kỳ biến nào được khai báo bên trong vòng lặp sẽ tiếp tục có thể truy cập được ngay cả sau khi vòng lặp kết thúc.
Điều này cũng áp dụng cho các biến $key => $value được sử dụng trong phần đầu foreach.
Ví dụ này cho bạn thấy những gì có thể xảy ra:
$array = array(1, 2);
$value = 'my value';
foreach ($array as $key => $value)
{
// do something
}
echo $value; /* Output is "2" */
Sau vòng lặp, $value không còn chứa "my value" nữa nhưng thay vào đó là giá trị 2, vì nó đã bị ghi đè bởi phép gán foreach.
Trong khi mình đang tìm kiếm thêm thông tin chi tiết về vấn đề này, mình đã tìm thấy bài đăng thú vị này nêu bật một mối nguy hiểm tiềm tàng khác.
Hãy để xem một ví dụ này trước:
$array = array(1, 2, 3);
foreach ($array as $key => &$value)
{
// do something
}
/* Everything ok here */
print_r($array);
foreach ($array as $key => $value)
{
// do something
}
/* Something wrong! */
print_r($array);
Kết quả:
Array
(
[0] => 1
[1] => 2
[2] => 3
)
Array
(
[0] => 1
[1] => 2
[2] => 2
)
Sau vòng lặp foreach thứ hai, phần tử cuối cùng bên trong mảng đã thay đổi. Chuyện gì đã xảy ra ở đây?
Trong phần giới thiệu đầu tiên, chúng ta sử dụng cú pháp &$value, vì vậy $value được đặt làm tham chiếu cho từng phần tử mảng. Trong vòng lặp cuối cùng, $value do đó tương đương với $array[2].
Sau khi foreach kết thúc, $value tiếp tục có thể truy cập được (vì foreach không có phạm vi riêng).
Khi foreach thứ hai bắt đầu, $value vẫn bị ràng buộc với phần tử cuối cùng của mảng, do đó, trong mỗi vòng lặp, cả $value và $Array[2] đều bị ghi đè.
Đây là chi tiết những gì xảy ra:
-
Foreach đầu tiên, $value trỏ đến $array[2].
-
Foreach thứ hai, lần lặp đầu tiên: $array[2] được đặt với phần tử mảng đầu tiên (vì vậy, $array[2] = 1).
-
Foreach thứ hai, lần lặp thứ hai: $array[2] được đặt với phần tử mảng thứ hai (vì vậy, $arary[2] = 2).
-
Foreach thứ hai, lần lặp thứ ba: $array[2] được đặt với phần tử mảng thứ ba (tức là chính nó, vì vậy $array[2] = 2).
Điều đó giải thích kết quả chúng ta nhận được như trên.
Để tránh vấn đề này, luôn luôn là một ý tưởng tốt để unset bất kỳ biến nào được sử dụng trong các vòng lặp foreach, đặc biệt là khi sử dụng tham chiếu.
4. Có cần sử dụng RESET()?
Trước PHP 7, nên sử dụng reset() (trước PHP 5, sử dụng trước khi bắt đầu vòng lặp).
Đối với PHP 7 thì vấn đề này không còn nữa, vì foreach hiện sử dụng con trỏ nội bộ. Điều này tiết kiệm cho chúng ta một vài dòng mã.
Ví dụ sau đây cho thấy rằng sau vòng lặp foreach, con trỏ bên trong mảng con trỏ vẫn trỏ đến phần tử đầu tiên.
$array = array(1, 2, 3);
foreach ($array as $item)
{
echo 'Item: ' . $item . '<br>';
}
/* Current item is still 1 */
echo 'Current item: ' . current($array); /* Outputs 1 */
5. So sánh hiệu suất của vòng lặp Foreach với vòng lặp For và vòng lặp While
So sánh hiệu suất giữa vòng lặp foreach, for và while
Khi bạn cần lặp qua một mảng, bạn cũng có thể sử dụng vòng lặp while hoặc vòng lặp for thay vì sử dụng foreach.
-
Câu hỏi ở đây là: Vòng lặp nào mang lại hiệu suất tốt nhất?
Mình đã chạy thử nghiệm với một mảng gồm 1 triệu sản phẩm. Khi sử dụng các khóa integer, hóa ra cả ba cấu trúc điều khiển đều chạy trong cùng một khoảng thời gian (0,02 giây trên máy thử nghiệm).
Tuy nhiên, nếu chúng ta đang xử lý các mảng kết hợp (nghĩa là, các mảng có key là chuỗi), vòng lặp foreach nhanh hơn gấp đôi so với vòng lặp for và vòng lặp while.
Điều này là có thể bởi thực tế là các cấu trúc điều khiển while và for cần phải sử dụng hàm phụ trợ để truy xuất các phần tử mảng, như current() và next().
Foreach thường là cách dễ nhất để lặp qua các mảng và hóa ra nó cũng nhanh nhất, vì vậy nó nên là lựa chọn hàng đầu của bạn.
Tuy nhiên, điều đáng nói là trong các tình huống thực tế, sự khác biệt về hiệu năng sẽ gần như không đáng kể, trừ khi bạn cần xử lý các mảng thực sự lớn (và trong trường hợp đó, trước tiên bạn nên sửa ứng dụng của mình hoặc sử dụng một giải pháp khác như là Java).
Đoạn mã so sánh hiệu suất của Foreach so với vòng lặp For và vòng lặp While
/* Thiết lập cái này để tránh lỗi bộ nhớ */
ini_set('memory_limit','1024M');
$array = array();
/* Tạo một mảng số */
for ($i = 0; $i < 1000000; $i++)
{
$array[$i] = $i;
}
/* Hoặc tạo một mảng kết hợp */
for ($i = 0; $i < 1000000; $i++)
{
$array[$i] = strval($i) . 'string';
}
$count = count($array);
$start = microtime(TRUE);
/* Vòng lặp Foreach, OK cho cả 2 mảng */
foreach ($array as $item)
{
$var = $item;
}
/* For chỉ lặp mảng số */
for ($i = 0; $i < $count; $i++)
{
$var = $array[$i];
}
/* While chỉ lặp mảng số */
$i = 0;
while ($i < $count)
{
$var = $array[$i];
$i++;
}
/* For lặp mảng kết hợp */
for ($item = current($array); $item !== FALSE; $item = next($array))
{
$var = $item;
}
/* While lặp mảng kết hợp */
$item = current($array);
while ($item !== FALSE)
{
$var = $item;
$item = next($array);
}
$end = microtime(TRUE);
echo ($end - $start);
6. Hiệu suất của key => value
Các vòng lặp Foreach của PHP hỗ trợ cả cú pháp $array as $value và $array as $key => $value
-
Cú pháp đầu tiên có hiệu suất nhanh nhất.
Trong thử nghiệm của mình, sử dụng cú pháp thứ hai dẫn đến thời gian thực hiện dài hơn 50%. Nếu bạn không cần key, có lẽ bạn nên sử dụng cú pháp đầu tiên.
Mặc dù vậy, các con số quá nhỏ đến nỗi bạn sẽ chẳng nhận thấy bất kỳ sự khác biệt nào trong các tình huống thực tế.
Vì vậy đừng lo lắng quá nhiều về hiệu suất (trên máy thử nghiệm của mình, hai thử nghiệm đã chạy trong khoảng 0,2 và 0,3 giây, trong một Mảng 10 triệu phần tử).
Lời kết
Trên đây là 6 sự thật về vòng lặp Foreach mà mình nghĩ là bạn nên biết nếu bạn muốn trở thành một Lập trình viên PHP có kinh nghiệm.
-
Bạn sẽ nhận được nhiều kinh nghiệm lập trình hơn với Khóa Học Lập trình Web với PHP tại NIIT - ICT Hà Nội
Như mọi khi, mình thực sự cảm ơn bạn đã đọc bài viết này, và nếu bạn thích nó, xin vui lòng dành một giây để chia sẻ nó!
Alex
---
HỌC VIỆN ĐÀO TẠO CNTT NIIT - ICT HÀ NỘI
Dạy học Lập trình chất lượng cao (Since 2002). Học làm Lập trình viên. Hành động ngay!
Đc: Tầng 3, 25T2, N05, Nguyễn Thị Thập, Cầu Giấy, Hà Nội
SĐT: 02435574074 - 0914939543 - 0353655150
Email: hello@niithanoi.edu.vn
Fanpage: https://facebook.com/NIIT.ICT/
#niit #niithanoi #niiticthanoi #hoclaptrinh #khoahoclaptrinh #hoclaptrinhjava #hoclaptrinhphp