Các mảng trong PHP được sao chép dưới dạng giá trị hoặc tham chiếu đến các biến mới và khi được truyền cho các hàm?


259

1) Khi một mảng được truyền dưới dạng đối số cho một phương thức hoặc hàm, nó được truyền bằng tham chiếu hay theo giá trị?

2) Khi gán một mảng cho một biến, biến mới là tham chiếu đến mảng ban đầu, hay nó là bản sao mới?
Còn việc này thì sao?

$a = array(1,2,3);
$b = $a;

$bmột tài liệu tham khảo $a?


Đồng thời xem Bản sao khi nào-foreach-copy
nawfal

3
@MarlonJerezIsla: có vẻ như Array chỉ được nhân bản nếu bạn sửa đổi nó bên trong hàm. Vẫn đến từ các ngôn ngữ khác, nó có vẻ kỳ lạ.
dùng276648

Câu trả lời:


276

Đối với phần thứ hai của câu hỏi của bạn, xem trang mảng của hướng dẫn , trạng thái (trích dẫn) :

Việc gán mảng luôn liên quan đến sao chép giá trị. Sử dụng toán tử tham chiếu để sao chép một mảng bằng tham chiếu.

Và ví dụ đã cho:

<?php
$arr1 = array(2, 3);
$arr2 = $arr1;
$arr2[] = 4; // $arr2 is changed,
             // $arr1 is still array(2, 3)

$arr3 = &$arr1;
$arr3[] = 4; // now $arr1 and $arr3 are the same
?>


Đối với phần đầu tiên, cách tốt nhất để chắc chắn là thử ;-)

Xem xét ví dụ về mã này:

function my_func($a) {
    $a[] = 30;
}

$arr = array(10, 20);
my_func($arr);
var_dump($arr);

Nó sẽ cung cấp đầu ra này:

array
  0 => int 10
  1 => int 20

Điều này cho biết hàm đã không sửa đổi mảng "bên ngoài" được truyền dưới dạng tham số: nó được truyền dưới dạng bản sao và không phải là tham chiếu.

Nếu bạn muốn nó được chuyển qua tham chiếu, bạn sẽ phải sửa đổi hàm, theo cách này:

function my_func(& $a) {
    $a[] = 30;
}

Và đầu ra sẽ trở thành:

array
  0 => int 10
  1 => int 20
  2 => int 30

Như, lần này, mảng đã được thông qua "bằng cách tham chiếu".


Đừng ngần ngại đọc phần Tài liệu tham khảo Giải thích của tài liệu hướng dẫn: nó sẽ trả lời một số câu hỏi của bạn ;-)


những gì như $ a = & $ this-> a. $ A bây giờ có phải là tài liệu tham khảo cho & this-> a không?
Frank

1
Khi bạn đang sử dụng &, vâng, nó nên - xem php.net/manual/en/ Kẻ
Pascal MARTIN

1
Chúa ơi, tôi không thể tin đây là vấn đề tôi gặp phải ... đây có phải là một bài học không, luôn luôn đọc hướng dẫn sử dụng
Heavy_Bullets

2
Xin chào Pascal, tôi thấy câu trả lời của Kosta Kontos dường như chính xác hơn. Tôi làm một bài kiểm tra nhanh đơn giản để xác nhận phát hiện của anh ấy gist.github.com/anonymous/aaf845ae354578b74906 Bạn có thể nhận xét về phát hiện của anh ấy không?
Cheok Yan Cheng

1
Đây cũng là vấn đề tôi gặp phải: nghĩ rằng đó là một cái gì đó kỳ lạ về các mảng lồng nhau nhưng thực ra đó chỉ là cách gán mảng hoạt động trong PHP.
Danh sách Jeremy

120

Liên quan đến câu hỏi đầu tiên của bạn, mảng được truyền bằng tham chiếu UNLESS, nó được sửa đổi trong phương thức / hàm bạn đang gọi. Nếu bạn cố gắng sửa đổi mảng trong phương thức / hàm, một bản sao của nó được tạo trước, và sau đó chỉ bản sao được sửa đổi. Điều này làm cho có vẻ như mảng được truyền theo giá trị trong khi thực tế nó không phải là.

