PHP Conference Japan 2024

參考的用途

使用參考會執行三種基本操作:透過參考賦值透過參考傳遞,以及 透過參考返回。本節將介紹這些操作,並提供進一步閱讀的連結。

透過參考賦值

在第一種情況中,PHP 參考允許您讓兩個變數指向相同的內容。這意味著,當您執行

<?php

$a
=& $b;

?>
表示 $a$b 指向相同的內容。

注意事項:

在這裡,$a$b 完全相等。 $a 並非指向 $b,反之亦然。 $a$b 指向同一個位置。

注意事項:

如果您透過引用賦值、傳遞或返回一個未定義的變數,它將會被建立。

範例 #1 使用未定義變數的引用

<?php

function foo(&$var) {}

foo($a); // $a 被「建立」並賦值為 null

$b = array();
foo($b['b']);
var_dump(array_key_exists('b', $b)); // bool(true)

$c = new stdClass();
foo($c->d);
var_dump(property_exists($c, 'd')); // bool(true)

?>

相同的語法可以用於返回引用的函式

<?php

$foo
=& find_var($bar);

?>

對*不*以引用返回的函式使用相同的語法會產生錯誤,對 new 運算子的結果使用它也會產生錯誤。 雖然物件是以指標的方式傳遞,但這些指標與引用不同,詳見物件與引用的說明。

警告

如果您在函式內將一個引用賦值給一個宣告為 global 的變數,則該引用將只在函式內可見。 您可以使用 $GLOBALS 陣列來避免這種情況。

範例 #2 在函式內引用全域變數

<?php

$var1
= "Example variable";
$var2 = "";

function
global_references($use_globals)
{
global
$var1, $var2;

if (!
$use_globals) {
$var2 =& $var1; // 僅在函式內可見
} else {
$GLOBALS["var2"] =& $var1; // 在全域範圍也看得到
}
}

global_references(false);
echo
"var2 的值是 '$var2'\n"; // var2 的值是 ''

global_references(true);
echo
"var2 的值是 '$var2'\n"; // var2 的值是 'Example variable'

?>
可以把 global $var; 想成 $var =& $GLOBALS['var']; 的簡寫。因此,將另一個參考指派給 $var 只會改變區域變數的參考。

注意事項:

如果您在 foreach 陳述式中將值指派給具有參考的變數,則參考也會被修改。

範例 #3 參考與 foreach 陳述式

<?php

$ref
= 0;
$row =& $ref;

foreach (array(
1, 2, 3) as $row) {
// 做一些事
}

echo
$ref; // 3 - 迭代陣列的最後一個元素

?>

雖然嚴格來說不是透過參考進行賦值,但使用語言結構 array() 建立的表達式也可以透過在要新增的陣列元素前加上 & 來表現得像這樣。範例

<?php

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

$arr = array(&$a, &$b[0], &$b[1]);
$arr[0]++;
$arr[1]++;
$arr[2]++;
/* $a == 2, $b == array(3, 4); */

?>

不過,需要注意的是,陣列內的參考值潛在危險性。以一般方式(非透過參考)將參考值賦值到左值時,並不會讓左值變成參考值,但是陣列內的參考值會在這些一般賦值中保留下來。這也適用於以值傳遞陣列的函式呼叫。範例:

<?php

/* 純量變數的賦值 */
$a = 1;
$b =& $a;
$c = $b;
$c = 7; // $c 不是參考值;$a 或 $b 不會改變

/* 陣列變數的賦值 */
$arr = array(1);
$a =& $arr[0]; // $a 和 $arr[0] 屬於同一個參考集合
$arr2 = $arr; // 這不是透過參考賦值!
$arr2[0]++;
/* $a == 2, $arr == array(2) */
/* 即使 $arr 不是參考值,$arr 的內容也會被改變! */

?>
換句話說,陣列的參考行為是以元素為單位定義的;個別元素的參考行為與陣列容器的參考狀態是分離的。

