PHP 练习题(15 题)

PHP 学了语法之后,必须动手写才能融会贯通。这一页整理 15 道精选练习题,按难度分三级——基础 6 题(变量 / 运算 / 控制流 / 函数)、进阶 5 题(数组 / 字符串)、实战 4 题(Web 综合应用)。每题都包含题目描述、提示、可折叠的参考答案。

使用建议:先别急着展开答案。读题后自己打开本地 PHP 环境(或 3v4l 在线 PHP)试着写,卡住了再看提示,实在写不出再对照参考答案。答案不止一种,你的写法可能比参考更好——能跑通、可读性强就是好代码。

基础(6 题):变量 / 运算 / 控制流 / 函数

这一组覆盖 PHP 的基础语言元素,目标是用最短的代码解决一个小问题,训练"把思路翻译成代码"的能力。

第 1 题:翻转字符串

题目:写一个函数 reverse_string($s),把传入的字符串翻转过来。比如 "hello" 翻转成 "olleh"。

提示:PHP 内置 strrev() 函数最直接;也可以手写循环从后往前按下标 $s[$i] 取字符拼接。

参考答案
<?php
// 方法 1:内置 strrev 最简洁
function reverse_string($s) {
    return strrev($s);
}

// 方法 2:手动循环拼接(理解原理)
function reverse_manual($s) {
    $out = '';
    for ($i = strlen($s) - 1; $i >= 0; $i--) {
        $out .= $s[$i];          // PHP 字符串可按下标取字符
    }
    return $out;
}

echo reverse_string("hello") . PHP_EOL;    // olleh
echo reverse_string("PHP") . PHP_EOL;      // PHP
echo reverse_manual("world") . PHP_EOL;    // dlrow
// 输出: olleh / PHP / dlrow

第 2 题:判断素数

题目:写一个函数 is_prime($n),判断正整数 $n 是否为素数(只能被 1 和自己整除)。

提示:小于 2 的都不是素数;只需检查从 2 到 sqrt($n) 的整除性——超过平方根的因子必然配对一个更小的、已经检查过的因子。

参考答案
<?php
function is_prime($n) {
    if ($n < 2) return false;
    // 只需检查到 sqrt($n),超过的因子必然配对一个更小的、已检查过的
    for ($i = 2; $i <= (int) sqrt($n); $i++) {
        if ($n % $i === 0) return false;
    }
    return true;
}

var_dump(is_prime(2));   // bool(true)
var_dump(is_prime(7));   // bool(true)
var_dump(is_prime(10));  // bool(false)
var_dump(is_prime(1));   // bool(false)
// 输出: true / true / false / false

第 3 题:FizzBuzz

题目:用 echo 打印 1 到 15。3 的倍数打印 Fizz,5 的倍数打印 Buzz,同时是 3 和 5 的倍数打印 FizzBuzz,其余打印数字本身。

提示:if / elseif / else 的判断顺序很关键——必须先判断"同时是 3 和 5 的倍数"(即 15 的倍数),否则会被前面的分支截胡。

参考答案
<?php
// 经典题:3 的倍数 Fizz,5 的倍数 Buzz,15 的倍数 FizzBuzz,其余数字本身
for ($i = 1; $i <= 15; $i++) {
    if ($i % 15 === 0) {           // 必须先判断 15,否则会被 3 或 5 截胡
        echo "FizzBuzz\n";
    } elseif ($i % 3 === 0) {
        echo "Fizz\n";
    } elseif ($i % 5 === 0) {
        echo "Buzz\n";
    } else {
        echo $i . "\n";
    }
}
// 输出:
// 1
// 2
// Fizz
// 4
// Buzz
// Fizz
// 7
// 8
// Fizz
// Buzz
// 11
// Fizz
// 13
// 14
// FizzBuzz

第 4 题:数组最大值

题目:写一个函数 find_max($arr),返回数组中的最大值;空数组返回 null。

提示:PHP 内置 max($arr) 最直接;也可手动遍历维护一个"擂主"变量,逐个比较更新。

