4. 分支和循环:if判断语句、运算符、if-else语句、while循环、for循环、break、continue

好,咱们今天聊聊控制流。说白了,就是让程序学会「动脑子」和「反复干活」。没有这两个东西,代码就是一潭死水,从上往下跑一遍就结束了。我刚开始学编程时,觉得这玩意儿简单,后来在CUDA里写分支,才发现坑有多深。嗯,咱们一步步来。

4.1 if判断语句:让代码学会做选择

if 语句,就是「如果……就……」。Python 里写起来很自然:

temperature = 38
if temperature > 37.5:
    print("体温偏高,建议休息")

注意看,条件后面有个冒号,下一行要缩进。这是 Python 的规矩,少一个空格都不行。我在项目中遇到过有人把 if 和后面的条件写在同一行,结果报错找了半天——嗯,Python 不吃这套。

小提示: 条件表达式的结果必须是布尔值(True 或 False)。Python 里,0、空字符串、空列表、None 都会被当作 False,其他都是 True。这个特性在 CUDA 核函数里也适用,但要注意 warp 内线程的分支发散问题。

4.2 运算符:比较和逻辑

写条件判断,离不开运算符。咱们分两类说。

比较运算符:

运算符含义示例
==等于a == b
!=不等于a != b
>大于a > b
<小于a < b
>=大于等于a >= b
<=小于等于a <= b

逻辑运算符:

运算符含义示例
and与(两边都真才真)a > 0 and b > 0
or或(一边真就真)a > 0 or b > 0
not非(取反)not a > 0

我个人习惯把复杂的条件拆开写,别堆在一行。你想想看,if a > 0 and b < 10 or c == 5 and not d 这种代码,三天后你自己都看不懂。我曾经在调试一个 CUDA 核函数时,就是因为条件写得太长,导致 warp 内线程分支严重,性能直接腰斩。后来拆成几个小 if,清爽多了。

4.3 if-else语句:二选一

有选择,就有备选。if-else 就是「如果……否则……」:

score = 85
if score >= 60:
    print("及格了")
else:
    print("没及格")

还可以用 elif 做多分支:

if score >= 90:
    grade = "优秀"
elif score >= 80:
    grade = "良好"
elif score >= 60:
    grade = "及格"
else:
    grade = "不及格"
注意: Python 没有 switch-case 语句(3.10 之后有 match-case,但用法不同)。多分支时,elif 是唯一选择。另外,条件判断是从上往下依次检查的,一旦匹配就跳出整个 if-elif-else 结构。所以,把最可能成立的条件放前面,能省点时间。

4.4 while循环:只要条件成立,就一直干

while 循环,说白了就是「只要……就一直做……」。比如:

count = 0
while count < 5:
    print(f"第 {count+1} 次循环")
    count += 1

这里有个关键点:循环体内一定要有改变条件的语句,否则就是死循环。我见过有人写 while True 然后忘了加 break,程序直接卡死。嗯,Ctrl+C 都救不了你。

在 CUDA 里,while 循环用得不多,因为 GPU 擅长的是大量并行线程,每个线程做简单的事。但有时候,比如在实现归约算法时,while 循环是少不了的。我记得有一次写一个动态并行度的算法,while 循环的条件里用到了线程 ID 和全局变量,调试起来那叫一个酸爽。

4.5 for循环:遍历一切

for 循环是 Python 里最常用的循环。它用来遍历一个可迭代对象:

# 遍历列表
fruits = ["苹果", "香蕉", "橘子"]
for fruit in fruits:
    print(fruit)

# 遍历范围
for i in range(5):
    print(i)  # 输出 0 到 4

# 遍历字典
scores = {"张三": 90, "李四": 85}
for name, score in scores.items():
    print(f"{name}: {score}")

range() 函数很常用,它有三个参数:start、stop、step。比如 range(1, 10, 2) 会生成 1, 3, 5, 7, 9。我个人习惯用 range 来写固定次数的循环,比 while 更安全,因为不会忘记加计数器。

核心区别: for 循环适合「知道要循环多少次」的场景,while 循环适合「不知道多少次,但知道什么时候停」的场景。在 CUDA 中,for 循环更常见,因为每个线程通常处理固定数量的数据。

4.6 break和continue:控制循环的节奏

这两个关键字,就像循环里的「刹车」和「跳过」。

  • break: 立即跳出整个循环,不再执行后续迭代。
  • continue: 跳过当前迭代的剩余部分,直接进入下一次迭代。

看个例子:

# break 示例:找到第一个大于 50 的数就停
numbers = [10, 30, 55, 20, 80]
for num in numbers:
    if num > 50:
        print(f"找到了:{num}")
        break

# continue 示例:跳过所有偶数
for i in range(10):
    if i % 2 == 0:
        continue
    print(i)  # 只输出奇数

我曾经在写一个 CUDA 核函数时,需要跳过某些不符合条件的线程。当时用了 continue,结果发现 warp 内的线程还是全部执行了——因为 GPU 的 warp 是 SIMT 模型,所有线程执行相同的指令,只是通过掩码来控制哪些线程「有效」。所以,在 GPU 上滥用 continue 和 break 反而可能降低性能。嗯,这个坑我踩过。

避坑指南: 在 Python 里,break 和 continue 只作用于最内层的循环。如果你有嵌套循环,想跳出外层,可以用一个标志变量,或者把外层循环写成函数,用 return 跳出。我曾经在双层循环里用 break,结果只跳出了内层,外层还在跑,数据全乱了。

4.7 实战小技巧:循环与分支的配合

实际项目中,分支和循环经常一起用。比如,从一堆数据里筛选出符合条件的:

data = [12, 45, 78, 23, 56, 89, 34]
filtered = []
for value in data:
    if value > 50:
        filtered.append(value)
print(filtered)  # 输出 [78, 56, 89]

这种模式在数据处理中太常见了。在 CUDA 里,类似的逻辑可以用并行规约或 stream compaction 来实现,但原理是一样的——先判断,再收集。

好了,这一章的内容就这些。分支和循环是编程的骨架,没有它们,代码就是一具空壳。下一章咱们聊聊函数,让代码真正「活」起来。