Skip to content

do-while 循环

do-whilewhile 循环的变体。与 while 不同,do-while 会先执行一次循环体,然后在每次迭代结束后检查条件。这意味着 do-while 循环至少会执行一次,无论条件表达式的初始值是什么。

前置知识

  • 熟悉 while 循环 的基本用法
  • 了解 break 的用法
  • 理解布尔值和条件表达式

基础概念

do-while 循环的执行流程:

  1. 执行循环体中的代码(至少一次)。
  2. 对条件表达式求值。
  3. 如果结果为 true,回到步骤 1 继续执行。
  4. 如果结果为 false,退出循环。

核心区别

while:先判断后执行(可能 0 次)。do-while:先执行后判断(至少 1 次)。

语法结构

基本 do-while 语法

php
<?php
declare(strict_types=1);

$count = 1;

do {
    echo "第 {$count} 次迭代\n";
    $count++;
} while ($count <= 5);
// 输出:
// 第 1 次迭代
// 第 2 次迭代
// 第 3 次迭代
// 第 4 次迭代
// 第 5 次迭代

与 while 的对比

php
<?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
<?php
declare(strict_types=1);

// 当条件初始就为 false 时,do-while 仍然执行一次
$done = true;

do {
    echo "初始化完成\n";
} while (!$done);
// 输出:初始化完成

详细说明

条件判断时机

do-while 在循环体执行之后才检查条件。这意味着循环体中对变量的修改会在当次迭代的条件判断中立即生效。

php
<?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
<?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
<?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
<?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
<?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
<?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, 1

do-while 与 while 的选择指南

场景推荐原因
输入验证do-while至少需要获取一次输入再验证
菜单显示do-while先显示菜单,再判断退出
文件读取while可能不需要读取(空文件)
计数循环for初始化、条件、递增一体化
条件可能初始为 falsewhile避免不必要的执行

注意事项

  1. 末尾的分号do-while 语句的 while(条件) 后面必须加分号 ;,这是 PHP 中少数几个需要分号结尾的控制结构。

  2. 避免无意的一次执行:如果循环体有副作用(如修改数据库),确保条件初始为 false 时不会产生问题。

  3. 不可用替代语法do-while 不支持替代语法(没有 endwhile 形式),因为 do 关键字和 while 条件分别在循环体的首尾。

  4. 不要用于已知迭代次数的场景:如果循环次数已知,使用 for 循环更清晰。

最佳实践

  1. 优先考虑 while:在大多数场景下,while 更常用。只在"至少执行一次"有明确意义时才使用 do-while

  2. 输入验证是最佳用例do-while 的典型用途包括用户输入验证、菜单驱动程序、需要先执行再判断的场景。

  3. 注意末尾分号while(条件); 末尾的分号容易被遗忘,导致语法错误。

  4. 保持循环体简洁:如果循环体过于复杂,考虑将逻辑提取为函数。

  5. 添加注释说明意图:当使用 do-while 时,在代码注释中说明为什么需要"先执行后判断",帮助其他开发者理解设计意图。

下一节

了解完 do-while 后,接下来学习最常见的循环结构 for 循环

进阶用法

调试与测试技巧

php
<?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
<?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
<?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 修正
性能下降索引缺失/数据量大添加索引,优化查询
数据不一致并发冲突/事务残留使用锁机制和事务
内存溢出大数据集/未释放资源增大内存限制,分批处理

故障排除步骤

  1. 检查错误日志和异常信息
  2. 确认配置和环境是否正确
  3. 使用调试工具逐步排查
  4. 参考官方文档查找已知问题

版本兼容性说明

功能最低版本说明
基础功能PHP 8.1本文档基准版本
只读属性PHP 8.1public readonly 修饰符
枚举类型PHP 8.1enum 类型和 match 表达式
FiberPHP 8.1协程/轻量级并发
命名参数PHP 8.0foo(arg_name: value)
联合类型PHP 8.0`int
Null 安全运算符PHP 8.0$obj?->method()
析构器 promotionPHP 8.0__construct(public $x)
php
<?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');

参考链接