一、回顾与本篇目标
上一篇你系统学习了 PHP 数组——索引数组、关联数组、多维数组,以及几十个常用的数组处理函数。你现在已经能用数组存储复杂的数据结构,用 foreach 遍历处理,用 array_filter 和 array_map 做数据筛选和转换。
但你可能注意到了一个问题:有些逻辑在多个地方重复出现。比如计算平均分、判断等级、格式化输出——这些逻辑如果每次都重新写一遍,代码会变得冗长且难以维护。如果一个地方需要修改(比如等级划分标准变了),你得在所有用到的地方逐一修改。
函数就是解决这个问题的。它把一段逻辑封装起来,起个名字,以后需要的时候直接调用名字就行。函数是编程中最核心的抽象工具——它让你把复杂问题拆成小块,逐个击破。
PHP 的函数机制和 C 语言非常相似(语法几乎一样),同时吸收了 JavaScript 的匿名函数和闭包特性。如果你之前学过 C 或 JavaScript,这一篇会非常顺畅。
本篇的目标:
- 学会定义和调用函数
- 掌握参数传递的四种方式:按值、按引用、默认参数、可变参数
- 理解 PHP 7+ 的类型声明(参数类型和返回类型)
- 掌握变量作用域:全局变量、局部变量、静态变量
- 学会匿名函数、闭包和箭头函数
- 了解常用的内置函数
二、函数的定义和调用
2.1 基本语法
PHP 中定义函数用 function 关键字,和 C 语言、JavaScript 完全一样:
function 函数名(参数1, 参数2, ...) {
// 函数体
return 返回值; // 可选
}
和 C/JS/Python 的对比:
| 特性 | C | JavaScript | Python | PHP |
|---|---|---|---|---|
| 定义关键字 | 返回类型 | function |
def |
function |
| 代码块 | { } |
{ } |
缩进 + 冒号 | { } |
| 参数类型 | 必须声明 | 不需要 | 不需要(可加 type hint) | 可选(PHP 7+ 支持类型声明) |
2.2 最简单的函数
<?php
// 定义一个函数:打印分隔线
function printLine() {
echo "============================<br>";
}
// 调用函数
echo "第一段内容<br>";
printLine();
echo "第二段内容<br>";
printLine();
?>
关键规则:
- 函数名不区分大小写——
printLine()和printline()调用的是同一个函数。但强烈建议始终使用定义时的确切大小写,保持代码一致性。 - 函数可以在定义之前调用——PHP 会先解析整个文件,把所有函数定义登记下来,然后再执行代码。这和 C 语言不同(C 需要函数原型),和 JavaScript 类似(函数提升)。
<?php
// 在定义之前调用——没问题!
sayHello();
function sayHello() {
echo "你好!";
}
?>
不过,如果函数定义是条件性的(比如在 if 语句内部),那就必须先定义再调用:
<?php
$debug = true;
if ($debug) {
function debugLog($msg) {
echo "[DEBUG] {$msg}<br>";
}
}
debugLog("程序启动"); // 如果 $debug 是 false,这里会报错——函数未定义
?>
2.3 带参数和返回值的函数
<?php
// 参数:接收外部传入的数据
// 返回值:把计算结果交还给调用者
function add($a, $b) {
$result = $a + $b;
return $result; // 把结果返回
}
$sum = add(10, 20);
echo "10 + 20 = {$sum}<br>"; // 30
// 也可以直接使用返回值
echo "5 + 8 = " . add(5, 8) . "<br>"; // 13
?>
return 的两个作用:
- 把函数内部计算的结果传递出去。
- 立即终止函数执行——return 后面的代码不会运行。
<?php
function checkAge($age) {
if ($age < 18) {
return "未成年"; // 函数在这里结束
}
return "已成年"; // 只有 age >= 18 才会执行到这里
}
echo checkAge(15); // 未成年
echo checkAge(25); // 已成年
?>
如果函数没有 return 语句,或者 return 后面没有值,函数返回 NULL。
三、函数参数详解
3.1 按值传递(默认)
PHP 默认按值传递参数——函数内部拿到的是原始变量的副本,修改副本不影响原始变量:
<?php
function doubleValue($num) {
$num = $num * 2;
echo "函数内部:{$num}<br>"; // 20
}
$original = 10;
doubleValue($original);
echo "函数外部:{$original}<br>"; // 10 —— 没有变!
?>
这和 C 语言的传值机制完全一样。PHP 中对象类型例外——对象默认按引用传递(后面面向对象篇会详讲)。
3.2 按引用传递
在参数前加 & 符号,函数内部对参数的修改会直接影响原始变量:
<?php
function doubleValue(&$num) { // 注意 & 符号
$num = $num * 2;
}
$original = 10;
doubleValue($original);
echo $original; // 20 —— 真的变了!
?>
什么时候用引用传递?
- 函数需要修改传入的变量本身(比如 swap 交换两个变量的值)。
- 传入的是大数组,不想复制一份浪费内存(不过 PHP 使用写时复制机制,通常不需要手动优化这个)。
- 需要让函数返回多个值——通过引用参数把额外的结果”传出去”。
<?php
// 通过引用参数返回额外的信息
function divide($a, $b, &$remainder) {
$remainder = $a % $b; // 把余数存到引用参数中
return intdiv($a, $b); // 返回商
}
$remainder = 0;
$quotient = divide(10, 3, $remainder);
echo "商:{$quotient},余数:{$remainder}"; // 商:3,余数:1
?>
3.3 默认参数
给参数设置默认值,调用时如果不传,就使用默认值:
<?php
function greet($name, $greeting = "你好") {
echo "{$greeting},{$name}!<br>";
}
greet("张三"); // 你好,张三!
greet("李四", "早上好"); // 早上好,李四!
?>
规则:有默认值的参数必须放在没有默认值的参数之后。
<?php
// 正确:默认参数在后面
function createUser($name, $age = 18, $city = "未知") { }
// 错误:默认参数不能在前
// function createUser($name = "匿名", $age) { } // 报错!
?>
默认参数的陷阱:默认值在函数定义时计算一次,不是每次调用时重新计算。如果默认值是可变的(比如当前时间),需要特别注意:
<?php
// 有问题的写法
function logWithTime($msg, $time = date("H:i:s")) {
echo "[{$time}] {$msg}<br>";
}
logWithTime("第一条"); // [14:30:00] 第一条
sleep(2);
logWithTime("第二条"); // [14:30:00] 第二条 —— 时间没变!因为默认值在定义时就固定了
// 正确的写法
function logWithTimeFixed($msg, $time = null) {
if ($time === null) {
$time = date("H:i:s"); // 每次调用时才获取当前时间
}
echo "[{$time}] {$msg}<br>";
}
?>
3.4 可变参数
在参数前加 ...(三个点号),表示可以接收任意数量的参数。这些参数被打包成一个数组:
<?php
function sum(...$numbers) {
// $numbers 是一个数组,包含所有传入的参数
$total = 0;
foreach ($numbers as $num) {
$total += $num;
}
return $total;
}
echo sum(10, 20); // 30
echo sum(10, 20, 30, 40); // 100
echo sum(); // 0 —— 没有参数时是空数组
?>
可变参数也可以和其他参数组合,但必须放在最后:
<?php
function calculate($operator, ...$numbers) {
if (count($numbers) === 0) return 0;
switch ($operator) {
case '+': return array_sum($numbers);
case '*': return array_product($numbers);
case 'max': return max($numbers);
case 'avg': return array_sum($numbers) / count($numbers);
default: return null;
}
}
echo calculate('+', 1, 2, 3, 4, 5); // 15
echo calculate('avg', 80, 90, 100); // 90
?>
反过来,也可以用 ... 把一个数组展开成多个参数传给函数:
<?php
$nums = [10, 20, 30, 40, 50];
echo sum(...$nums); // 150 —— 把数组展开为 5 个独立参数传给 sum
?>
3.5 参数类型声明(PHP 7+)
PHP 7 开始,可以在参数前声明期望的类型。如果传入的类型不匹配,PHP 会尝试自动转换;如果无法转换,则抛出 TypeError:
<?php
function add(int $a, int $b): int {
return $a + $b;
}
echo add(10, 20); // 30 —— 正常
echo add("10", "20"); // 30 —— 字符串自动转整数
// echo add("abc", 20); // TypeError —— "abc" 无法转成整数
?>
可声明的类型:int、float、string、bool、array、callable、类名/接口名、self、mixed(PHP 8.0+)。
默认情况下,PHP 会做强制类型转换(比如把 "10" 转成 10)。如果要禁止这种隐式转换,可以在文件开头声明严格模式:
<?php
declare(strict_types=1); // 必须写在文件最顶部
function add(int $a, int $b): int {
return $a + $b;
}
// add("10", 20); // 严格模式下,这行会报 TypeError!必须传 int 类型
echo add(10, 20); // 30 —— 正确
?>
严格模式只影响当前文件,不会影响其他文件的函数调用。
3.6 返回类型声明
在参数列表后面加 : 类型,声明函数的返回值类型:
<?php
function divide(int $a, int $b): float {
return $a / $b;
}
var_dump(divide(10, 3)); // float(3.3333333333333)
?>
如果函数可能没有返回值(比如出错时),可以用 nullable 类型——在类型前加问号:
<?php
function findUser(int $id): ?array {
if ($id <= 0) {
return null; // 可以返回 null
}
return ["id" => $id, "name" => "张三"]; // 也可以返回数组
}
$user = findUser(-1);
var_dump($user); // NULL
?>
void 类型:声明函数没有返回值。如果函数有 return 语句,return 后面不能有任何值:
<?php
function logMessage(string $msg): void {
echo "[日志] {$msg}<br>";
// return; // 可以写 return,但不能带值
}
?>
四、变量作用域
4.1 局部变量
函数内部定义的变量是局部变量,只能在函数内部访问。函数外部访问不到:
<?php
function myFunction() {
$message = "我是局部变量";
echo $message; // 函数内部可以访问
}
myFunction();
// echo $message; // 报错!未定义变量 $message
?>
4.2 全局变量
在所有函数外部定义的变量是全局变量。但函数内部不能直接访问全局变量——需要用 global 关键字声明,或者用 $GLOBALS 超全局数组:
<?php
$counter = 0; // 全局变量
function increment() {
global $counter; // 声明要使用全局变量 $counter
$counter++;
}
function incrementV2() {
$GLOBALS['counter']++; // 另一种访问全局变量的方式
}
increment();
increment();
incrementV2();
echo $counter; // 3
?>
全局变量应尽量少用。它们让函数之间产生隐式的依赖关系——任何函数都可能修改全局状态,出 bug 时很难定位是谁改的。能用参数传递解决的问题,就不要用全局变量。
4.3 静态变量
在函数内部的变量前加 static 关键字,这个变量在函数多次调用之间保持其值。普通局部变量每次调用都重新创建,静态变量只在第一次调用时初始化:
<?php
function counter() {
static $count = 0; // 只在第一次调用时执行
$count++;
echo "调用次数:{$count}<br>";
}
counter(); // 调用次数:1
counter(); // 调用次数:2
counter(); // 调用次数:3
?>
静态变量的典型用途:
- 计数器:记录函数被调用了多少次。
- 缓存:在函数内部缓存计算结果,避免重复计算。
- 单例模式:确保某个对象只被创建一次。
<?php
// 用静态变量做缓存:只计算一次斐波那契数列
function fibonacci(int $n): int {
static $cache = []; // 缓存已计算的值
if (isset($cache[$n])) {
return $cache[$n]; // 直接从缓存返回
}
if ($n <= 1) {
$result = $n;
} else {
$result = fibonacci($n - 1) + fibonacci($n - 2);
}
$cache[$n] = $result;
return $result;
}
echo fibonacci(40); // 有缓存后快很多
?>
五、匿名函数、闭包和箭头函数
5.1 匿名函数
不需要给函数起名字,直接赋值给变量,或者作为参数传递给其他函数:
<?php
// 把匿名函数赋值给变量
$greet = function($name) {
echo "你好,{$name}!<br>";
};
$greet("张三"); // 你好,张三!
// 匿名函数作为回调参数
$nums = [1, 2, 3, 4, 5];
$doubled = array_map(function($n) {
return $n * 2;
}, $nums);
print_r($doubled); // [2, 4, 6, 8, 10]
?>
这和 JavaScript 的匿名函数用法几乎完全一样。
5.2 闭包:用 use 捕获外部变量
匿名函数默认不能访问外部作用域的变量。如果需要访问,用 use 关键字把外部变量”捕获”进来:
<?php
$prefix = "用户:";
// 用 use 捕获外部变量 $prefix
$greetWithPrefix = function($name) use ($prefix) {
echo "{$prefix}{$name}<br>";
};
$greetWithPrefix("张三"); // 用户:张三
// 注意:use 捕获的是变量的值,不是引用
$prefix = "管理员:";
$greetWithPrefix("李四"); // 用户:李四 —— 还是之前的 $prefix 值
?>
如果需要捕获引用(让闭包内部能看到外部变量的最新值),在 use 中的变量前加 &:
<?php
$count = 0;
$increment = function() use (&$count) { // 按引用捕获
$count++;
};
$increment();
$increment();
echo $count; // 2
?>
这种按引用捕获的方式,可以用来创建简单的计数器、累加器等。
5.3 箭头函数(PHP 7.4+)
箭头函数是匿名函数的更简洁写法,用 fn 关键字代替 function。它自动捕获外部变量(按值),不需要写 use:
<?php
$factor = 10;
// 箭头函数:自动捕获外部变量
$nums = [1, 2, 3, 4, 5];
$scaled = array_map(fn($n) => $n * $factor, $nums);
print_r($scaled); // [10, 20, 30, 40, 50]
?>
箭头函数的特点:
- 只能包含一个表达式,表达式的值自动作为返回值。
- 自动按值捕获外部变量(不需要
use)。 - 不能修改外部变量的值(因为按值捕获,不是引用)。
<?php
// 箭头函数 vs 匿名函数
// 匿名函数写法:
$sum = array_reduce($nums, function($carry, $item) {
return $carry + $item;
}, 0);
// 箭头函数写法(简洁很多):
$sum = array_reduce($nums, fn($carry, $item) => $carry + $item, 0);
?>
六、常用内置函数一览
PHP 内置了超过 1000 个函数。以下是最常用的按类别整理:
| 类别 | 函数 | 作用 |
|---|---|---|
| 字符串 | strlen($str) |
字符串长度 |
strpos($haystack, $needle) |
查找子串位置 | |
substr($str, $start, $length) |
截取子串 | |
str_replace($search, $replace, $str) |
替换子串 | |
| 数组 | count($arr) |
数组元素个数 |
in_array($val, $arr) |
检查值是否存在 | |
array_merge($a, $b) |
合并数组 | |
| 数学 | round($num, $precision) |
四舍五入 |
rand($min, $max) |
生成随机整数 | |
intdiv($a, $b) |
整数除法 | |
| 日期时间 | date("Y-m-d H:i:s") |
格式化当前日期时间 |
time() |
当前 Unix 时间戳 | |
| 文件 | file_get_contents($path) |
读取整个文件为字符串 |
file_put_contents($path, $data) |
写入数据到文件 |
七、综合演示:一个实用工具函数库
<?php
/**
* 实用工具函数库
*/
// 1. 安全的除法(除数为 0 时返回默认值)
function safeDivide(float $a, float $b, float $default = 0.0): float {
return $b != 0 ? $a / $b : $default;
}
// 2. 截断文本并添加省略号
function truncateText(string $text, int $maxLength = 100, string $suffix = "..."): string {
if (mb_strlen($text) <= $maxLength) {
return $text;
}
return mb_substr($text, 0, $maxLength) . $suffix;
}
// 3. 格式化金额(分转元,带千位分隔符)
function formatMoney(int $cents, string $currency = "¥"): string {
$yuan = $cents / 100;
return $currency . number_format($yuan, 2);
}
// 4. 生成随机字符串
function randomString(int $length = 16, string $chars = null): string {
if ($chars === null) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
}
$result = '';
$maxIndex = strlen($chars) - 1;
for ($i = 0; $i < $length; $i++) {
$result .= $chars[rand(0, $maxIndex)];
}
return $result;
}
// 5. 获取指定数量的数组随机元素
function randomElements(array $arr, int $count = 1): array {
if ($count >= count($arr)) {
shuffle($arr);
return $arr;
}
$keys = array_rand($arr, $count);
if (!is_array($keys)) {
$keys = [$keys];
}
return array_intersect_key($arr, array_flip($keys));
}
// 测试
echo "safeDivide(10, 3) = " . safeDivide(10, 3) . "<br>"; // 3.333...
echo "safeDivide(10, 0) = " . safeDivide(10, 0) . "<br>"; // 0
echo "truncateText: " . truncateText("这是一段很长的文本需要被截断", 10) . "<br>";
echo "formatMoney: " . formatMoney(1999) . "<br>"; // ¥19.99
echo "randomString: " . randomString(8) . "<br>";
$fruits = ["苹果", "香蕉", "橘子", "葡萄", "草莓", "西瓜"];
$picked = randomElements($fruits, 2);
echo "随机水果:<br>";
foreach ($picked as $fruit) {
echo " - {$fruit}<br>";
}
?>
代码解析:
mb_strlen()和mb_substr():多字节字符串函数(mb_前缀)。处理中文时必须用这些函数,普通的strlen和substr是按字节处理的,会把一个中文字符(3 字节)算成 3 个字符。number_format():格式化数字,自动添加千位分隔符。array_rand():从数组中随机取一个或多个键。取多个时返回键的数组。array_intersect_key()+array_flip():根据键从原数组中取出对应的元素。
八、本篇动手练习
练习 1:写一个计算器函数
新建 practice5-1.php,写一个函数 calculate(float $a, float $b, string $op): float,根据 $op 的值(+、-、*、/)返回计算结果。除数为 0 时抛出异常或返回错误信息。
练习 2:写一个密码强度检测函数
新建 practice5-2.php,写一个函数 checkPasswordStrength(string $password): array,返回密码的强度评分(0-100)和评估等级。评分规则:长度(最多 25 分)、包含小写字母(20 分)、包含大写字母(20 分)、包含数字(20 分)、包含特殊字符(15 分)。
练习 3:闭包练习
新建 practice5-3.php,写一个函数 createMultiplier($factor),返回一个闭包(匿名函数),这个闭包接收一个数字参数,返回该数字乘以 $factor 的结果。测试创建几个不同的 multiplier 并调用。
练习 4:缓存函数
新建 practice5-4.php,写一个函数 expensiveCalculation(int $n): int(模拟耗时计算,比如递归计算阶乘),用静态变量缓存已计算的结果,避免重复计算。
九、本篇小结
这一篇你系统学习了 PHP 函数的核心知识:
- 函数的定义和调用:
function 函数名(参数) { }。函数可以在定义之前调用(PHP 预解析)。 - 参数传递:按值传递(默认,副本)、按引用传递(
&,修改原变量)、默认参数(有默认值,必须放最后)、可变参数(...$args,打包成数组)。 - 类型声明:参数类型(
int、string、array等)和返回类型(: int、: void、: ?array)。严格模式用declare(strict_types=1)。 - 变量作用域:局部变量只在函数内可用、全局变量需要
global或$GLOBALS才能访问、静态变量在多次调用间保持值。 - 匿名函数和闭包:匿名函数用
use捕获外部变量(默认按值,加&按引用)。箭头函数fn() =>是简洁写法,自动按值捕获外部变量。
函数是编程中最基础的抽象工具。把常用的逻辑封装成函数,你的代码会从”一堆指令”变成”搭积木”——调用现成的函数,组合出更复杂的功能。下一篇,我们学习 PHP 和 MySQL 的交互——用 PDO 连接数据库、执行增删改查、防止 SQL 注入。
下一篇预告
下一篇——《PHP 操作 MySQL——增删改查与 PDO》:连接 MySQL 数据库的两种方式(mysqli 和 PDO)、参数化查询防止 SQL 注入、查询结果的处理、事务的基本用法、一个完整的用户管理实例。
PHP 零基础入门,每周更新。













暂无评论内容