do-while 循环
do-while 是 while 循环的变体。与 while 不同,do-while 会先执行一次循环体,然后在每次迭代结束后检查条件。这意味着 do-while 循环至少会执行一次,无论条件表达式的初始值是什么。
基础概念
do-while 循环的执行流程:
- 执行循环体中的代码(至少一次)。
- 对条件表达式求值。
- 如果结果为
true,回到步骤 1 继续执行。 - 如果结果为
false,退出循环。
核心区别
while:先判断后执行(可能 0 次)。do-while:先执行后判断(至少 1 次)。
语法结构
基本 do-while 语法
<?php
declare(strict_types=1);
$count = 1;
do {
echo "第 {$count} 次迭代\n";
$count++;
} while ($count <= 5);
// 输出:
// 第 1 次迭代
// 第 2 次迭代
// 第 3 次迭代
// 第 4 次迭代
// 第 5 次迭代与 while 的对比
<?php
declare(strict_types=1);
// while:条件初始为 false,不执行
$counter = 10;
while ($counter < 5) {
echo "while: 不会执行\n";
$counter++;
}
echo "while 结束\n";
// do-while:先执行一次,然后检查条件
$counter = 10;
do {
echo "do-while: 至少执行一次\n";
$counter++;
} while ($counter < 5);
echo "do-while 结束\n";
// 输出:
// do-while: 至少执行一次
// do-while 结束单次执行的 do-while
<?php
declare(strict_types=1);
// 当条件初始就为 false 时,do-while 仍然执行一次
$done = true;
do {
echo "初始化完成\n";
} while (!$done);
// 输出:初始化完成详细说明
条件判断时机
do-while 在循环体执行之后才检查条件。这意味着循环体中对变量的修改会在当次迭代的条件判断中立即生效。
<?php
declare(strict_types=1);
$num = 1;
do {
echo "num = {$num}\n";
$num *= 2;
} while ($num <= 100);
// 输出:
// num = 1
// num = 2
// num = 4
// num = 8
// num = 16
// num = 32
// num = 64
// num = 128(执行后 128 > 100,条件为 false,退出)避免忘记更新条件变量
与 while 一样,如果在循环体中忘记更新条件变量,会导致死循环。但在 do-while 中这个问题可能更隐蔽,因为循环至少会执行一次。
<?php
declare(strict_types=1);
// 错误示例:忘记更新变量
$counter = 0;
/*
do {
echo "死循环...\n";
// 忘记 $counter++
} while ($counter < 10);
*/
// 正确示例
$counter = 0;
do {
echo "counter = {$counter}\n";
$counter++;
} while ($counter < 10);使用 break 控制循环
在 do-while 中也可以使用 break 提前退出循环。
<?php
declare(strict_types=1);
$attempts = 0;
do {
$attempts++;
echo "第 {$attempts} 次尝试\n";
// 模拟成功条件
$success = ($attempts >= 3);
if ($success) {
echo "操作成功,退出循环。\n";
break;
}
} while ($attempts < 10);实战示例
用户输入验证
do-while 最典型的使用场景是输入验证:先获取输入,再判断是否合法,不合法则继续循环。
<?php
declare(strict_types=1);
// 模拟 CLI 用户输入验证
function getValidAge(int $minAge = 1, int $maxAge = 150): int
{
// 模拟用户输入序列
$simulatedInputs = [-5, 0, 200, 25];
$inputIndex = 0;
$age = 0;
do {
if ($inputIndex < count($simulatedInputs)) {
$age = $simulatedInputs[$inputIndex];
$inputIndex++;
} else {
$age = $minAge; // 最终回退
}
if ($age < $minAge || $age > $maxAge) {
echo "无效年龄:{$age}。请输入 {$minAge}-{$maxAge} 之间的整数。\n";
}
} while ($age < $minAge || $age > $maxAge);
echo "有效年龄:{$age}\n";
return $age;
}
getValidAge();
// 输出:
// 无效年龄:-5。请输入 1-150 之间的整数。
// 无效年龄:0。请输入 1-150 之间的整数。
// 无效年龄:200。请输入 1-150 之间的整数。
// 有效年龄:25菜单驱动程序
do-while 非常适合实现"先显示菜单,再判断用户选择"的交互模式。
<?php
declare(strict_types=1);
function runMenu(): void
{
// 模拟用户操作序列:查看 -> 搜索 -> 退出
$choices = ['view', 'search', 'quit'];
$choiceIndex = 0;
do {
// 显示菜单
echo "=== 系统菜单 ===\n";
echo "1. 查看数据\n";
echo "2. 搜索数据\n";
echo "3. 退出\n";
// 获取用户选择
$choice = $choiceIndex < count($choices) ? $choices[$choiceIndex] : 'quit';
$choiceIndex++;
echo "用户选择:{$choice}\n";
switch ($choice) {
case 'view':
echo "显示所有数据...\n";
break;
case 'search':
echo "搜索数据...\n";
break;
case 'quit':
echo "再见!\n";
break;
default:
echo "无效选择,请重试。\n";
}
echo "\n";
} while ($choice !== 'quit');
}
runMenu();数据处理管道
在某些场景下,需要对数据进行多轮处理,直到满足某个条件才停止。
<?php
declare(strict_types=1);
function normalizeData(array &$data): int
{
$iterations = 0;
do {
$changed = false;
$iterations++;
foreach ($data as &$value) {
// 示例:将所有负值替换为其绝对值,并标记是否有修改
if ($value < 0) {
$value = abs($value);
$changed = true;
}
}
unset($value);
} while ($changed);
return $iterations;
}
$data = [-3, 5, -2, -8, 0, 7, -1];
$iterations = normalizeData($data);
echo "经过 {$iterations} 轮处理,结果:" . implode(', ', $data) . "\n";
// 输出:经过 1 轮处理,结果:3, 5, 2, 8, 0, 7, 1do-while 与 while 的选择指南
| 场景 | 推荐 | 原因 |
|---|---|---|
| 输入验证 | do-while | 至少需要获取一次输入再验证 |
| 菜单显示 | do-while | 先显示菜单,再判断退出 |
| 文件读取 | while | 可能不需要读取(空文件) |
| 计数循环 | for | 初始化、条件、递增一体化 |
| 条件可能初始为 false | while | 避免不必要的执行 |
注意事项
末尾的分号:
do-while语句的while(条件)后面必须加分号;,这是 PHP 中少数几个需要分号结尾的控制结构。避免无意的一次执行:如果循环体有副作用(如修改数据库),确保条件初始为
false时不会产生问题。不可用替代语法:
do-while不支持替代语法(没有endwhile形式),因为do关键字和while条件分别在循环体的首尾。不要用于已知迭代次数的场景:如果循环次数已知,使用
for循环更清晰。
最佳实践
优先考虑
while:在大多数场景下,while更常用。只在"至少执行一次"有明确意义时才使用do-while。输入验证是最佳用例:
do-while的典型用途包括用户输入验证、菜单驱动程序、需要先执行再判断的场景。注意末尾分号:
while(条件);末尾的分号容易被遗忘,导致语法错误。保持循环体简洁:如果循环体过于复杂,考虑将逻辑提取为函数。
添加注释说明意图:当使用
do-while时,在代码注释中说明为什么需要"先执行后判断",帮助其他开发者理解设计意图。
下一节
了解完 do-while 后,接下来学习最常见的循环结构 for 循环。
进阶用法
调试与测试技巧
<?php
declare(strict_types=1);
// 单元测试辅助函数
function createTestResource(): mixed
{
return match (true) {
default => new stdClass(),
};
}
// 调试输出函数
function debugOutput(mixed , string = ''): void
{
= ? ": " : '';
.= print_r(, true);
fwrite(STDERR, . "\n");
}
// 性能基准测试
function benchmark(callable , int = 1000): float
{
= hrtime(true);
for ($i = 0; $i < $iterations; $i++) {
$fn();
}
return (hrtime(true) - $start) / 1e9;
}日志记录实践
<?php
declare(strict_types=1);
/**
* 简易日志记录器
*/
class SimpleLogger
{
private string $logFile;
private string $level = 'INFO';
public function __construct(string $logFile)
{
$this->logFile = $logFile;
}
public function info(string $message, array $context = []): void
{
$this->log('INFO', $message, $context);
}
public function warning(string $message, array $context = []): void
{
$this->log('WARNING', $message, $context);
}
public function error(string $message, array $context = []): void
{
$this->log('ERROR', $message, $context);
}
private function log(string $level, string $message, array $context): void
{
$timestamp = date('Y-m-d H:i:s');
$contextStr = $context ? ' ' . json_encode($context, JSON_UNESCAPED_UNICODE) : '';
$line = "[{$timestamp}] [{$level}] {$message}{$contextStr}\n";
file_put_contents($this->logFile, $line, FILE_APPEND | LOCK_EX);
}
}配置与环境检测
<?php
declare(strict_types=1);
// 环境检测工具
class EnvironmentChecker
{
public static function checkRequirements(array $requirements): array
{
$results = [];
foreach ($requirements as $name => $check) {
$results[$name] = is_callable($check) ? $check() : false;
}
return $results;
}
public static function getSystemInfo(): array
{
return [
'php_version' => PHP_VERSION,
'os' => PHP_OS,
'sapi' => PHP_SAPI,
'memory_limit' => ini_get('memory_limit'),
'max_execution_time' => ini_get('max_execution_time'),
'loaded_extensions' => get_loaded_extensions(),
];
}
}常见问题排查
| 问题 | 可能原因 | 解决方案 |
|---|---|---|
| 连接超时 | 网络问题/配置错误 | 检查配置,增加超时时间 |
| 权限不足 | 文件/目录权限 | 使用 chmod/chown 修正 |
| 性能下降 | 索引缺失/数据量大 | 添加索引,优化查询 |
| 数据不一致 | 并发冲突/事务残留 | 使用锁机制和事务 |
| 内存溢出 | 大数据集/未释放资源 | 增大内存限制,分批处理 |
故障排除步骤
- 检查错误日志和异常信息
- 确认配置和环境是否正确
- 使用调试工具逐步排查
- 参考官方文档查找已知问题
版本兼容性说明
| 功能 | 最低版本 | 说明 |
|---|---|---|
| 基础功能 | PHP 8.1 | 本文档基准版本 |
| 只读属性 | PHP 8.1 | public readonly 修饰符 |
| 枚举类型 | PHP 8.1 | enum 类型和 match 表达式 |
| Fiber | PHP 8.1 | 协程/轻量级并发 |
| 命名参数 | PHP 8.0 | foo(arg_name: value) |
| 联合类型 | PHP 8.0 | `int |
| Null 安全运算符 | PHP 8.0 | $obj?->method() |
| 析构器 promotion | PHP 8.0 | __construct(public $x) |
<?php
declare(strict_types=1);
// 版本兼容性检测
function ensureVersion(string $minVersion): void
{
if (version_compare(PHP_VERSION, $minVersion, '<')) {
throw new RuntimeException(
sprintf('需要 PHP %s+, 当前版本: %s', $minVersion, PHP_VERSION)
);
}
}
ensureVersion('8.1.0');