傳值呼叫 (Pass By Reference)

參考值的第二個作用是透過參考傳遞變數。這是藉由讓函式中的區域變數和呼叫範圍中的變數參考相同的內容來完成的。範例:

<?php

function foo(&$var)
{
$var++;
}

$a=5;
foo($a);

?>
會讓 $a 的值變成 6。這是因為在函式 foo 中,變數 $var 指向的內容與 $a 相同。更多相關資訊,請閱讀傳遞參數 by reference 的章節。

透過參考回傳 (Return By Reference)

參考的第三種用法是透過參考回傳

新增註解

使用者貢獻的註解 23 則註解

ladoo at gmx dot at
19 年前
我在使用 pbaltz at NO_SPAM dot cs dot NO_SPAM dot wisc dot edu 底下範例的擴展版本時遇到了一些問題。
這可能會有點令人困惑,儘管如果您仔細閱讀了手冊,就會很清楚。它清楚地說明了參考總是指向變數內容的事實(至少對我來說是這樣)。

<?php
$a
= 1;
$c = 2;
$b =& $a; // $b 指向 1
$a =& $c; // $a 現在指向 2,但 $b 仍然指向 1;
echo $a, " ", $b;
// 輸出:2 1
?>
dexant9t at gmail dot com
3 年前
操作的是參考還是值很重要

這裡我們操作的是值,因此對參考進行操作也會更新原始變數;

$a = 1;
$c = 22;

$b = & $a;
echo "$a, $b"; //輸出:1, 1

$b++;
echo "$a, $b";//輸出:2, 2 兩個值都被更新

$b = 10;
echo "$a, $b";//輸出:10, 10 兩個值都被更新

$b =$c; //這會將值 22 指派給 $b,同時也會更新 $a
echo "$a, $b";//輸出:22, 22

但是,如果您使用 $b = &$c 取代 $b=$c
$b = &$c; //只更新 $b 的值,$a 仍然指向 10,$b 現在作為變數 $c 的參考

echo "$a, $b"//輸出:10, 22
Hlavac
17 年前
小心這個

foreach ($somearray as &$i) {
// 更新 $i 的值...
}
...
foreach ($somearray as $i) {
// $somearray 的最後一個元素神祕地被覆蓋了!
}

問題在於,第一個 foreach 迴圈之後,$i 包含了 $somearray 最後一個元素的參考,而第二個 foreach 迴圈很開心地對它進行賦值!
elrah [] polyptych [dot] com
13 年前
參考似乎會產生副作用。以下有兩個例子。兩者都只是將一個陣列複製到另一個陣列。在第二個例子中,在複製之前建立了對第一個陣列中一個值的參考。在第一個例子中,索引 0 的值指向兩個不同的記憶體位置。在第二個例子中,索引 0 的值指向同一個記憶體位置。

我不會說這是一個 bug,因為我不知道 PHP 的設計行為是什麼,但我認為「任何」開發人員都不會預期到這種行為,所以要小心。

這種情況可能造成問題的一個例子是,如果您在腳本中執行陣列複製並期望某種行為,但稍後在腳本的前面部分添加對陣列中一個值的參考,然後發現陣列複製行為意外地改變了。

<?php
// 例一
$arr1 = array(1);
echo
"\n複製前:\n";
echo
"\$arr1[0] == {$arr1[0]}\n";
$arr2 = $arr1;
$arr2[0]++;
echo
"\n複製後:\n";
echo
"\$arr1[0] == {$arr1[0]}\n";
echo
"\$arr2[0] == {$arr2[0]}\n";