Ví dụ: trong trường hợp đầu tiên này, mặc dù bạn không xác định hàm của mình để chấp nhận $ my_array theo tham chiếu (bằng cách sử dụng ký tự & trong định nghĩa tham số), nó vẫn được chuyển qua tham chiếu (nghĩa là: bạn không lãng phí bộ nhớ với một bản sao không cần thiết).

function handle_array($my_array) {  

    // ... read from but do not modify $my_array
    print_r($my_array);

    // ... $my_array effectively passed by reference since no copy is made
}

Tuy nhiên, nếu bạn sửa đổi mảng, một bản sao của nó sẽ được tạo trước (sử dụng nhiều bộ nhớ hơn nhưng khiến mảng ban đầu của bạn không bị ảnh hưởng).

function handle_array($my_array) {

    // ... modify $my_array
    $my_array[] = "New value";

    // ... $my_array effectively passed by value since requires local copy
}

FYI - đây được gọi là "bản sao lười biếng" hoặc "bản sao trên văn bản".


8
Đây là một thông tin siêu thú vị! Hình như đó là sự thật; nhưng tôi không thể tìm thấy bất kỳ tài liệu chính thức nào hỗ trợ thực tế này. Chúng ta cũng cần biết phiên bản nào của PHP hỗ trợ khái niệm sao chép lười biếng này. Bất cứ ai có thêm thông tin?
Mario Awad

8
Cập nhật, tìm thấy một số tài liệu chính thức, vẫn cần tìm phiên bản PHP nào hỗ trợ sao chép lười biếng (họ gọi nó là "copy on write" trong hướng dẫn sử dụng): php.net/manual/en/i INTERNals2.variabled.intro.php
Mario Awad

7
Đây hoàn toàn là một quyết định triển khai của máy ảo PHP và không phải là một phần của ngôn ngữ - nó thực sự không hiển thị đối với người lập trình. Copy-on-write chắc chắn được khuyến nghị vì lý do hiệu năng, nhưng việc triển khai sao chép mọi mảng không có sự khác biệt so với quan điểm của lập trình viên, vì vậy chúng tôi có thể nói rằng ngữ nghĩa ngôn ngữ chỉ định giá trị truyền qua.
Superfly

14
@Superfly chắc chắn sẽ tạo ra sự khác biệt khi tôi muốn biết liệu tôi có thể vượt qua mảng 100MB của mình thông qua hàng tá chức năng mà không hết bộ nhớ hay không! Bạn có thể đúng rằng dù sao cũng đúng khi gọi các giá trị ngữ nghĩa, nhưng bỏ qua những ngụy biện như vậy về thuật ngữ, "chi tiết triển khai" được đề cập ở đây chắc chắn là vấn đề đối với các lập trình viên PHP trong thế giới thực.
Đánh dấu Amery

3
Có một cách giải quyết khác về vấn đề này, điều này khiến cho việc nhận thức về việc sao chép trên văn bản thậm chí còn quan trọng hơn khi nghĩ về hiệu suất. Bạn có thể nghĩ việc truyền mảng bằng tham chiếu giúp tiết kiệm bộ nhớ so với truyền theo giá trị (nếu bạn không biết về sao chép khi ghi) nhưng thực sự nó có thể có tác dụng ngược lại ! Nếu mảng được sau đó truyền theo giá trị (theo mã bên sở hữu hoặc thứ 3 của bạn), PHP sau đó để tạo ra một bản sao đầy đủ hoặc nó không còn có thể theo dõi các tính tham khảo! Xem thêm tại đây: stackoverflow.com/questions/21974581/ Ấn
Dan King

80

TL; DR

a) phương thức / hàm chỉ đọc tham số mảng => tham chiếu ẩn (bên trong)
b) phương thức / hàm sửa đổi đối số mảng => giá trị
c) đối số mảng phương thức / hàm được đánh dấu rõ ràng là tham chiếu (có dấu và) => tham chiếu rõ ràng (đất sử dụng)