参考答案
<?php
// 方法 1:直接用内置 max()(推荐)
function find_max($arr) {
    if (empty($arr)) return null;
    return max($arr);
}

// 方法 2:手动遍历(理解原理)
function find_max_manual($arr) {
    if (empty($arr)) return null;
    $m = $arr[0];
    for ($i = 1; $i < count($arr); $i++) {
        if ($arr[$i] > $m) $m = $arr[$i];
    }
    return $m;
}

echo find_max([3, 1, 4, 1, 5, 9, 2, 6]) . PHP_EOL;   // 9
echo find_max([-5, -2, -9]) . PHP_EOL;               // -2
var_dump(find_max([]));                              // NULL
// 输出: 9 / -2 / NULL

第 5 题:数组求和

题目:写一个函数 sum_arr($arr),返回数值数组的总和;空数组返回 0。

提示:PHP 内置 array_sum($arr) 一行搞定;也可 foreach 累加理解原理。

参考答案
<?php
// 方法 1:内置 array_sum 最直接
function sum_arr($arr) {
    return array_sum($arr);
}

// 方法 2:foreach 循环累加(理解原理)
function sum_foreach($arr) {
    $total = 0;
    foreach ($arr as $v) {
        $total += $v;
    }
    return $total;
}

echo sum_arr([1, 2, 3, 4, 5]) . PHP_EOL;       // 15
echo sum_foreach([10, 20, 30]) . PHP_EOL;      // 60
echo sum_arr([]) . PHP_EOL;                    // 0(空数组 array_sum 返回 0)
// 输出: 15 / 60 / 0

第 6 题:计算阶乘

题目:写一个函数 factorial($n),返回 $n!(n 的阶乘)。要求 factorial(0) == 1,且对负数抛 InvalidArgumentException。

提示:循环写法用累乘变量;递归写法更贴近数学定义 n! = n * (n-1)!,但要注意栈深度。

参考答案
<?php
// 方法 1:循环
function factorial_loop($n) {
    if ($n < 0) throw new InvalidArgumentException("阶乘不接受负数");
    $result = 1;
    for ($i = 1; $i <= $n; $i++) {
        $result *= $i;
    }
    return $result;
}

// 方法 2:递归(更贴近数学定义 n! = n * (n-1)!)
function factorial_rec($n) {
    if ($n < 0) throw new InvalidArgumentException("阶乘不接受负数");
    if ($n < 2) return 1;
    return $n * factorial_rec($n - 1);
}

echo factorial_loop(5) . PHP_EOL;   // 120
echo factorial_loop(0) . PHP_EOL;   // 1
echo factorial_rec(6) . PHP_EOL;    // 720
// 输出: 120 / 1 / 720

进阶(5 题):数组 / 字符串

这一组围绕 PHP 最强大的内置武器——数组函数族。PHP 的数组既能当列表又能当字典,内置几十个高阶函数,熟练运用 array_map / array_filter / array_reduce / usort 能让代码大幅简化。

第 7 题:关联数组增删改查

题目:给定一个关联数组 ["id" => 1, "name" => "小明", "age" => 20],演示如何增、删、改、查。

提示:$arr[key] = value 同时用于"增"新键和"改"已存在键;isset($arr[key]) 判断键是否存在;unset($arr[key]) 删除键值对。

参考答案
<?php
// 关联数组:键是字符串,值是任意类型。PHP 关联数组等价于字典 / map
$user = ["id" => 1, "name" => "小明", "age" => 20];

// 增:直接赋值新键
$user["email"] = "xm@example.com";

// 改:已存在的键重新赋值
$user["age"] = 21;

// 查:用键访问;isset 判断键是否存在(避免报错)
echo $user["name"] . PHP_EOL;                 // 小明
var_dump(isset($user["phone"]));              // bool(false)

// 删:unset 销毁键值对
unset($user["email"]);

print_r($user);
// 输出:
// 小明
// bool(false)
// Array ( [id] => 1 [name] => 小明 [age] => 21 )

