2024年PHP开发者大会日本站

函数参数和参数

函数参数在函数签名中声明。信息可以通过参数列表传递给函数,参数列表是由逗号分隔的表达式列表。参数从左到右计算,结果在函数实际调用之前(*急切*求值)赋值给函数的参数。

PHP支持按值传递参数(默认值)、按引用传递默认参数值可变长度参数列表命名参数也受支持。

示例 #1 将数组传递给函数

<?php
function takes_array($input)
{
echo
"$input[0] + $input[1] = ", $input[0]+$input[1];
}
?>

从PHP 8.0.0开始,函数参数列表可以包含尾随逗号,该逗号将被忽略。这在参数列表很长或包含很长的变量名的情况下尤其有用,使得垂直列出参数更加方便。

示例 #2 带尾随逗号的函数参数列表

<?php
function takes_many_args(
$first_arg,
$second_arg,
$a_very_long_argument_name,
$arg_with_default = 5,
$again = 'a default string', // 8.0.0之前不允许使用此尾随逗号。
)
{
// ...
}
?>

按引用传递参数

默认情况下,函数参数按值传递(因此,如果函数内参数的值发生更改,则函数外部的值不会更改)。为了允许函数修改其参数,必须按引用传递它们。

为了使函数的参数始终按引用传递,在函数定义中参数名前面加上一个取地址符 (&)。

示例 #3 按引用传递函数参数

<?php
function add_some_extra(&$string)
{
$string .= 'and something extra.';
}
$str = 'This is a string, ';
add_some_extra($str);
echo
$str; // 输出 'This is a string, and something extra.'
?>

将常量表达式作为参数传递给期望按引用传递的参数是错误的。

默认参数值

函数可以使用类似于赋值变量的语法为参数定义默认值。只有在未传递参数的参数时才使用默认值。请注意,传递**null**不会赋值默认值。

示例 #4 函数中默认参数的使用

<?php
function makecoffee($type = "cappuccino")
{
return
"Making a cup of $type.\n";
}
echo
makecoffee();
echo
makecoffee(null);
echo
makecoffee("espresso");
?>

上面的例子将输出

Making a cup of cappuccino.
Making a cup of .
Making a cup of espresso.

默认参数值可以是标量值、数组、特殊类型**null**,以及从PHP 8.1.0开始,使用new ClassName()语法的对象。

示例 #5 使用非标量类型作为默认值

<?php
function makecoffee($types = array("cappuccino"), $coffeeMaker = NULL)
{
$device = is_null($coffeeMaker) ? "hands" : $coffeeMaker;
return
"Making a cup of ".join(", ", $types)." with $device.\n";
}
echo
makecoffee();
echo
makecoffee(array("cappuccino", "lavazza"), "teapot");?>

上面的例子将输出

Making a cup of cappuccino with hands.
Making a cup of cappuccino, lavazza with teapot.

示例 #6 使用对象作为默认值(从PHP 8.1.0开始)

<?php
class DefaultCoffeeMaker {
public function
brew() {
return
"Making coffee.\n";
}
}
class
FancyCoffeeMaker {
public function
brew() {
return
"Crafting a beautiful coffee just for you.\n";
}
}
function
makecoffee($coffeeMaker = new DefaultCoffeeMaker)
{
return
$coffeeMaker->brew();
}
echo
makecoffee();
echo
makecoffee(new FancyCoffeeMaker);
?>

上面的例子将输出

Making coffee.
Crafting a beautiful coffee just for you.

默认值必须是常量表达式,而不是(例如)变量、类成员或函数调用。

请注意,任何可选参数都应指定在任何必需参数之后,否则它们不能从调用中省略。考虑以下示例

示例 #7 默认函数参数的错误用法

<?php
function makeyogurt($container = "bowl", $flavour)
{
return
"Making a $container of $flavour yogurt.\n";
}

echo
makeyogurt("raspberry"); // "raspberry" 是 $container,而不是 $flavour
?>

上面的例子将输出

Fatal error: Uncaught ArgumentCountError: Too few arguments
 to function makeyogurt(), 1 passed in example.php on line 42

现在,将其与以下内容进行比较