Hoặc này:
- param-mảng không ampersand : được truyền bằng tham chiếu; các hoạt động viết thay đổi một bản sao mới của mảng, bản sao được tạo ra trong lần ghi đầu tiên;
- ampersand mảng param : thông qua tham chiếu; các hoạt động viết thay đổi mảng ban đầu.

Hãy nhớ rằng - PHP thực hiện sao chép giá trị thời điểm bạn viết vào tham số mảng không phải là dấu và. Đó là những gì copy-on-writecó nghĩa. Tôi muốn cho bạn thấy nguồn C của hành vi này, nhưng nó đáng sợ ở đó. Sử dụng tốt hơn xdebug_debug_zval () .

Pascal MARTIN đã đúng. Kosta Kontos thậm chí còn hơn thế.

Câu trả lời

Nó phụ thuộc.

Phiên bản dài

Tôi nghĩ rằng tôi đang viết điều này cho bản thân mình. Tôi nên có một blog hoặc một cái gì đó ...

Bất cứ khi nào mọi người nói về các tài liệu tham khảo (hoặc con trỏ, cho vấn đề đó), họ thường kết thúc trong một logisticy (chỉ cần nhìn vào chủ đề này !).
PHP là một ngôn ngữ đáng kính, tôi nghĩ rằng tôi nên thêm vào sự nhầm lẫn (mặc dù đây là một bản tóm tắt các câu trả lời ở trên). Bởi vì, mặc dù hai người có thể đúng cùng một lúc, tốt hơn hết là bạn chỉ nên đập đầu vào một câu trả lời.

Trước hết, bạn nên biết rằng bạn không phải là một người bán hàng nếu bạn không trả lời theo cách thức trắng đen . Mọi thứ phức tạp hơn "có / không".

Như bạn sẽ thấy, toàn bộ điều theo giá trị / tham chiếu có liên quan rất nhiều đến chính xác những gì bạn đang làm với mảng đó trong phạm vi phương thức / hàm của bạn: đọc nó hoặc sửa đổi nó?

PHP nói gì? (còn gọi là "thay đổi khôn ngoan")

Các nhãn hiệu nói này (tôi nhấn mạnh):

Theo mặc định, các đối số hàm được truyền theo giá trị (để nếu giá trị của đối số trong hàm bị thay đổi , nó không bị thay đổi bên ngoài hàm). Để cho phép một hàm sửa đổi các đối số của nó, chúng phải được truyền bằng tham chiếu .

Để có một đối số cho một hàm luôn được truyền bằng tham chiếu, hãy thêm một dấu và (&) vào tên đối số trong định nghĩa hàm

Theo như tôi có thể nói, khi các lập trình viên lớn, nghiêm túc, trung thực với Chúa nói về các tài liệu tham khảo, họ thường nói về việc thay đổi giá trị của tài liệu tham khảo đó . Và đó chính xác là những gì hướng dẫn sử dụng nói về : hey, if you want to CHANGE the value in a function, consider that PHP's doing "pass-by-value".

Có một trường hợp khác mà họ không đề cập đến, mặc dù: nếu tôi không thay đổi bất cứ điều gì - chỉ cần đọc?
Điều gì xảy ra nếu bạn truyền một mảng cho một phương thức không đánh dấu rõ ràng một tham chiếu và chúng ta không thay đổi mảng đó trong phạm vi hàm? Ví dụ:

<?php
function readAndDoStuffWithAnArray($array) 
{
    return $array[0] + $array[1] + $array[2];
}

$x = array(1, 2, 3);

echo readAndDoStuffWithAnArray($x);

Đọc tiếp, bạn đồng hành của tôi.

PHP thực sự làm gì? (còn gọi là "trí nhớ khôn ngoan")