// 例二
$arr3 = array(1);
$a =& $arr3[0];
echo
"\n複製前:\n";
echo
"\$a == $a\n";
echo
"\$arr3[0] == {$arr3[0]}\n";
$arr4 = $arr3;
$arr4[0]++;
echo
"\n複製後:\n";
echo
"\$a == $a\n";
echo
"\$arr3[0] == {$arr3[0]}\n";
echo
"\$arr4[0] == {$arr4[0]}\n";
?>
amp at gmx dot info
17 年前
有些事情乍看之下可能並不明顯
如果您想要透過引用來循環陣列,您不能使用單純賦值的 foreach 控制結構。您必須使用擴展的鍵值賦值 foreach 或 for 控制結構。

單純賦值的 foreach 控制結構會產生物件或值的副本。以下程式碼:

`$v1 = 0;`
`$arrV = array(&$v1, &$v1);`
`foreach ($arrV as $v)`
{
`$v1++;`
`echo $v."\n";`
}

會產生:

0
1

這表示 foreach 中的 $v 並非 $v1 的引用,而是陣列中實際元素所參考物件的副本。

以下程式碼:

`$v1 = 0;`
`$arrV = array(&$v1, &$v1);`
`foreach ($arrV as $k => $v)`
{
`$v1++;`
`echo $arrV[$k]."\n";`
}

以及

`$v1 = 0;`
`$arrV = array(&$v1, &$v1);`
`$c = count($arrV);`
`for ($i = 0; $i < $c; $i++)`
{
`$v1++;`
`echo $arrV[$i]."\n";`
}

都會產生:

1
2

因此會循環原始物件 (都是 $v1),就我們的目標而言,這就是我們想要的。

(使用 php 4.1.3 測試)
匿名
8 年前
回覆 ' elrah [] polyptych [dot] com ',需要注意的是,陣列(或類似的大型資料容器)預設是透過引用傳遞的。因此,這種行為並非副作用。陣列複製和在函式內傳遞陣列總是透過「引用傳遞」來完成...
nay at woodcraftsrus dot com
13 年前
在 PHP 中,如果您想在程式中共享一個物件,您不再需要指標。

<?php
class foo{
protected
$name;
function
__construct($str){
$this->name = $str;
}
function
__toString(){
return
'my name is "'. $this->name .'" and I live in "' . __CLASS__ . '".' . "\n";
}
function
setName($str){
$this->name = $str;
}
}

class
MasterOne{
protected
$foo;
function
__construct($f){
$this->foo = $f;
}
function
__toString(){
return
'Master: ' . __CLASS__ . ' | foo: ' . $this->foo . "\n";
}
function
setFooName($str){
$this->foo->setName( $str );
}
}

class
MasterTwo{
protected
$foo;
function
__construct($f){
$this->foo = $f;
}
function
__toString(){
return
'Master: ' . __CLASS__ . ' | foo: ' . $this->foo . "\n";
}
function
setFooName($str){
$this->foo->setName( $str );
}
}

$bar = new foo('bar');

print(
"\n");
print(
"Only Created \$bar and printing \$bar\n");
print(
$bar );

print(
"\n");
print(
"Now \$baz is referenced to \$bar and printing \$bar and \$baz\n");
$baz =& $bar;
print(
$bar );

print(
"\n");
print(
"Now Creating MasterOne and Two and passing \$bar to both constructors\n");
$m1 = new MasterOne( $bar );
$m2 = new MasterTwo( $bar );
print(
$m1 );
print(
$m2 );

print(
"\n");
print(
"Now changing value of \$bar and printing \$bar and \$baz\n");
$bar->setName('baz');
print(
$bar );
print(
$baz );

print(
"\n");
print(
"Now printing again MasterOne and Two\n");
print(
$m1 );
print(
$m2 );

print(
"\n");
print(
"Now changing MasterTwo's foo name and printing again MasterOne and Two\n");
$m2->setFooName( 'MasterTwo\'s Foo' );
print(
$m1 );
print(
$m2 );

print(
"Also printing \$bar and \$baz\n");
print(
$bar );
print(
$baz );
?>
charles at org oo dot com
17 年前
指向我下面的文章。
當您在迴圈中使用引用時,您需要使用 unset($var)。

