先敲门还是先推门?C++ 里 `while` 和 `do-while` 的处世哲学
你有没有遇到过这种情况:走到一扇关着的门前,不确定里面有没有人,于是你下意识地先敲了敲门,再推门进去。如果没人回应,你推门进去也没意义;如果门根本没锁,你一推就开了,敲门反而显得多余。
写循环的时候,我们也会遇到类似的抉择:是先判断条件再执行,还是先执行一次再判断? 这就是 while 和 do-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 = 0,while 什么都没做,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 也一样,如果循环体内不改变条件变量的值,它也会变成一个无限循环。记住:循环必须有“前进”的机制。
写在最后
while 和 do-while 看起来只是顺序不同,但它们背后是两种不同的思维模式:
while是“条件驱动”的,适合那些需要判断后再行动的逻辑。do-while是“行动驱动”的,适合那些不问三七二十一先做一次再说的场景。
理解了这个区别,你写代码时就能更准确地表达自己的意图,而不是稀里糊涂地随便选一个。
进阶建议:去读一读 for 循环的源码——其实 for 本质上就是一个带初始化、条件判断、变量更新的 while 变体。当你把三种循环都吃透,遇到任何需要重复的逻辑,都能一眼判断出最合适的写法。
关于作者
我是赵老师,持有 NOI 信息学奥赛教练证书,拥有 15 年以上的软件开发经验,从事信息学少儿编程教学已有 8 年时间。
这些年累计帮助 多名 学生通过编程特长升入自己心仪的目标学校。
如果你在编程学习上有任何疑问,欢迎联系我:18620372957(微信同号)