Các lập trình viên lớn và nghiêm túc tương tự, khi họ thậm chí còn nghiêm túc hơn, họ nói về "tối ưu hóa bộ nhớ" liên quan đến các tài liệu tham khảo. PHP cũng vậy. Bởi vì PHP is a dynamic, loosely typed language, that uses copy-on-write and reference counting, đó là lý do .

Sẽ không lý tưởng khi truyền các mảng HUGE cho các hàm khác nhau và PHP để tạo các bản sao của chúng (rốt cuộc đó là "pass-by-value"):

<?php

// filling an array with 10000 elements of int 1
// let's say it grabs 3 mb from your RAM
$x = array_fill(0, 10000, 1); 

// pass by value, right? RIGHT?
function readArray($arr) { // <-- a new symbol (variable) gets created here
    echo count($arr); // let's just read the array
}

readArray($x);

Bây giờ, nếu điều này thực sự là giá trị truyền qua, chúng ta sẽ có một số RAM 3mb +, bởi vì có hai bản sao của mảng đó, phải không?

Sai lầm. Miễn là chúng ta không thay đổi $arrbiến số, đó là một tài liệu tham khảo, thông minh về trí nhớ . Bạn không nhìn thấy nó. Đó là lý do tại sao PHP đề cập đến các tham chiếu đất người dùng khi nói về &$someVar, để phân biệt giữa các tham chiếu nội bộ và rõ ràng (với ký hiệu và).

Sự kiện

Vì thế, when an array is passed as an argument to a method or function is it passed by reference?

Tôi đã đưa ra ba trường hợp (yeah, ba):
a) phương thức / hàm chỉ đọc đối số mảng
b) phương thức / hàm sửa đổi đối số mảng
c) đối số mảng phương thức / hàm được đánh dấu rõ ràng là tham chiếu (với một ký hiệu)


Đầu tiên, chúng ta hãy xem mảng đó thực sự ăn bao nhiêu bộ nhớ (chạy ở đây ):

<?php
$start_memory = memory_get_usage();
$x = array_fill(0, 10000, 1);
echo memory_get_usage() - $start_memory; // 1331840

Đó là nhiều byte. Tuyệt quá.

a) phương thức / hàm chỉ đọc đối số mảng

Bây giờ chúng ta hãy tạo một hàm chỉ đọc mảng đã nói dưới dạng đối số và chúng ta sẽ thấy logic đọc chiếm bao nhiêu bộ nhớ:

<?php

function printUsedMemory($arr) 
{
    $start_memory = memory_get_usage();

    count($arr);       // read
    $x = $arr[0];      // read (+ minor assignment)
    $arr[0] - $arr[1]; // read

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1); // this is 1331840 bytes
printUsedMemory($x);

Muốn đoán không? Tôi nhận được 80! Xem cho chính mình . Đây là phần mà hướng dẫn sử dụng PHP bỏ qua. Nếu thông số $arrthực sự được truyền qua giá trị, bạn sẽ thấy một cái gì đó tương tự như 1331840byte. Có vẻ như $arrhành xử như một tài liệu tham khảo, phải không? Đó là bởi vì nó một tài liệu tham khảo - một nội bộ.

b) phương thức / hàm sửa đổi đối số mảng

Bây giờ, hãy viết cho param đó, thay vì đọc từ nó:

<?php

function printUsedMemory($arr)
{
    $start_memory = memory_get_usage();

    $arr[0] = 1; // WRITE!

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1);
printUsedMemory($x);

Một lần nữa, xem cho chính mình , nhưng, đối với tôi, đó là khá gần là 1331840. Vì vậy, trong trường hợp này, mảng được thực sự được sao chép vào $arr.

c) đối số mảng phương thức / hàm được đánh dấu rõ ràng là tham chiếu (với dấu và)

Bây giờ chúng ta hãy xem có bao nhiêu bộ nhớ hoạt động ghi vào một tham chiếu rõ ràng (chạy ở đây ) - lưu ý dấu và trong chữ ký hàm:

<?php

function printUsedMemory(&$arr) // <----- explicit, user-land, pass-by-reference
{
    $start_memory = memory_get_usage();

    $arr[0] = 1; // WRITE!

    echo memory_get_usage() - $start_memory; // let's see the memory used whilst reading
}