例如:
<?php
foreach($var as &$value)
{
...
}
unset(
$value);
?>
Oddant
11 年前
關於陣列引用的範例。
我認為這也應該寫在陣列章節中。
的確,如果您在某種程度上是程式語言的新手,您應該注意陣列是指向位元組向量的指標。

<?php $arr = array(1); ?>
這裡的 $arr 包含一個指向陣列所在位置的引用。
撰寫
<?php echo $arr[0]; ?>
會解除陣列的引用以存取它的第一個元素。

現在您也應該注意的事情(即使您不是程式語言的新手)是 PHP 使用引用來包含陣列的不同值。這是合理的,因為 PHP 陣列元素的類型可以不同。

考慮以下範例:

<?php

$arr
= array(1, 'test');

$point_to_test =& $arr[1];

$new_ref = 'new';

$arr[1] =& $new_ref;

echo
$arr[1]; // 顯示 'new';
echo $point_to_test; // 顯示 'test' ! (仍然指向記憶體中的某個位置)

?>
php.devel at homelinkcs dot com
20 年前
回覆 lars at riisgaardribe dot dk,

當一個變數被複製時,內部會使用參考,直到副本被修改為止。因此,在您的情況下,您根本不應該使用參考,因為它不會節省任何記憶體使用量,反而會增加邏輯錯誤的機率,正如您所發現的。
dovbysh at gmail dot com
17 年前
針對 "php at hood dot id dot au 2007年3月4日 10:56" 這篇文章的解決方案

<?php
$a1
= array('a'=>'a');
$a2 = array('a'=>'b');

foreach (
$a1 as $k=>&$v)
$v = 'x';

echo
$a1['a']; // 會印出 x

unset($GLOBALS['v']);

foreach (
$a2 as $k=>$v)
{}

echo
$a1['a']; // 會印出 x

?>
Amaroq
14 年前
我想我需要更正我上一篇的貼文。

當存在建構子時,我上一篇貼文中提到的奇怪行為就不會發生。我猜測 PHP 把 reftest() 當作建構子來處理(也許是因為它是第一個函式?),並在實例化時運行它。

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
__construct()
{
return
0;
}

public function
reftest()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; //印出 2。
echo $reference->c; //印出 2。
?>
akinaslan at gmail dot com
13 年前
在此範例中,類別名稱與其第一個函式名稱不同,而且沒有建構函式。最終,正如您所猜測的,「a」和「c」相等。因此,如果沒有建構函式,同時類別及其第一個函式名稱相同,「a」和「c」將永遠不會相等。在我看來,只要函式名稱與類別名稱不同,PHP 就不会搜尋任何函式作為建構函式。

<?php
class reftest_new
{
public
$a = 1;
public
$c = 1;

public function
reftest()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest_new();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; // 顯示 2。
echo $reference->c; // 顯示 2。
?>
Amaroq
16 年前
您參考變數的順序很重要。

<?php
$a1
= "One";
$a2 = "Two";
$b1 = "Three";
$b2 = "Four";

$b1 =& $a1;
$a2 =& $b2;

echo
$a1; // 顯示 "One"
echo $b1; // 顯示 "One"

echo $a2; // 顯示 "Four"
echo $b2; // 顯示 "Four"
?>
Drewseph
16 年前
如果您在將變數傳遞給以傳址方式接受變數的函式之前先設定該變數,則在函式內編輯該變數會變得更加困難(如果不是不可能的話)。

範例
<?php
function foo(&$bar) {
$bar = "hello\n";
}

foo($unset);
echo(
$unset);
foo($set = "set\n");
echo(
$set);

?>

輸出
hello
set

這讓我感到困惑,但事實就是如此。
dnhuff at acm dot org
16 年前
回覆 Drewseph,使用 foo($a = 'set'); 其中 $a 是一個參考形式參數。

$a = 'set' 是一個表達式。表達式不能以傳址方式傳遞,你不討厭這樣嗎?我是很討厭。如果你打開 E_NOTICE 錯誤報告,你就會看到相關的錯誤訊息。

解決方法:$a = 'set'; foo($a); 這樣就可以達到你的目的。
firespade at gmail dot com
17 年前
這裡有一個關於引用的小範例。這是我理解引用的最佳方式,希望也能幫助到其他人。

$b = 2;
$a =& $b;
$c = $a;
echo $c;

// 接著... $c = 2
Amaroq
14 年前
在類別中使用引用時,你可以引用 $this-> 變數。

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
reftest()
{
$b =& $this->a;
$b = 2;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; //會印出 2。
echo $reference->c; //會印出 2。
?>

然而,這看起來並非完全可靠。在某些情況下,它可能會出現奇怪的行為。

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
reftest()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}
}

