PHP: 函数进阶

函数基础已经掌握了。这节课让你写出更灵活、更现代、更 PHP 8 风格的函数——可变参数、命名参数、箭头函数,这些是 PHP 持续进化的成果。

1. 可变参数(...$args)

不知道会传几个参数?用 ... 接收所有参数为数组:

PHP
<?php
function sum(...$numbers): int {
    return array_sum($numbers);
}

echo sum(1, 2);           // 3
echo sum(1, 2, 3, 4, 5);  // 15
echo sum();               // 0

// 也可以前面有固定参数,后面接收剩余
function sendMessage(string $to, string ...$messages): void {
    foreach ($messages as $msg) {
        echo "发送给{$to}:{$msg}<br>";
    }
}

sendMessage("小明", "早上好", "午饭吃了吗", "晚安");
// 发送给小明:早上好
// 发送给小明:午饭吃了吗
// 发送给小明:晚安
?>

▶ 示例:... 展开数组为参数

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

$nums = [1, 2, 3];
echo add(...$nums);  // 6(数组展开为3个参数)

// 常用于 array_push 等函数
$stack = ["a", "b"];
array_push($stack, ...["c", "d", "e"]);
print_r($stack);  // ["a", "b", "c", "d", "e"]
?>
▶ 试一试

... 是双向的——定义函数时打包参数为数组,调用函数时展开数组为参数。


2. 命名参数(PHP 8.0)

不用记住参数顺序,按名字传参:

▶ 示例:命名参数

PHP
<?php
function createUser(
    string $name,
    int $age,
    string $email = "",
    bool $isAdmin = false
): array {
    return compact('name', 'age', 'email', 'isAdmin');
}

// 传统方式:必须按顺序传
$u1 = createUser("小明", 25, "xiaoming@example.com", true);

// 命名参数:顺序随便,跳过有默认值的参数
$u2 = createUser(
    name: "小红",
    age: 22,
    isAdmin: true
    // email 用默认值
);

// 函数参数很多时,命名参数可读性巨大提升
setcookie(
    name: "theme",
    value: "dark",
    expires_or_options: time() + 3600,
    httponly: true
);
?>
▶ 试一试
💡 提示: 命名参数特别适合这些场景:(1) 参数多且大多有默认值;(2) 只传后几个参数;(3) 让代码更自文档化——createUser(name: "小明", age: 25) 一看就懂。


3. 引用传递(&$var)

默认情况下,PHP 函数是按值传递——函数内修改参数不影响外部变量。加 & 可以按引用传递:

PHP
<?php
// 按值传递(默认):函数内修改不影响外部
function addTen(int $n): void {
    $n += 10;
}
$x = 5;
addTen($x);
echo $x;  // 5(没变)

// 按引用传递:函数内修改直接影响外部
function addTenRef(int &$n): void {
    $n += 10;
}
addTenRef($x);
echo $x;  // 15(变了!)
?>
按值传递 按引用传递
写法 function f($x) function f(&$x)
函数内修改 不影响外部 影响外部变量
适用场景 大多数情况 需要"返回"多个值
⚠️ 注意: 引用传递会让代码行为变得"不透明"——调用者不知道自己的变量被改了。用返回值替代引用传递通常更清晰。只有在数组排序(sort(&$arr))这类必须改原数组的场景才用引用。


4. 变量作用域

PHP 中,函数内部和外部是两个世界:

PHP
<?php
$globalVar = "我在外面";

function test(): void {
    // echo $globalVar;  // Warning: Undefined variable
    // 函数内部无法直接访问外部变量

    $localVar = "我在里面";
    echo $localVar;  // OK
}

test();
// echo $localVar;  // Warning: 也访问不到函数内部的变量
?>

(1) 用 global 关键字打破隔离

PHP
<?php
$counter = 0;

function increment(): void {
    global $counter;  // 声明我要用全局变量
    $counter++;
}

increment();
increment();
echo $counter;  // 2
?>

(2) $GLOBALS 超全局数组

PHP
<?php
$name = "小明";

function showName(): void {
    echo $GLOBALS['name'];  // 小明
}
showName();
?>
💡 提示: global 关键字是"必要之恶"——知道它的存在,但尽量不用。全局变量让代码难以测试和维护。大多数情况应该把数据作为参数传入函数。


5. static 静态变量

函数内的 static 变量在多次调用之间保持值

PHP
<?php
function getNextId(): int {
    static $id = 0;
    $id++;
    return $id;
}

echo getNextId();  // 1
echo getNextId();  // 2
echo getNextId();  // 3

