通过删除在发生按引用传递时(即函数定义使用 &)在函数调用中包含引用符号的能力,代码的可读性会下降,因为必须查看函数定义才能知道传递的变量是按引用传递的还是按值传递的(即可能被修改)。如果函数调用和函数定义都要求引用符号(即 &),则可读性会提高,并且还会减少代码本身中意外错误的可能性。在 5.4.0 中完全使用致命错误现在迫使每个人使用可读性较低的代码。也就是说,函数只是使用变量,还是可能修改它......现在我们必须找到函数定义并实际查看它才能知道,而以前我们可以立即知道意图。
您可以按引用将变量传递给函数,以便函数可以修改该变量。语法如下
<?php
function foo(&$var)
{
$var++;
}
$a=5;
foo($a);
// $a 在这里为 6
?>
注意: 函数调用中没有引用符号 - 只有在函数定义中才有。仅函数定义就足以正确地按引用传递参数。
以下内容可以按引用传递
foo($a)
从函数返回的引用,例如
<?php
function foo(&$var)
{
$var++;
}
function &bar()
{
$a = 5;
return $a;
}
foo(bar());
?>
不应按引用传递其他表达式,因为结果未定义。例如,以下按引用传递的示例无效
<?php
function foo(&$var)
{
$var++;
}
function bar() // 注意缺少 &
{
$a = 5;
return $a;
}
foo(bar()); // 产生通知
foo($a = 5); // 表达式,而不是变量
foo(5); // 产生致命错误
class Foobar
{
}
foo(new Foobar()) // 从 PHP 7.0.7 开始产生通知
// 注意:只有变量才能按引用传递
?>
通过删除在发生按引用传递时(即函数定义使用 &)在函数调用中包含引用符号的能力,代码的可读性会下降,因为必须查看函数定义才能知道传递的变量是按引用传递的还是按值传递的(即可能被修改)。如果函数调用和函数定义都要求引用符号(即 &),则可读性会提高,并且还会减少代码本身中意外错误的可能性。在 5.4.0 中完全使用致命错误现在迫使每个人使用可读性较低的代码。也就是说,函数只是使用变量,还是可能修改它......现在我们必须找到函数定义并实际查看它才能知道,而以前我们可以立即知道意图。
<?php
// PHP >= 5.6
// 在这里我们使用 'use' 运算符在函数的作用域内创建变量。虽然看起来新创建的变量与函数外部的 '$x' 有关系,但实际上我们在函数内部创建了一个 '$x' 变量,它与函数外部的 '$x' 变量无关。我们谈论的是相同的名字,但内存中的内容位置不同。
$x = 10;
(function() use ($x){
$x = $x*$x;
var_dump($x); // 100
})();
var_dump($x); // 10
// 现在使用引用 (&) 会发生神奇的事情。现在我们实际上正在访问函数作用域外部的 '$y' 变量的内容。我们在函数内部对变量 '$y' 执行的所有操作都将在该函数的作用域外部反映出来。记住这将是函数式范式中的一个不纯函数,因为我们正在通过引用更改变量的值。
$y = 10;
(function() use (&$y){
$y = $y*$y;
var_dump($y); // 100
})();
var_dump($y); // 100
?>
注意 unset() 会销毁引用
$x = 'x';
change( $x );
echo $x; // 输出 "x" 而不是 "q23" ---- 删除 unset() 则输出为 "q23" 而不是 "x"
function change( & $x )
{
unset( $x );
$x = 'q23';
return true;
}
按引用传递的参数可以具有默认值。
您可以使用 func_num_args() 找出变量是否实际上是按引用传递的
<?php
function refault( & $ref = 'Do I have to be calculated?'){
echo 'NUM ARGS: '. func_num_args()."\n";
echo "ORI VALUE: {$ref}\n";
if( func_num_args() > 0 ) $ref = 'Yes, expensive to calculate result: ' . sleep(1);
else $ref = 'No.';
echo "NEW VALUE: {$ref}\n";
}
$result = 'Do I have to be calculated?';
refault( $result );
echo "RESULT: {$result}\n";
// NUM ARGS: 1
// ORI VALUE: Do I have to be calculated?
// NEW VALUE: Yes, expensive to calculate result: 0
// RESULT: Yes, expensive to calculate result: 0
refault();
// NUM ARGS: 0
// ORI VALUE: Do I have to be calculated?
// NEW VALUE: No.
?>
在类内部,按引用传递不存在的数组元素会被添加到数组中,值为 null。与普通函数相比,这会将函数的行为从抛出错误更改为在引用的数组中创建一个新的(null)条目,并使用新的键。
<?php
class foo {
public $arr = ['a' => 'apple', 'b' => 'banana'];
public function normalFunction($key) {
return $this->arr[$key];
}
public function &referenceReturningFunction($key) {
return $this->arr[$key];
}
}
$bar = new foo();
$var = $bar->normalFunction('beer'); //Notice Error. Undefined index beer
$var = &$bar->referenceReturningFunction('beer'); // No error. The value of $bar is now null
var_dump($bar->arr);
/**
[
"a" => "apple",
"b" => "banana",
"beer" => null,
],
*/
?>
这不是“错误” - 框架按设计执行,但这需要仔细思考才能弄清楚发生了什么。PHP7.3
我设计了一个可以轻松传递引用的类。
<?php
#### 问题 1
function problem(&$value)
{
}
problem(1); // 不能通过引用传递
#### 问题 2
class problem2
{
static function __callStatic($name, &$arguments) // 不能通过引用传递参数
{
}
}
?>
我的解决方案 👇
<?php
class Reference
{
function __construct(public mixed &$data)
{
}
/**
* 创建
*
* @param mixed $data
* @return self
*/
static function &create(mixed &$data): self
{
if ($data instanceof self) {
return $data;
}
$r = new self($data);
return $r;
}
/**
* 获取值
*
* @param mixed $reference
* @return mixed
*/
static function &get(mixed &$reference): mixed
{
if ($reference instanceof self) {
return $reference->data;
}
return $reference;
}
}
#### 问题解决 1 ####
function test($value)
{
$value = &Reference::get($value); // 值或引用
$value = "test-$value";
return $value;
}
echo test(1), PHP_EOL; // test-1
$val = 2;
echo test(Reference::create($val)), PHP_EOL; // test-2
echo $val, PHP_EOL; // test-2
#### 问题解决 2 ####
class TestCall
{
static function __callStatic($name, $arguments)
{
$value = &Reference::get($arguments[0]);
$value = "$name-$value";
return $value;
}
}
echo TestCall::test(3), PHP_EOL; // test-3
$val = 4;
echo TestCall::test(Reference::create($val)), PHP_EOL; // test-4
echo $val, PHP_EOL; // test-4
注释指出,在 5.3 系列中,函数变量引用将收到弃用警告,但是当通过 call_user_func 调用函数时,操作将中止,不会出现致命错误。
这不是“错误”,因为它不太可能值得解决,但应在此文档中注明。
<?php
/*
此函数使用“按引用传递”在两个简单变量之间内部交换内容。
一些编程语言内置了这样的交换函数
但是 PHP 似乎缺少这样的函数。 所以,
创建了一个来满足需求。 它只处理
简单、单个变量,而不是数组,但它
仍然是一个非常方便的工具。
此函数实际上不返回值,但
指示变量的内容将在
调用后交换(交换)。
*/
// ------------------------------------------
// 下面是 swap(...) 函数的演示调用。
$a = 123.456;
$b = 'abcDEF';
print "<pre>Define:\na = $a\nb = '$b'</pre>";
swap($a,$b);
print "<pre>After swap(a,b):\na = '$a'\nb = $b</pre>";
// -------------------------------
function swap (&$arg1, &$arg2)
{
// 交换指示变量的内容。
$w=$arg1; $arg1=$arg2; $arg2=$w;
}
?>
对于任何想知道的人,当数组被非引用传递到函数中,然后通过引用传递到另一个函数中而没有写入它时,写时复制行为只是执行了 Right Thing™。 例如
<?php
function do_sort(array $array) : array {
usort($array, function ($a, $b) {
return strnatcasecmp($a['name'], $b['name']);
});
return $array;
}
$data = [
[
'name' => 'one',
], [
'name' => 'two',
], [
'name' => 'three',
], [
'name' => 'four',
],
];
var_dump($data);
do_sort($data); // 不影响 $data 的值
var_dump($data);
$data = do_sort($data);
var_dump($data);
有些人注意到引用参数不能被分配默认值。 实际上是错的,它们可以像其他变量一样被分配值,但不能有“默认引用值”,例如这段代码无法编译
<?php
function use_reference( $someParam, &$param =& $POST )
{
...
}
?>
但这一个可以工作
<?php
function use_reference( $someParam, &$param = null )
?>
所以这里有一个解决方法,为引用参数提供默认值
<?php
$array1 = array ( 'test', 'test2' );
function AddTo( $key, $val, &$array = null)
{
if ( $array == null )
{
$array =& $_POST;
}
$array[ $key ] = $val ;
}
AddTo( "indirect test", "test", $array1 );
AddTo( "indirect POST test", "test" );
echo "Array 1 " ;
print_r ( $array1);
echo "_POST ";
print_r( $_POST );
?>
这段脚本的输出是
Array 1 数组
(
[0] => test
[1] => test2
[indirect test] => test
)
_POST 数组
(
[indirect POST test] => test
)
当然,这意味着你只能将默认引用分配给全局变量或超全局变量。
玩得开心
function set(&$arr) {
$test = 10;
$arr = &$test;
}
$arr = [1, 2, 3];
set( $arr );
var_dump( $arr ); // [1, 2, 3]
如果你用一个新的“地址”更改了引用变量,它最初指向的变量将不会改变。
同意:这种变化会导致代码可读性降低。
此外,它会破坏许多现有的完美工作的代码,这些代码不再可移植,在某些情况下需要进行复杂的修改。
另一个问题是产生的致命错误:如果我想允许用户使用一个甚至不在变量中的值,或者函数调用的返回值,或者使用 call_user_func ... 那么这会导致很多情况使代码在运行时崩溃。
小心使用匿名函数和“use”关键字的引用
如果你使用的是 PHP 5.3 到 < 5.3.10 版本,“use”关键字会破坏引用
<?php
function withRef(&$arg) {
echo 'withRef - BEGIN - '.$arg."\n"; // 1
$func = function() use($arg) { /* do nothing, just declare using $arg */ };
$arg = 2;
echo 'withRef - END - '.$arg."\n"; // 2
}
$arg = 1;
echo 'withRef - BEFORE - '.$arg."\n"; // 1
withRef($arg);
// in PHP 5.3 < 5.3.10 : display 1
// in PHP 5.3 >= 5.3.10 : display 2
echo 'withRef - AFTER - '.$arg."\n";
?>
一个解决方法是在“use”关键字中使用引用变量的副本
<?php
...
$arg2 = $arg;
$func = function() use($arg2) { /* do nothing, just declare using $arg2 */ };
有时我们需要函数来构建或修改数组,这些数组的元素是其他变量(例如数组或对象)的引用。在这个例子中,我写了两个函数“tst”和“tst1”来执行此任务。注意函数的编写方式以及它们的使用方式。
<?php
function tst(&$arr, $r) {
// 参数 '$arr' 被声明为按引用传递,
// 但 '$r' 没有;
// 然而,在函数体中,我们使用了对
// '$r' 参数的引用
array_push($arr, &$r);
// 或者,这也可以是 $arr[] = &$r(在这种情况下)
}
$arr0 = array(); // 一个空数组
$arr1 = array(1,2,3); // 要在 $arr0 中引用的数组
// 注意我们如何调用函数:
tst($arr0, &$arr1); // 我们正在调用时传递对 '$arr1' 的引用!
print_r($arr0); // 仅包含对 $arr1 的引用
array_push($arr0, 5); // 我们向 $arr0 添加另一个元素
array_push($arr1, 18); // 我们也向 $arr1 添加另一个元素
print_r($arr1);
print_r($arr0); // $arr1 中的更改反映在 $arr0 中
// -----------------------------------------
// 一个更简单的方法:
function tst1(&$arr, &$r) {
// 参数 '$arr' 和 '$r" 都被声明为按引用传递,
// 再次,在函数体中,我们使用了对
// '$r' 参数的引用
array_push($arr, &$r);
// 或者,这也可以是 $arr[] = &$r(在这种情况下)
}
$arr0 = array(); // 一个空数组
$arr1 = array(1,2,3); // 要在 $arr0 中引用的数组
// 注意我们如何调用函数:
tst1($arr0, $arr1); // 'tst1' 理解 '$r' 是对 '$arr1' 的引用
echo "-------- 2nd. alternative ------------ <br>\n";
print_r($arr0); // 仅包含对 $arr1 的引用
array_push($arr0, 5); // 我们向 $arr0 添加另一个元素
array_push($arr1, 18);
print_r($arr1);
print_r($arr0); // $arr1 中的更改反映在 $arr0 中
// 这将输出:
// X-Powered-By: PHP/4.1.2
// Content-type: text/html
//
// Array
// (
// [0] => Array
// (
// [0] => 1
// [1] => 2
// [2] => 3
// )
//
// )
// Array
// (
// [0] => 1
// [1] => 2
// [2] => 3
// [3] => 18
// )
// Array
// (
// [0] => Array
// (
// [0] => 1
// [1] => 2
// [2] => 3
// [3] => 18
// )
//
// [1] => 5
// )
// -------- 2nd. alternative ------------
// Array
// (
// [0] => Array
// (
// [0] => 1
// [1] => 2
// [2] => 3
// )
//
// )
// Array
// (
// [0] => 1
// [1] => 2
// [2] => 3
// [3] => 18
// )
// Array
// (
// [0] => Array
// (
// [0] => 1
// [1] => 2
// [2] => 3
// [3] => 18
// )
//
// [1] => 5
// )
?>
在这两种情况下,我们都得到了相同的结果。
我希望这在某种程度上是有用的
塞尔吉奥。
当按引用传递数组的一部分时,PHP 的行为很奇怪,该部分还不存在。
<?php
function func(&$a)
{
// void();
}
$a['one'] =1;
func($a['two']);
?>
var_dump($a) 返回
array(2) {
["one"]=>
int(1)
["two"]=>
NULL
}
... 这似乎并非有意!
只是一个简单的注释...
<?php
$num = 1;
function blah(&$var)
{
$var++;
}
blah($num);
echo $num; #2
?>
<?php
$num = 1;
function blah()
{
$var =& $GLOBALS["num"];
$var++;
}
blah();
echo $num; #2
?>
两段代码的功能相同!第二段代码“解释”了按引用传递参数的工作原理。
tnestved at yahoo dot com 的评论是错误的,因为它纯粹基于感知而不是架构。传递对象的方法不应该关心它是按引用传递还是按值传递,读者也不应该关心。
如果我们谈论可读性和感知,那么接收方法需要显示传入的对象是一个引用,而不是一个对象实例,否则读者会感到困惑,为什么对象没有返回。
良好的函数头可以缓解这种情况下的所有问题。
引用仍然是引用,即使它是一个未按引用传递的数组元素。
一个例子
<?php
$c = 3;
$array = array(
'a' => 1,
'b' => 2,
'c' => &$c
);
var_dump($array);
function func($array) {
$array['a'] = 4;
$array['b'] = 5;
$array['c'] = 6;
var_dump($array);
}
func($array);
var_dump($array);
var_dump($c);
?>
这将输出以下内容
array(3) {
["a"]=>
int(1)
["b"]=>
int(2)
["c"]=>
&int(3)
}
array(3) {
["a"]=>
int(4)
["b"]=>
int(5)
["c"]=>
&int(6)
}
array(3) {
["a"]=>
int(1)
["b"]=>
int(2)
["c"]=>
&int(6)
}
int(6)
您可以使用它允许函数对数组的一部分进行读写访问,同时将其限制为对数组其余部分的只读访问
<?php
$config = [
'ro' => [
// ...
],
'rw' => [
// ...
]
];
$copy = $config;
$copy['rw'] = &$config['rw'];
func($copy);
?>