示例 #8 默认函数参数的正确用法

<?php
function makeyogurt($flavour, $container = "bowl")
{
return
"正在制作一个 "$container " 的 "$flavour " 酸奶。\n";
}

echo
makeyogurt("raspberry"); // "raspberry" 是 $flavour
?>

上面的例子将输出

Making a bowl of raspberry yogurt.

从 PHP 8.0.0 开始,可以使用命名参数跳过多个可选参数。

示例 #9 正确使用默认函数参数

<?php
function makeyogurt($container = "bowl", $flavour = "raspberry", $style = "Greek")
{
return
"正在制作一个 "$container " 的 "$flavour " "$style " 酸奶。\n";
}

echo
makeyogurt(style: "natural");
?>

上面的例子将输出

Making a bowl of raspberry natural yogurt.

从 PHP 8.0.0 开始,在可选参数之后声明必填参数已被弃用。通常可以通过删除默认值来解决这个问题,因为默认值永远不会被使用。此规则的一个例外是 Type $param = null 形式的参数,其中null 默认值使类型隐式可为空。从 PHP 8.4.0 开始,此用法已弃用,应改用显式可空类型

示例 #10 在必填参数之后声明可选参数

<?php

function foo($a = [], $b) {} // 默认值未使用;从 PHP 8.0.0 开始已弃用
function foo($a, $b) {} // 功能等效,无弃用通知

function bar(A $a = null, $b) {} // 从 PHP 8.1.0 开始,$a 隐式必填
// (因为它在必填参数之前),
// 但隐式可为空 (从 PHP 8.4.0 开始已弃用),
// 因为默认参数值为 null
function bar(?A $a, $b) {} // 推荐用法

?>

注意 从 PHP 7.1.0 开始,省略未指定默认值的参数将抛出 ArgumentCountError;在之前的版本中,它会发出警告。

注意 期望按引用传递参数的参数可以具有默认值。

变长参数列表

PHP 通过使用 ... 令牌支持用户定义函数中的变长参数列表。

参数列表可以包含 ... 令牌,以表示函数接受可变数量的参数。这些参数将作为 数组 传递给给定的变量。

示例 #11 使用 ... 访问变长参数

<?php
function sum(...$numbers) {
$acc = 0;
foreach (
$numbers as $n) {
$acc += $n;
}
return
$acc;
}

echo
sum(1, 2, 3, 4);
?>

上面的例子将输出

10

... 也可用于调用函数,将 数组Traversable 变量或字面量解包到参数列表中。

示例 #12 使用 ... 提供参数

<?php
function add($a, $b) {
return
$a + $b;
}

echo
add(...[1, 2])."\n";

$a = [1, 2];
echo
add(...$a);
?>

上面的例子将输出

3
3

可以在 ... 令牌之前指定普通的 positional 参数。在这种情况下,只有与 positional 参数不匹配的尾随参数才会添加到由 ... 生成的数组中。

也可以在 ... 令牌之前添加类型声明。如果存在此声明,则 ... 捕获的所有参数都必须与该参数类型匹配。

示例 #13 类型声明的变长参数

<?php
function total_intervals($unit, DateInterval ...$intervals) {
$time = 0;
foreach (
$intervals as $interval) {
$time += $interval->$unit;
}
return
$time;
}

$a = new DateInterval('P1D');
$b = new DateInterval('P2D');
echo
total_intervals('d', $a, $b).' 天';

// 这将失败,因为 null 不是 DateInterval 对象。
echo total_intervals('d', null);
?>

上面的例子将输出

3 days
Catchable fatal error: Argument 2 passed to total_intervals() must be an instance of DateInterval, null given, called in - on line 14 and defined in - on line 2

最后,变长参数也可以通过在 ... 前添加一个&符号 (&) 来按引用传递

命名参数

PHP 8.0.0 引入了命名参数作为现有 positional 参数的扩展。命名参数允许根据参数名称而不是参数位置向函数传递参数。这使得参数的含义具有自文档性,使参数的顺序无关紧要,并允许任意跳过默认值。

命名参数通过在值前添加参数名称后跟冒号来传递。允许使用保留关键字作为参数名称。参数名称必须是标识符,不允许动态指定。

