五:PHP 函数——封装可复用的代码

一、回顾与本篇目标

上一篇你系统学习了 PHP 数组——索引数组、关联数组、多维数组,以及几十个常用的数组处理函数。你现在已经能用数组存储复杂的数据结构,用 foreach 遍历处理,用 array_filter 和 array_map 做数据筛选和转换。

但你可能注意到了一个问题:有些逻辑在多个地方重复出现。比如计算平均分、判断等级、格式化输出——这些逻辑如果每次都重新写一遍,代码会变得冗长且难以维护。如果一个地方需要修改(比如等级划分标准变了),你得在所有用到的地方逐一修改。

函数就是解决这个问题的。它把一段逻辑封装起来,起个名字,以后需要的时候直接调用名字就行。函数是编程中最核心的抽象工具——它让你把复杂问题拆成小块,逐个击破。

PHP 的函数机制和 C 语言非常相似(语法几乎一样),同时吸收了 JavaScript 的匿名函数和闭包特性。如果你之前学过 C 或 JavaScript,这一篇会非常顺畅。

本篇的目标:

  1. 学会定义和调用函数
  2. 掌握参数传递的四种方式:按值、按引用、默认参数、可变参数
  3. 理解 PHP 7+ 的类型声明(参数类型和返回类型)
  4. 掌握变量作用域:全局变量、局部变量、静态变量
  5. 学会匿名函数、闭包和箭头函数
  6. 了解常用的内置函数

二、函数的定义和调用

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 的两个作用

  1. 把函数内部计算的结果传递出去
  2. 立即终止函数执行——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" 无法转成整数
?>

可声明的类型intfloatstringboolarraycallable、类名/接口名、selfmixed(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_ 前缀)。处理中文时必须用这些函数,普通的 strlensubstr 是按字节处理的,会把一个中文字符(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,打包成数组)。
  • 类型声明:参数类型(intstringarray 等)和返回类型(: int: void: ?array)。严格模式用 declare(strict_types=1)
  • 变量作用域:局部变量只在函数内可用、全局变量需要 global$GLOBALS 才能访问、静态变量在多次调用间保持值。
  • 匿名函数和闭包:匿名函数用 use 捕获外部变量(默认按值,加 & 按引用)。箭头函数 fn() => 是简洁写法,自动按值捕获外部变量。

函数是编程中最基础的抽象工具。把常用的逻辑封装成函数,你的代码会从”一堆指令”变成”搭积木”——调用现成的函数,组合出更复杂的功能。下一篇,我们学习 PHP 和 MySQL 的交互——用 PDO 连接数据库、执行增删改查、防止 SQL 注入。

下一篇预告

下一篇——《PHP 操作 MySQL——增删改查与 PDO》:连接 MySQL 数据库的两种方式(mysqli 和 PDO)、参数化查询防止 SQL 注入、查询结果的处理、事务的基本用法、一个完整的用户管理实例。

PHP 零基础入门,每周更新。

© 版权声明
THE END
喜欢就支持一下吧
点赞10 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容