$reference = new reftest();

$reference->reftest();
$reference->reftest2();

echo
$reference->a; //會印出 3。
echo $reference->c; //會印出 2。
?>

在第二個程式碼區塊中,我修改了 reftest(),讓 $b 遞增而不是直接改成 2。結果不知何故,它最後變成了 3 而不是預期的 2。
php at hood dot id dot au
17 年前
我今天在 foreach 中使用引用時發現了一些東西

<?php
$a1
= array('a'=>'a');
$a2 = array('a'=>'b');

foreach (
$a1 as $k=>&$v)
$v = 'x';

echo
$a1['a']; // 會輸出 x

foreach ($a2 as $k=>$v)
{}

echo
$a1['a']; // 會輸出 b (!)
?>

閱讀手冊後,看起來這似乎是預期的行為。但這讓我困惑了好幾天!

(我使用的解決方案是將第二個 foreach 也改成使用參考。)
strata_ranger at hotmail dot com
15 年前
一個有趣且非典型的參考用法:建立任意維度的陣列。

例如,一個函式可以接受來自資料庫的結果集,並根據一欄(或多欄)產生一個多維陣列作為鍵值,這在你希望以階層方式存取結果集,或者即使你只是希望結果以每一列的主鍵/唯一鍵值欄位作為鍵值時,都可能很有用。

<?php
function array_key_by($data, $keys, $dupl = false)
/*
* $data - Multidimensional array to be keyed
* $keys - List containing the index/key(s) to use.
* $dupl - How to handle rows containing the same values. TRUE stores it as an Array, FALSE overwrites the previous row.
*
* Returns a multidimensional array indexed by $keys, or NULL if error.
* The number of dimensions is equal to the number of $keys provided (+1 if $dupl=TRUE).
*/
{
// Sanity check
if (!is_array($data)) return null;

// Allow passing single key as a scalar
if (is_string($keys) or is_integer($keys)) $keys = Array($keys);
elseif (!
is_array($keys)) return null;

// Our output array
$out = Array();

// Loop through each row of our input $data
foreach($data as $cx => $row) if (is_array($row))
{

// Loop through our $keys
foreach($keys as $key)
{
$value = $row[$key];

if (!isset(
$last)) // First $key only
{
if (!isset(
$out[$value])) $out[$value] = Array();
$last =& $out; // Bind $last to $out
}
else
// Second and subsequent $key....
{
if (!isset(
$last[$value])) $last[$value] = Array();
}

// Bind $last to one dimension 'deeper'.
// First lap: was &$out, now &$out[...]
// Second lap: was &$out[...], now &$out[...][...]
// Third lap: was &$out[...][...], now &$out[...][...][...]
// (etc.)
$last =& $last[$value];
}

if (isset(
$last))
{
// At this point, copy the $row into our output array
if ($dupl) $last[$cx] = $row; // Keep previous
else $last = $row; // Overwrite previous
}
unset(
$last); // Break the reference
}
else return
NULL;

// Done
return $out;
}