// static 变量只在第一次调用时初始化
// 后续调用保持上次的值
?>
💡 提示: 静态变量适合缓存计算结果(避免重复计算)或生成唯一 ID。但不要用它替代类的属性——那是面向对象的活。


6. 匿名函数(Closure)

函数不必有名字——可以赋给变量、当参数传递:

PHP
<?php
// 匿名函数赋值给变量
$greet = function(string $name): string {
    return "你好,{$name}!";
};

echo $greet("小明");  // 你好,小明!

// 作为参数传给其他函数(回调)
$numbers = [1, 2, 3, 4, 5];
$doubled = array_map(function($n) {
    return $n * 2;
}, $numbers);
print_r($doubled);  // [2, 4, 6, 8, 10]
?>

(1) 匿名函数的 use 关键字

匿名函数如果需要外部变量,要用 use 导入:

PHP
<?php
$prefix = "用户";

// ❌ 错误:$prefix 在匿名函数内部不可见
// $greet = function($name) {
//     return "{$prefix}:{$name}";
// };

// ✅ 用 use 导入外部变量
$greet = function($name) use ($prefix): string {
    return "{$prefix}:{$name}";
};

echo $greet("小明");  // 用户:小明

// 按引用导入(可以修改外部变量)
$count = 0;
$inc = function() use (&$count): void {
    $count++;
};
$inc(); $inc();
echo $count;  // 2
?>

7. 箭头函数(PHP 7.4+)

箭头函数 fn() => 是单行匿名函数的极简版,自动捕获外部变量(不用写 use):

PHP
<?php
$factor = 3;

// 匿名函数写法
$anon = function($n) use ($factor) {
    return $n * $factor;
};

// 箭头函数写法(自动捕获 $factor)
$arrow = fn($n) => $n * $factor;

echo $anon(5);   // 15
echo $arrow(5);  // 15

// array_map 中的黄金搭档
$nums = [1, 2, 3, 4, 5];
$tripled = array_map(fn($n) => $n * $factor, $nums);
print_r($tripled);  // [3, 6, 9, 12, 15]
?>
💡 提示: 箭头函数只能包含一个表达式(不能用多行代码),自动按值捕获外部变量。适合简单的 map/filter/usort 回调。


8. 回调函数与 callable 类型

PHP 可以用函数名、匿名函数、对象方法作为回调:

PHP
<?php
// 方式 1:函数名字符串
function double(int $n): int {
    return $n * 2;
}
$result = array_map('double', [1, 2, 3]);
// 不是 'double'(带引号),是函数名作为字符串传给 array_map

// 方式 2:匿名函数
$result = array_map(fn($n) => $n * 3, [1, 2, 3]);

// 方式 3:callable 类型声明
function apply(callable $fn, array $data): array {
    return array_map($fn, $data);
}

$result = apply('double', [1, 2, 3]);          // ✅
$result = apply(fn($n) => $n * 2, [1, 2, 3]);   // ✅
// apply(123, [1, 2, 3]);  // TypeError!
?>

❓ 常见问题

Q ... 在 PHP 中到底有几个含义?
A 两个。(1) 函数定义时,...$args 把剩余参数打包成数组;(2) 函数调用时,...$arr 把数组展开为参数列表。sum(...$nums) 是展开,function sum(...$nums) 是打包。
Q 命名参数会打乱顺序吗?可以混合使用按位置和按名称吗?
A 可以混合。规则是:命名参数必须放在按位置传递的参数之后。foo(1, c: 3, b: 2) 合法,foo(a: 1, 2, 3) 不合法。
Q 什么时候用匿名函数,什么时候用箭头函数?
A 单行表达式用箭头函数 fn($x) => $x * 2,多行逻辑用匿名函数 function($x) { ... }。箭头函数更简洁且自动捕获外部变量(不用 use)。

📖 小节

📝 作业

  1. 写一个函数 average(...$numbers): float,接收任意数量的数字返回平均值。
  2. array_map 和箭头函数,将一个字符串数组中的所有字符串转为大写。
  3. 利用 static 变量写一个 hitCounter() 函数,每次调用返回递增的访问次数。
Web-Tutorial.com

Web-Tutorial 技术团队

由多位开发者共同维护的编程教程平台。每篇教程由对应领域的开发者编写和审核,确保内容准确可靠。如发现任何问题,欢迎向我们反馈。

100%

🙏 帮我们做得更好

我们是刚上线的编程教程站,几个人的小团队,精力有限。页面虽经检查,难免还有疏漏——链接失效、排版错乱、内容有误、语言生硬……

如果您发现了,麻烦告诉我们,我们会在收到反馈后第一时间进行修复,再次感谢您的光临 🙏