第 8 题:array_map 对每元素应用函数

题目:给定 [1,2,3,4,5],用 array_map 生成每个元素的平方;再对一个字符串数组全部转大写。

提示:array_map(callback, array) 返回新数组(不改原数组);PHP 7.4+ 的箭头函数 fn($x) => $x * $x 写起来最简洁;也可传函数名字符串如 "strtoupper"。

参考答案
<?php
// array_map(callback, array):对每个元素应用回调,返回新数组(不改原数组)
$nums = [1, 2, 3, 4, 5];

// 1. 平方(箭头函数 PHP 7.4+)
$squares = array_map(fn($x) => $x * $x, $nums);
print_r($squares);   // [1, 4, 9, 16, 25]

// 2. 字符串数组转大写
$names = ["alice", "bob", "carol"];
$upper = array_map("strtoupper", $names);
print_r($upper);     // ["ALICE", "BOB", "CAROL"]

// 3. 传统匿名函数写法(等价箭头函数)
$doubled = array_map(function ($x) { return $x * 2; }, $nums);
print_r($doubled);   // [2, 4, 6, 8, 10]
// 输出: 三个数组依次为平方 / 大写名 / 翻倍

第 9 题:array_filter 过滤

题目:给定 [1..10],过滤出所有偶数;再过滤出长度 ≥ 5 的字符串。

提示:array_filter(array, callback) 保留回调返回 true 的元素;注意它不重建索引,过滤后键保持原值,通常配 array_values() 重建为连续索引。

参考答案
<?php
// array_filter(array, callback):保留回调返回 true 的元素
// 注意:不重新索引,过滤后键保持原值,常配合 array_values 重建索引
$nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];

// 1. 保留偶数
$evens = array_filter($nums, fn($x) => $x % 2 === 0);
print_r(array_values($evens));   // [2, 4, 6, 8, 10]

// 2. 保留长度 >= 5 的字符串
$words = ["hi", "hello", "ok", "world", "hey"];
$long = array_filter($words, fn($s) => strlen($s) >= 5);
print_r(array_values($long));    // ["hello", "world"]

// 3. 不传回调:过滤掉所有 falsy 值(0、""、null、false)
$mixed = [0, 1, "", "a", null, false, 2];
$truthy = array_filter($mixed);
print_r(array_values($truthy));  // [1, "a", 2]
// 输出: 三组过滤结果如上

第 10 题:array_reduce 聚合

题目:用 array_reduce 完成三件事:(a) 求和;(b) 求乘积(阶乘的通用形式);(c) 把字符串数组拼成一句话。

提示:array_reduce(array, callback, initial) 把数组"归约"成单个值;回调签名 fn($carry, $item) => ...,$carry 是上次回调的返回值,第三个参数是初始值。

参考答案
<?php
// array_reduce(array, callback, initial):把数组逐步"归约"成单个值
$nums = [1, 2, 3, 4, 5];

// 1. 求和(等价 array_sum,但更通用)
$sum = array_reduce($nums, fn($carry, $item) => $carry + $item, 0);
echo $sum . PHP_EOL;   // 15

// 2. 求乘积(阶乘的通用形式)
$product = array_reduce($nums, fn($carry, $item) => $carry * $item, 1);
echo $product . PHP_EOL;   // 120

// 3. 拼接字符串
$words = ["PHP", "is", "fun"];
$sentence = array_reduce($words, fn($carry, $w) => $carry . " " . $w, "");
echo trim($sentence) . PHP_EOL;   // PHP is fun
// 输出: 15 / 120 / PHP is fun

第 11 题:usort 自定义比较排序

题目:用 usort 对数组升序、降序排序;再对一组关联数组(学生)按"年龄"键升序排序。

提示:比较函数返回负数表示 a 在前、正数 b 在前、0 相等;PHP 太空船操作符 $a <=> $b 一行搞定数值比较(降序就交换参数)。

