文章目录
-
- [0. 前言](#0. 前言)
- [1. 问题场景:看似简单,却让人懵圈](#1. 问题场景:看似简单,却让人懵圈)
- [2. 关键真相:不是无效,是权利被吞噬](#2. 关键真相:不是无效,是权利被吞噬)
- [3. 对照实验:一改阈值,效果立刻不同](#3. 对照实验:一改阈值,效果立刻不同)
- [4. 核心定义:同循环内的双条件竞态](#4. 核心定义:同循环内的双条件竞态)
- [5. 木桶效应完美解释竞态规律](#5. 木桶效应完美解释竞态规律)
-
- [① `&&` 逻辑与 → 短板效应(严格模式)](#①
&&逻辑与 → 短板效应(严格模式)) - [② `||` 逻辑或 → 长板效应(宽松模式)](#②
||逻辑或 → 长板效应(宽松模式))
- [① `&&` 逻辑与 → 短板效应(严格模式)](#①
- [6. 进阶实验:改变初始值,竞态依然成立](#6. 进阶实验:改变初始值,竞态依然成立)
- [7. 巨大误区:和短路求值无关](#7. 巨大误区:和短路求值无关)
- [8. 最终规律总结(建议收藏)](#8. 最终规律总结(建议收藏))
- [9. 结语](#9. 结语)
摘要 :本文从一段简单的C++ while循环出发,通过对照实验,发现并总结循环内部多条件之间的竞态现象,结合木桶效应与严格/宽松逻辑,彻底讲清 &&/ ||下循环执行次数的本质规律,纠正"条件无效""短路求值误导"等常见误区。
0. 前言
最近在写循环逻辑时,遇到一个非常反直觉的问题:
一个只有两个判断条件的while循环,明明语法完全正确,执行次数却总是和预期不符。一开始以为是短路求值、变量自增顺序等问题,反复实验后才发现,这是一个同循环内的双条件竞态问题,和多线程里的竞态逻辑高度相似。
今天把整个发现过程、实验验证和底层规律完整分享出来,帮大家避开同类逻辑坑。
1. 问题场景:看似简单,却让人懵圈
先看一段基础代码:
cpp
#include<iostream>
using namespace std;
// 对方认为的严重程度
const int duifang_max = 20;
// 自己认为的超时次数
const int my_timeout = 5;
int main() {
int current = 0;
int loop = 0;
while ((current <= duifang_max) && (loop <= my_timeout)) {
cout << loop << endl;
loop++;
current++;
}
return 0;
}
直观感受
两个变量同步+1,&&表示"同时满足才继续",应该两个条件一起生效。
实际运行结果
0
1
2
3
4
5
循环只执行了6次,看起来完全由loop决定,current像"没起作用"。
甚至去掉current++,输出结果完全一样。
2. 关键真相:不是无效,是权利被吞噬
很多人会得出一个错误结论:current <= 20这个条件没用。
但真实原因是:
current并不是无关,而是它的终止条件还没来得及触发,循环就被loop提前终止了。
loop只需要5次就达到上限,而current需要20次。
在竞态关系里,先达到终止条件的一方,会直接吞噬掉另一方的判断权,让后者根本没有机会被判断、被触发,看起来就像"失效"了。
3. 对照实验:一改阈值,效果立刻不同
当我们把阈值改为:
cpp
const int duifang_max = 4;
const int my_timeout = 5;
情况1:不加current++
输出:0 1 2 3 4 5(6次)
情况2:加上current++
输出:0 1 2 3 4(5次)
这一下就证明:
current条件是有效 的,只是在之前的数值场景下,它的"发言权"被更快达到阈值的loop吞噬了。
4. 核心定义:同循环内的双条件竞态
"竞态"这个词灵感来自多线程竞态条件 :
多个执行单元同时推进,谁先到达临界状态,谁就决定最终结果。
放到循环里就是:
同一个循环内,多个判断条件同步变化,谁先达到"不满足"状态,谁就终止循环 ,这种现象我称之为:
同循环内的双条件竞态问题
竞态的核心:终止所需步数
判断谁会赢得竞态,不看初始值,不单独看阈值,只看:
从当前状态到条件不满足,需要执行多少次循环。
公式(变量每次+1):
终止所需步数 = 阈值 - 初始值 + 1
循环最终执行次数,由步数更少的条件决定。
5. 木桶效应完美解释竞态规律
这个竞态逻辑,可以直接用生活中的木桶效应类比,非常好记:
① && 逻辑与 → 短板效应(严格模式)
- 继续循环:必须两个条件同时满足
- 终止循环:任意一个不满足就停止
- 执行次数 = 两个条件步数的 最小值
- 门槛高、符合条件的机会少,循环执行次数更少
因为严格,所以更容易被终止,先到终点的条件直接"掐断"循环。
② || 逻辑或 → 长板效应(宽松模式)
- 继续循环:只要一个条件满足就行
- 终止循环:必须两个都不满足
- 执行次数 = 两个条件步数的 最大值
- 门槛低、符合条件的机会多,循环执行次数更多
因为宽松,所以能一直执行到最后一个条件失效。
6. 进阶实验:改变初始值,竞态依然成立
我们把loop初始值改为2:
cpp
int current = 0;
int loop = 2;
const int duifang_max = 4;
const int my_timeout = 5;
计算终止步数:
- current:4 - 0 + 1 = 5步
- loop:5 - 2 + 1 = 4步
在&&严格模式下:
循环只执行4次,由步数更少的loop决定。
换成||宽松模式:
循环执行5次,由步数更多的current决定。
再次证明:
竞态只看终止步数,初始值、阈值都只是计算参数。
7. 巨大误区:和短路求值无关
这是最容易踩的坑:
很多人(包括我一开始)都以为是短路求值导致条件不生效。
实际上:
- 短路求值是
A为假时跳过B的判断 - 本问题中,两个条件一开始都为真,只是先后变假
- 即使出现短路,也只是判断顺序问题,结果本质由竞态决定
真正的难点是两个认知颠倒:
- 把"继续条件"和"终止条件"搞反
- 把"权利被吞噬"误解为"条件本身无效"
8. 最终规律总结(建议收藏)
-
&&严格模式 = 短板竞态同时满足才继续,一个不满足就停,执行次数取最小值。
-
||宽松模式 = 长板竞态一个满足就继续,全都不满足才停,执行次数取最大值。
-
条件不是无效,是判断权被吞噬
先达到终止状态的条件,会让另一个条件失去执行机会。
-
竞态与短路求值无关
核心是多条件之间的竞争关系,不是语法短路。
9. 结语
一个看似简单的双条件循环,背后藏着和多线程同源的竞态逻辑。
很多时候我们写循环只关注语法,却忽略了条件之间的"竞争关系",导致莫名的执行次数错误、边界异常。
理解了同循环双条件竞态问题 之后,再遇到多条件循环:
不用瞎猜,不用反复调试,只需要算出每个条件的终止步数,就能精准预判循环执行次数。
原创不易,觉得有用欢迎点赞、收藏、关注~