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)。📖 小节
...$args可变参数:打包参数为数组 / 展开数组为参数- 命名参数(PHP 8):
fn(name: "小明", age: 25),跳过有默认值的参数 &$var引用传递:函数内修改外部变量(慎用)global/$GLOBALS在函数内访问全局变量static变量:函数调用间保持值- 匿名函数 +
use:函数作为变量传递 - 箭头函数
fn() => expr:简洁回调,自动捕获外部变量
📝 作业
- 写一个函数
average(...$numbers): float,接收任意数量的数字返回平均值。 - 用
array_map和箭头函数,将一个字符串数组中的所有字符串转为大写。 - 利用 static 变量写一个
hitCounter()函数,每次调用返回递增的访问次数。