参考答案
<?php
// usort(array, comparator):用自定义比较函数排序(会重建索引)
// 比较函数:返回负数表示 a 在前,正数 b 在前,0 相等
$nums = [3, 1, 4, 1, 5, 9, 2, 6];

// 1. 升序(<=> 是太空船操作符,PHP 7+)
usort($nums, fn($a, $b) => $a <=> $b);
print_r($nums);   // [1, 1, 2, 3, 4, 5, 6, 9]

// 2. 降序:交换参数位置(或取反)
usort($nums, fn($a, $b) => $b <=> $a);
print_r($nums);   // [9, 6, 5, 4, 3, 2, 1, 1]

// 3. 按关联数组的某个键排序(实战最常见)
$users = [
    ["name" => "小明", "age" => 30],
    ["name" => "小红", "age" => 25],
    ["name" => "小刚", "age" => 28],
];
usort($users, fn($a, $b) => $a["age"] <=> $b["age"]);   // 按 age 升序
foreach ($users as $u) echo $u["name"] . ":" . $u["age"] . " ";
// 输出: 小红:25 小刚:28 小明:30

实战(4 题):Web 综合应用

这一组贴近真实 Web 开发场景:多维数据组织、表单接收与转义、会话管理、单文件表单计算器——这四件事几乎覆盖了 PHP 在 Web 上的核心用法。

第 12 题:学生管理(关联数组 + 多维数组遍历)

题目:用多维数组存储若干学生(姓名、各科成绩、年龄),遍历输出每人的总分和均分,再按数学成绩降序排名。

提示:外层 foreach 遍历每个学生,内层用 array_sum($stu["scores"]) 算总分;usort($students, fn($a,$b) => $b["scores"]["数学"] <=> $a["scores"]["数学"]) 按数学降序。

参考答案
<?php
// 多维数组:每个学生是一个关联数组,整体是索引数组
$students = [
    ["name" => "小明", "scores" => ["数学" => 90, "语文" => 80], "age" => 18],
    ["name" => "小红", "scores" => ["数学" => 95, "语文" => 88], "age" => 17],
    ["name" => "小刚", "scores" => ["数学" => 70, "语文" => 100], "age" => 19],
];

// 1. 遍历所有学生所有成绩
foreach ($students as $stu) {
    $total = array_sum($stu["scores"]);
    $avg = $total / count($stu["scores"]);
    printf("%s 总分 %d, 均分 %.1f\n", $stu["name"], $total, $avg);
}

// 2. 按数学成绩降序排名(用 usort)
usort($students, fn($a, $b) => $b["scores"]["数学"] <=> $a["scores"]["数学"]);
echo "数学排名: ";
foreach ($students as $i => $stu) {
    echo ($i + 1) . "." . $stu["name"] . " ";
}
echo "\n";
// 输出:
// 小明 总分 170, 均分 85.0
// 小红 总分 183, 均分 91.5
// 小刚 总分 170, 均分 85.0
// 数学排名: 2.小红 1.小明 3.小刚

第 13 题:表单处理($_GET / $_POST + htmlspecialchars)

题目:接收 POST 提交的姓名和年龄,做基本校验(姓名非空、年龄 1~150),通过则输出问候。所有输出必须防 XSS。

提示:$_POST["name"] ?? "" 用 null 合并避免 undefined index;htmlspecialchars($s, ENT_QUOTES, "UTF-8") 把 < > & " ' 全部转义,杜绝 XSS 注入。

参考答案
<?php
// 表单接收 + XSS 防护:所有用户输入输出前都要 htmlspecialchars 转义
// 假设表单: <form method="POST"><input name="name"><input name="age"></form>

// null 合并运算符 ?? :键不存在时给默认值,避免 undefined index 警告
$name = trim($_POST["name"] ?? "");
$age = (int) ($_POST["age"] ?? 0);

$errors = [];
if ($name === "") {
    $errors[] = "姓名不能为空";
}
if ($age <= 0 || $age > 150) {
    $errors[] = "年龄必须在 1~150 之间";
}