// A sample result set to test the function with
$data = Array(Array('name' => 'row 1', 'foo' => 'foo_a', 'bar' => 'bar_a', 'baz' => 'baz_a'),
Array(
'name' => 'row 2', 'foo' => 'foo_a', 'bar' => 'bar_a', 'baz' => 'baz_b'),
Array(
'name' => 'row 3', 'foo' => 'foo_a', 'bar' => 'bar_b', 'baz' => 'baz_c'),
Array(
'name' => 'row 4', 'foo' => 'foo_b', 'bar' => 'bar_c', 'baz' => 'baz_d')
);

// First, let's key it by one column (result: two-dimensional array)
print_r(array_key_by($data, 'baz'));

// Or, key it by two columns (result: 3-dimensional array)
print_r(array_key_by($data, Array('baz', 'bar')));

// We could also key it by three columns (result: 4-dimensional array)
print_r(array_key_by($data, Array('baz', 'bar', 'foo')));

?>
joachim at lous dot org
21 年前
所以,要建立一個以參考傳遞的 setter 函式,你需要在參數列表和賦值中都指定參考語法,像這樣:

class foo{
var $bar;
function setBar(&$newBar){
$this->bar =& $newBar;
}
}

如果忘記兩個「&」中的任何一個,在呼叫 setBar 之後,$foo->bar 最終會變成一個副本。
butshuti at smartrwanda dot org
11 年前
這似乎是一個隱藏的行為:當類別的函式名稱與類別名稱相同時,在建立該類別的物件時,它似乎會被隱式地呼叫。
例如,你可以看一下函式「reftest()」的命名:它位於類別「reftest」中。可以透過以下方式測試此行為:

<?php
class reftest
{
public
$a = 1;
public
$c = 1;

public function
reftest1()
{
$b =& $this->a;
$b++;
}

public function
reftest2()
{
$d =& $this->c;
$d++;
}

public function
reftest()
{
echo
"REFTEST() called here!\n";
}
}

$reference = new reftest();
/*你必須注意,以上程式碼也會隱式地呼叫 reference->reftest()*/

$reference->reftest1();
$reference->reftest2();

echo
$reference->a."\n"; //會印出 2,而不是之前注意到的 3。
echo $reference->c."\n"; //印出 2。
?>

以上程式碼的輸出結果為:

REFTEST() called here!
2
2

請注意,reftest() 函式似乎被呼叫了(儘管沒有明確地呼叫它)!
torntech.com 的管理員
11 年前
到目前為止,還沒有討論到的是參考的參考(reference of a reference)。
我需要一個快速且簡便的方法來替不正確的命名建立別名,直到能進行適當的重寫為止。
希望這可以為其他人省下測試的時間,因為在「是/為/不是」的頁面中沒有涵蓋這個部分。
雖然離最佳實務還很遠,但它確實有效。

<?php
$a
= 0;

$b =& $a;
$a =& $b;

$a = 5;
echo
$a . ', ' . $b;
//輸出:5,5

echo ' | ';

$b = 6;
echo
$a . ',' . $b;
//輸出:6,6

echo ' | ';
unset(
$a );
echo
$a . ', ' . $b;

//輸出:, 6

class Product {

public
$id;
private
$productid;

public function
__construct( $id = null ) {
$this->id =& $this->productid;
$this->productid =& $this->id;
$this->id = $id;
}

public function
getProductId() {
return
$this->productid;
}

}

echo
' | ';

$Product = new Product( 1 );
echo
$Product->id . ', ' . $Product->getProductId();
//輸出 1, 1
$Product->id = 2;
echo
' | ';
echo
$Product->id . ', ' . $Product->getProductId();
//輸出 2, 2
$Product->id = null;
echo
' | ';
echo
$Product->id . ', ' . $Product->getProductId();
//輸出 ,
To Top