示例 #14 命名参数语法

<?php
myFunction
(paramName: $value);
array_foobar(array: $value);

// 不支持。
function_name($variableStoringParamName: $value);
?>

示例 #15 positional 参数与命名参数

<?php
// 使用 positional 参数:
array_fill(0, 100, 50);

// 使用命名参数:
array_fill(start_index: 0, count: 100, value: 50);
?>

传递命名参数的顺序无关紧要。

示例 #16 与上面的示例相同,只是参数顺序不同

<?php
array_fill
(value: 50, count: 100, start_index: 0);
?>

命名参数可以与位置参数结合使用。在这种情况下,命名参数必须放在位置参数之后。也可以只指定函数的一些可选参数,而不管它们的顺序。

示例 #17 命名参数与位置参数的结合使用

<?php
htmlspecialchars
($string, double_encode: false);
// 等同于
htmlspecialchars($string, ENT_QUOTES | ENT_SUBSTITUTE | ENT_HTML401, 'UTF-8', false);
?>

多次将参数传递给同一个命名参数将导致 Error 异常。

示例 #18 多次将参数传递给同一个命名参数时抛出的错误

<?php

function foo($param) { ... }

foo(param: 1, param: 2);
// 错误:命名参数 $param 覆盖了之前的参数

foo(1, param: 2);
// 错误:命名参数 $param 覆盖了之前的参数

?>

从 PHP 8.1.0 开始,可以在解包参数之后使用命名参数。命名参数_不能_覆盖已解包的参数。

示例 #19 解包后使用命名参数

<?php
function foo($a, $b, $c = 3, $d = 4) {
return
$a + $b + $c + $d;
}

var_dump(foo(...[1, 2], d: 40)); // 46
var_dump(foo(...['b' => 2, 'a' => 1], d: 40)); // 46

var_dump(foo(...[1, 2], b: 20)); // 致命错误。命名参数 $b 覆盖了之前的参数
?>
添加笔记

用户贡献笔记 8 条笔记

php at richardneill dot org
9 年前
为了测试引用传递和值传递的性能,我使用了这个脚本。结论如下。

#!/usr/bin/php
<?php
function sum($array,$max){ // 对于引用,请使用:“&$array”
$sum=0;
for (
$i=0; $i<2; $i++){
#$array[$i]++; // 取消注释此行以修改函数内的数组。
$sum += $array[$i];
}
return (
$sum);
}

$max = 1E7 // 1000 万个数据点。
$data = range(0,$max,1);

$start = microtime(true);
for (
$x = 0 ; $x < 100; $x++){
$sum = sum($data, $max);
}
$end = microtime(true);
echo
"Time: ".($end - $start)." s\n";

/* 运行时间:
# 传递方式 修改? 时间
- ------- --------- ----
1 值 否 56 us
2 引用 否 58 us

3 值 是 129 s
4 引用 是 66 us

结论:

1. PHP 已经很聪明地处理了零拷贝/写时复制。除非需要,否则函数调用不会复制数据;只有在写入时才会复制数据。这就是为什么 #1 和 #2 的时间相似,而 #3 比 #4 慢 200 万倍。
[你永远不需要使用 &$array 来要求编译器进行零拷贝优化;它可以自己解决这个问题。]

2. 你确实使用 &$array 来告诉编译器“函数可以在原地覆盖我的参数,我不再需要原始数据了”。当我们需要复制大量内存时,这会对性能产生巨大影响。
(这是在 C 中唯一的方法,数组总是作为指针传递)

3. & 的另一个用途是指定应在何处*返回*数据。(例如,exec() 使用的方式)。
(这是一种类似 C 的传递输出指针的方法,而 PHP 函数通常返回复杂类型或数组中的多个答案)

4. 只有函数定义包含 & 这一点是不方便的。调用者也应该包含它,至少作为语法糖。否则
会导致代码难以阅读:因为阅读函数调用的人不会期望它通过引用传递。目前,
必须使用注释编写按引用传递的函数调用,如下所示:
$sum = sum($data,$max); //警告,$data 通过引用传递,并且可能会被修改。

5. 有时,引用传递可以由调用者选择,而不是函数定义。PHP 不允许这样做,但对于调用者来说,决定将数据作为引用传递是有意义的。即“我已经完成了这个变量,可以覆盖它在内存中的数据了”。
*/
?>
Simmo at 9000 dot 000
2 年前
对于任何刚开始使用 php 或搜索的人,为了理解本页描述的变长参数中的“...标记”
https://php.net/manual/en/functions.arguments.php#functions.variable-arg-list
<?php