$x = array_fill(0, 10000, 1);
printUsedMemory($x);

Đặt cược của tôi là bạn nhận được tối đa 200! Vì vậy, điều này ăn khoảng bộ nhớ nhiều như đọc từ một thông số không ampersand .


Tiết kiệm cho tôi một vài giờ trong việc gỡ lỗi rò rỉ bộ nhớ!
Ragen Dazs

2
Kosta Kontos: Đây là một câu hỏi quan trọng đến mức bạn nên đánh dấu đây là câu trả lời được chấp nhận. Điều đó nói rằng, @nevvermind: Bài luận tuyệt vời, nhưng vui lòng bao gồm phần TL; DR hàng đầu.
Nhà phát triển AVID

1
@nevvermind: Tôi không phải là một từ viết tắt, khác biệt chính là Kết luận xuất hiện thường ở cuối bài viết, trong khi TL; DR xuất hiện dưới dạng dòng đầu tiên cho những người chỉ cần trả lời ngắn thay vì đi qua một phân tích dài . Nghiên cứu của bạn là tốt và đây không phải là lời chỉ trích, chỉ là $ 00,02 của tôi.
Nhà phát triển AVID

1
Bạn đúng. Tôi đã đặt kết luận lên hàng đầu. Nhưng tôi vẫn muốn mọi người ngừng lười biếng trong việc đọc toàn bộ, trước khi đi đến bất kỳ kết luận nào . Cuộn quá dễ dàng để chúng ta bận tâm thay đổi thứ tự của mọi thứ.
nevvermind

1
Tôi đoán PHP đã đạt được nhiều năm hiệu quả hơn sau đó vì các ví dụ về máy tính bảng của bạn cho số lượng thấp hơn nhiều :)
drzaus

14

Theo mặc định

  1. Nguyên thủy được thông qua bởi giá trị. Không có khả năng với Java, chuỗi là nguyên thủy trong PHP
  2. Mảng nguyên thủy được truyền theo giá trị
  3. Các đối tượng được thông qua tham chiếu
  4. Mảng của các đối tượng được truyền theo giá trị (mảng) nhưng mỗi đối tượng được truyền bằng tham chiếu.

    <?php
    $obj=new stdClass();
    $obj->field='world';
    
    $original=array($obj);
    
    
    function example($hello) {
        $hello[0]->field='mundo'; // change will be applied in $original
        $hello[1]=new stdClass(); // change will not be applied in $original
        $
    }
    
    example($original);
    
    var_dump($original);
    // array(1) { [0]=> object(stdClass)#1 (1) { ["field"]=> string(5) "mundo" } } 

Lưu ý: Là một tối ưu hóa, mọi giá trị đơn lẻ được truyền dưới dạng tham chiếu cho đến khi nó được sửa đổi bên trong hàm. Nếu nó được sửa đổi và giá trị được chuyển qua tham chiếu thì nó đã được sao chép và bản sao được sửa đổi.


4
Câu trả lời này phải được + 1'ed lên đầu. Nó chứa một gotcha tối nghĩa mà các câu trả lời khác không đề cập: "4 - Mảng của các đối tượng được truyền theo giá trị (mảng) nhưng mỗi đối tượng được truyền bằng tham chiếu." Tôi đã gãi đầu vì cái đó!
augustin

@magallanes tuyệt vời nên được đánh giá đầu tiên đối với tôi, bạn làm rõ cho tôi một rắc rối của mảng đối tượng mà tôi đã có. Có cách nào để sửa đổi một đối tượng trong một mảng chỉ bằng một trong hai biến mảng (bản gốc và bản sao) không?
fede72bari

5

Khi một mảng được truyền cho một phương thức hoặc hàm trong PHP, nó được truyền theo giá trị trừ khi bạn rõ ràng chuyển nó bằng tham chiếu, như vậy:

function test(&$array) {
    $array['new'] = 'hey';
}

