CC++ & Algorithm

先敲门还是先推门?C++ 里 `while` 和 `do-while` 的处世哲学

你有没有遇到过这种情况:走到一扇关着的门前,不确定里面有没有人,于是你下意识地先敲了敲门,再推门进去。如果没人回应,你推门进去也没意义;如果门根本没锁,你一推就开了,敲门反而显得多余。

写循环的时候,我们也会遇到类似的抉择:是先判断条件再执行,还是先执行一次再判断? 这就是 whiledo-while 最本质的区别,也是很多初学者容易踩坑的地方。


两个朋友,两种性格

while 想象成一个谨慎的朋友,他做任何事之前都要先问一句:“条件成立吗?成立我才做。” 而 do-while 是个行动派,他先把事情做了,然后再回头问:“条件还成立吗?成立我再继续。”

这个区别看起来只是顺序换了一下,但带来的后果完全不同:

  • while可能一次都不执行。如果条件一开始就为假,循环体直接跳过。
  • do-while至少执行一次。不管条件是真还是假,循环体先跑一遍再说。

用代码说人话:

int homework = 0;   // 作业已经写完了

// while:先检查,条件为假,一次都不执行
while (homework > 0) {
    cout << "写作业" << endl;
}

// do-while:先写一次,再检查,循环体至少执行一次
do {
    cout << "写作业" << endl;
} while (homework > 0);

看,同样都是 homework = 0while 什么都没做,do-while 却强行让你多写了一题。这就是“先看条件”和“先做一次”的差别。


用对地方,才是关键

你可能觉得 do-while 有点霸道,但它恰恰在最需要“先做一次”的场景里大放异彩。

猜数字游戏就是经典例子。程序生成一个 1~10 的随机数,用户反复猜,直到猜对。你想一想:用户是不是至少会猜一次?当然,你不可能一次都不猜就知道答案。所以用 do-while 非常自然:

int secret = rand() % 10 + 1;
int guess;

do {
    cin >> guess;
    if (guess > secret) cout << "太大了!";
    else if (guess < secret) cout << "太小了!";
} while (guess != secret);

如果非要用 while 写,你得在循环外面先让用户输入一次,然后在循环里面再输入一次,代码显得冗余,逻辑上也多了一个“入口外”的分支。

反过来,有些场景循环体可能一次都不需要执行。比如检查密码:

string input;
cin >> input;
while (input != password) {
    cin >> input;
}

如果用户第一次就输对了,循环体根本不该跑。要是用 do-while,你就要先提示“密码错误”再让用户输入,哪怕人家是对的——这不是找茬吗?


有一道题,恰好把这种性格差异考得明明白白

很多入门判断会问:“在 do-while 循环中,如果条件表达式的初始值为 false,循环体会执行几次?”

答案是:一次

因为 do-while 的执行流程是“先执行循环体,再判断条件”,判断在行动之后。哪怕你写:

do {
    cout << "我来了" << endl;
} while (false);

屏幕上也会打出一行“我来了”。而同样的条件放到 while 里:

while (false) {
    cout << "我不会出现" << endl;
}

屏幕上什么都没有。这就是两种循环最核心的性格差异,也是考试里最喜欢挖的坑。记住一句话:“while 先看后做,do-while 先做再看。”


另一个隐藏的坑:忘记封号

初学 do-while 时,很多人会忘记在 while(条件); 后面加分号while 循环末尾不用加分号(因为它本身就是完整的语句),但 do-while 的结尾必须加分号,表示整个 do-while 语句结束。

do {
    x--;
} while (x > 5)   // 编译错误:缺少分号

这个分号其实是在告诉编译器:我整个循环体已经结束了,别把它和下面的代码连在一起。忘一次没事,把它当成“do-while 的专属句号”就好。


再看一道有意思的题:打印字符矩阵

有这样一个问题:根据给出的 5×7 字符图案规律,输出一个 10×18 的图形。图案长这样:

ABCDEFG
BABCDEF
CBABCDE
DCBABCD
EDCBABC

看出来了吗?第 i 行的特点是:从第 i 个字母开始往前倒着走到 A,然后再从 B 往后走完剩下的字母。用循环写,本质上就是处理“行号”和“列号”与字母的映射关系。

这道题完全可以不用 while,用 for 更清晰。但如果你非要用 while,也能写,只是要注意循环变量要手动更新——这又回到了我们前面提到的**“忘记更新循环变量”**的问题。

int i = 1;
while (i <= 10) {
    int j = 1;
    while (j <= 18) {
        // 第 i 行第 j 列的字母怎么算?
        // 特征:对角线方向上是连续的字母序列
        char ch = 'A' + abs(i - j);  // 关键是找到这个规律
        cout << ch;
        j++;
    }
    cout << endl;
    i++;
}

这里最关键的是 abs(i - j) 这个表达式。第 i 行第 j 列的位置,字母和它到对角线的距离有关。这是典型的“找规律 + 循环控制”问题。你会发现,循环本身并不难,难的是你能不能把问题抽象成循环变量之间的关系


什么时候选哪个?我的经验法则

如果你不确定该用哪个,问自己一个问题:“这个循环体,有没有可能一次都不该执行?”

  • 如果答案是“有可能”——用 while。比如检查输入是否合法,用户可能第一次就输对了。
  • 如果答案是“不可能,至少得做一次”——用 do-while。比如显示菜单让用户选择,你不先显示菜单,用户选什么?

还有一个小技巧:do-while 特别适合那些“先尝试,再判断是否继续”的场景。比如读取文件直到读到有效数据,或者验证用户输入直到满足某种要求。它的结构让“先做一次”变得更自然,不需要额外在循环外写重复代码。


别把循环写成死循环

不管用哪种循环,都要确保循环变量能在循环体内被改变。我见过不少同学写了这样的代码:

int i = 0;
while (i < 5) {
    cout << "Hello" << endl;
}

然后程序像卡住了一样疯狂输出。原因很简单:i 的值从头到尾没变过,条件永远为真。这不是 while 的问题,是写代码的人忘了给循环“出口”。

do-while 也一样,如果循环体内不改变条件变量的值,它也会变成一个无限循环。记住:循环必须有“前进”的机制。


写在最后

whiledo-while 看起来只是顺序不同,但它们背后是两种不同的思维模式:

  • while 是“条件驱动”的,适合那些需要判断后再行动的逻辑。
  • do-while 是“行动驱动”的,适合那些不问三七二十一先做一次再说的场景。

理解了这个区别,你写代码时就能更准确地表达自己的意图,而不是稀里糊涂地随便选一个。

进阶建议:去读一读 for 循环的源码——其实 for 本质上就是一个带初始化、条件判断、变量更新的 while 变体。当你把三种循环都吃透,遇到任何需要重复的逻辑,都能一眼判断出最合适的写法。


关于作者

我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。

这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。

如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)

这篇文章对你有帮助吗?

成为第一个评价的人

评论0

还没有评论,来抢沙发~

评论加载中...

想系统学习这个知识点?查看完整知识点 →