func
($a, ...$b)

?>
三个点,或省略号,“...”或点,在其他语言中有时被称为“扩展运算符”。

由于这仅用于函数参数,因此它在 PHP 中可能并非技术上的真正运算符。(至少在 8.1 中是这样?)。

(由于名称“...标记”难以搜索,我希望此注释能帮助到某人)。
LilyWhite
3 年前
值得注意的是,您可以使用函数作为函数参数

<?php
function run($op, $a, $b) {
return
$op($a, $b);
}

$add = function($a, $b) {
return
$a + $b;
};

$mul = function($a, $b) {
return
$a * $b;
};

echo
run($add, 1, 2), "\n";
echo
run($mul, 1, 2);
?>

输出
3
2
Hayley Watson
7 年前
使用 ... 向函数调用提供多个参数的限制少于在函数声明中使用它来声明可变参数时的限制。特别是,如果所有此类用法都在任何位置参数之后,则可以多次使用它来解包参数。

<?php

$array1
= [[1],[2],[3]];
$array2 = [4];
$array3 = [[5],[6],[7]];

$result = array_merge(...$array1); // 正确,结果:$result == [1,2,3];
$result = array_merge($array2, ...$array1); // $result == [4,1,2,3]
$result = array_merge(...$array1, $array2); // 致命错误:参数解包后不能使用位置参数。
$result = array_merge(...$array1, ...$array3); // 正确!$result == [1,2,3,5,6,7]
?>

上面错误情况的正确结果应该是$result==[1,2,3,4],但这在(v7.1.8)版本中还不支持。
gabriel at figdice dot org
8年前
即使不需要通过引用传递,函数参数为对象时,其属性也会被函数修改。

<?php
$x
= new stdClass();
$x->prop = 1;

function
f ( $o ) // 注意没有&
{
$o->prop ++;
}

f($x);

echo
$x->prop; // 输出:2
?>

数组的情况有所不同

<?php
$y
= [ 'prop' => 1 ];

function
g( $a )
{
$a['prop'] ++;
echo
$a['prop']; // 输出:2
}

g($y);

echo
$y['prop']; // 输出:1
?>
boan dot web at outlook dot com
6年前
引用

"如果参数的默认值为 NULL,则声明可以接受 NULL 值。"

但是你可以这样做 (PHP 7.1+)

<?php
function foo(?string $bar) {
//...
}

foo(); // 致命错误
foo(null); // 正确
foo('Hello world'); // 正确
?>
Luna
1年前
当使用命名参数并且只为部分参数添加默认值时,带有默认值的参数必须放在最后,否则PHP会抛出错误。

<?php

function test1($a, $c, $b = 2)
{
return
$a + $b + $c;
}

function
test2($a, $b = 2, $c)
{
return
$a + $b + $c;
}

echo
test1(a: 1, c: 3)."\n"; // 正确
echo test2(a: 1, c: 3)."\n"; // ArgumentCountError: 未传递参数 #2 ($b)

?>

我认为这是因为PHP内部将调用重写为类似test1(1, 3)和test2(1, , 3)的形式。第一个调用有效,但第二个显然无效。
Hayley Watson
7 年前
如果在函数的参数列表中使用 ...,出于显而易见的原因,你只能使用一次。不太明显的是它必须位于最后一个参数;正如手册所说:“你可以在 ... 标记之前指定普通的位置参数。(我的强调)”。

<?php
function variadic($first, ...$most, $last)
{
/*等等*/}

variadic(1, 2, 3, 4, 5);
?>
将导致致命错误,即使看起来正确的方法是将 $first 设置为 1,$most 设置为 [2, 3, 4],$last 设置为 5。
To Top