$a = $array(1,2,3);
// prints [0=>1,1=>2,2=>3]
var_dump($a);
test($a);
// prints [0=>1,1=>2,2=>3,'new'=>'hey']
var_dump($a);

Trong câu hỏi thứ hai của bạn, $bkhông phải là một tài liệu tham khảo $a, mà là một bản sao của $a.

Giống như ví dụ đầu tiên, bạn có thể tham khảo $abằng cách làm như sau:

$a = array(1,2,3);
$b = &$a;
// prints [0=>1,1=>2,2=>3]
var_dump($b);
$b['new'] = 'hey';
// prints [0=>1,1=>2,2=>3,'new'=>'hey']
var_dump($a);

1

Chủ đề này là một chút cũ hơn nhưng ở đây một cái gì đó tôi vừa đi qua:

Hãy thử mã này:

$date = new DateTime();
$arr = ['date' => $date];

echo $date->format('Ymd') . '<br>';
mytest($arr);
echo $date->format('Ymd') . '<br>';

function mytest($params = []) {
    if (isset($params['date'])) {
        $params['date']->add(new DateInterval('P1D'));
    }
}

http://codepad.viper-7.com/gwPYMw

Lưu ý rằng không có amp cho tham số $ params và nó vẫn thay đổi giá trị của $ Array ['date']. Điều này không thực sự phù hợp với tất cả các giải thích khác ở đây và những gì tôi nghĩ cho đến bây giờ.

Nếu tôi sao chép đối tượng $ params ['date'], ngày kết thúc thứ 2 vẫn giữ nguyên. Nếu tôi chỉ đặt nó thành một chuỗi, nó cũng không ảnh hưởng đến đầu ra.


3
Mảng được sao chép, nhưng nó không phải là một bản sao sâu . Điều này có nghĩa là các giá trị nguyên thủy như số và chuỗi được sao chép vào $ param, nhưng đối với các đối tượng, tham chiếu được sao chép thay vì đối tượng được sao chép. $ Array đang giữ một tham chiếu đến $ date và mảng sao chép $ params cũng vậy. Vì vậy, khi bạn gọi một hàm trên $ params ['date'] làm thay đổi giá trị của nó, bạn cũng sẽ thay đổi $ Array ['date'] và $ date. Khi bạn đặt $ params ['date'] thành một chuỗi, bạn chỉ thay thế tham chiếu của $ params thành $ date bằng thứ khác.
ejegg

1

Để mở rộng một trong những câu trả lời, các phân đoạn của mảng đa chiều được truyền theo giá trị trừ khi được chuyển qua tham chiếu rõ ràng bằng tham chiếu.

<?php
$foo = array( array(1,2,3), 22, 33);

function hello($fooarg) {
  $fooarg[0][0] = 99;
}

function world(&$fooarg) {
  $fooarg[0][0] = 66;
}

hello($foo);
var_dump($foo); // (original array not modified) array passed-by-value

world($foo);
var_dump($foo); // (original array modified) array passed-by-reference

Kết quả là:

array(3) {
  [0]=>
  array(3) {
    [0]=>
    int(1)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  array(3) {
    [0]=>
    int(66)
    [1]=>
    int(2)
    [2]=>
    int(3)
  }
  [1]=>
  int(22)
  [2]=>
  int(33)
}

0

Trong các mảng PHP được truyền cho các hàm theo giá trị theo mặc định, trừ khi bạn rõ ràng chuyển chúng theo tham chiếu, như đoạn mã sau đây cho thấy:

$foo = array(11, 22, 33);

function hello($fooarg) {
  $fooarg[0] = 99;
}

function world(&$fooarg) {
  $fooarg[0] = 66;
}

hello($foo);
var_dump($foo); // (original array not modified) array passed-by-value

world($foo);
var_dump($foo); // (original array modified) array passed-by-reference

Đây là đầu ra:

array(3) {
  [0]=>
  int(11)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
array(3) {
  [0]=>
  int(66)
  [1]=>
  int(22)
  [2]=>
  int(33)
}
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.