每天一個 PHP 語法四引用使用及實現

ifelse發表於2020-04-15

說明

這裡基於php7.2.5進行測試,php7之後內部結構變化應該不是太大,但與php5.X有差別。

什麼是引用

在PHP中引用是一種資料型別(結構),是指 指向同一個型別的資料結構,來看具體儲存結構

struct _zend_reference {
  // 引用計數用於垃圾回收  先忽略
    zend_refcounted_h gc; 
  // zval是另一個變數  zval還記得嗎 儲存變數的結構
  // 這裡val指向另一個zval
    zval              val;
};

忘了的這裡看zval

如何使用

// 定義變數
$a = "hello";
// &生成引用變數
$b = &$a;

echo $b;

echo PHP_EOL;
php hello.php

hello

結果大家都知道,$b與$a的值是相同的,而且你還知道,修改$b同時也會作用與$a.

$a = "hello";
$b = &$a;

echo "b:".$b;

echo PHP_EOL;

$b = "world";

echo "a:".$a;

echo PHP_EOL;
php hello.php

b:hello
a:world

如何實現

回憶zval的格式執行第一句時,生成的資料結構是這樣的(簡版示意圖,真實結構複雜)

當執行$b = &$a時,&$a會先生成引用型別的資料結構,然後引用的zval指向之前的hello的結構,$a,$b則共同指向引用結構。

那麼在修改$b時,實際是修改了引用型別指向的zend_value,所以導致$a的值也發生了變化。

小結

php中使用&生成一個引用型別的資料,這個引用的zval指向原變數所指向的zval, 變數則會指向這個應用結構,當發生引用賦值之後,被賦值的變數也會指向這個引用,更改其中任何一個變數,所有的變數都會發生變化。類似於你大名叫大壯, 小名叫小壯, 但是身份證都是xxoo100, 無論修改大壯還是小壯的身份證,他始終只有一個身份證。

擴充套件

我們可能聽過取地址符這麼一種說法,那麼在C語言或者GO中,通過使用&可以獲取到變數的記憶體地址。

#include <stdio.h>
#include<string.h>

int main()
{
    char str[] = "hello";
  // &獲取變數的記憶體地址
    printf("%p\n", &str);
}
gcc pointer.c -o pointer
./pointer

0x7ffee6d3292a

但是PHP的&並不是獲取變數的地址,這是需要注意的。

unset引用變數

unset($b);
echo "a:".$a;

echo PHP_EOL;
echo "b:".$b;

echo PHP_EOL;
php hello.php

a:world
PHP Notice:  Undefined variable: b

unset($b)之後,只是銷燬了變數b及b對引用的指向,沒有影響$a。

foreach中的引用

$list = [
    ['id' => 3, 'total' => 3],
    ['id' => 4, 'total' => 4],
    ['id' => 5, 'total' => 5],
];

foreach ($list as $key => &$info) {
    $info['total'] = $info['total'] + 3;
}

print_r($list);

// 一頓操作
$info['name'] = "hello";
print_r($info);
php hello.php

Array
(
    [0] => Array
        (
            [id] => 3
            [total] => 6
        )

    [1] => Array
        (
            [id] => 4
            [total] => 7
        )

    [2] => Array
        (
            [id] => 5
            [total] => 8
        )

)

Array
(
    [id] => 5
    [total] => 8
    [name] => hello
)

這裡在foreach之後需要把info unset掉防止發生資料問題。

總結

PHP中通過&獲取對變數的引用,實質是多個變數通過中間引用型別(不是指向記憶體地址),指向同一個值。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章