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 教程目录