if (empty($errors)) {
    // ENT_QUOTES 转义单双引号,UTF-8 指定编码
    $safe_name = htmlspecialchars($name, ENT_QUOTES, "UTF-8");
    echo "你好," . $safe_name . "! 年龄 " . $age;
} else {
    foreach ($errors as $e) echo "<p>" . htmlspecialchars($e, ENT_QUOTES, "UTF-8") . "</p>";
}
// 输出(假设 POST 提交 name=Alice&age=20):
// 你好,Alice! 年龄 20

第 14 题:会话计数器(session_start + $_SESSION)

题目:实现一个"用户刷新一次,计数 +1"的会话访问计数器,显示访问次数和会话 ID。

提示:session_start() 必须放在任何输出之前(连空格都不行);$_SESSION["visits"] 跨页面持久化;session_id() 取当前会话 ID。

参考答案
<?php
// 计数每个用户本会话访问页面的次数。session_start 必须在任何输出之前调用
session_start();

// 首次访问:初始化;之后每次累加
if (!isset($_SESSION["visits"])) {
    $_SESSION["visits"] = 0;
}
$_SESSION["visits"]++;

echo "本次会话你已访问本页 " . $_SESSION["visits"] . " 次。\n";
echo "会话 ID: " . session_id() . "\n";

// 重置示例(实际项目可能放在登出逻辑里)
// session_unset();    // 清空所有会话变量
// session_destroy();  // 销毁会话
// 输出(连续刷新 3 次):
// 本次会话你已访问本页 1 次。
// 本次会话你已访问本页 2 次。
// 本次会话你已访问本页 3 次。

第 15 题:简易计算器(switch + $_POST 表单)

题目:用 PHP 单文件实现一个支持 + - * / 的计算器:同时显示表单、接收 POST、用 switch 分派运算、除零给出友好错误。

提示:calc($a, $op, $b) 用 switch 分派;除零抛 RuntimeException;主流程用 $_SERVER["REQUEST_METHOD"] === "POST" 判断提交,用 try/catch 接异常。

参考答案
<?php
// 一个 PHP 页面同时处理表单显示和计算逻辑(单文件表单的典型结构)
function calc($a, $op, $b) {
    switch ($op) {
        case "+": return $a + $b;
        case "-": return $a - $b;
        case "*": return $a * $b;
        case "/":
            if ($b == 0) throw new RuntimeException("除数不能为 0");
            return $a / $b;
        default:  throw new InvalidArgumentException("不支持的运算符: " . $op);
    }
}

// 处理 POST 提交
$result = null;
$error = null;
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    try {
        $a = (float) ($_POST["a"] ?? 0);
        $b = (float) ($_POST["b"] ?? 0);
        $op = $_POST["op"] ?? "";
        $result = calc($a, $op, $b);
    } catch (Throwable $e) {
        $error = $e->getMessage();
    }
}

// 非表单环境演示(直接调用 calc,不走 HTML)
echo calc(3, "+", 5) . PHP_EOL;    // 8
echo calc(10, "/", 4) . PHP_EOL;   // 2.5
echo calc(2, "*", 9) . PHP_EOL;    // 18
// 输出: 8 / 2.5 / 18
// HTML 表单部分省略,实际项目接 <form method="POST"> 收集 a/op/b 提交

练习建议

不要只看答案。把每道题在本地或在线环境亲手敲一遍,改一改输入试试边界情况(空数组、负数、超长字符串、含特殊字符的表单输入)。编程是手艺活,键盘上练出来的肌肉记忆比眼睛看十遍都管用。

试着扩展:做完第 12 题学生管理,能不能加上"按总分排名""导出 CSV"?做完第 13 题表单,能不能加上邮箱格式正则校验?做完第 15 题计算器,能不能加上幂运算和百分比?每个扩展都是一次小型的真实开发。

更多练习资源:刷算法去 LeetCode;做项目去 GitHub 找灵感;遇到不懂的先查 PHP 官方手册(有中文版,数组函数查起来最权威)。

← 上一篇 PHP 实战项目

返回 PHP 教程目录

✈️💬