
1. 课程定位与教学思路很多人学编程前两讲学变量、学数据类型的时候还挺轻松赋值、加减乘除、字符串拼接照着敲一遍就能跑出结果。但到了分支和循环这块突然就卡住了——不是看不懂语法而是不知道这东西到底有什么用也不知道一个完整的程序应该怎么从零开始写出来。这一讲标题里有两个关键词一个是“分支”一个是“循环”再加上“最详细的代码实现”。先说课程定位这是整个编程教学系列里第一个真正意义上的分水岭。变量和数据只是存储信息的容器而分支和循环让程序拥有了“判断能力”和“重复执行能力”。没有这两个结构代码只能从上到下顺序执行一遍就结束有了它们代码才能根据不同的输入给出不同的反应才能把一个动作重复做成千上万次。我做了多年教学和一线项目开发见过不少初学者卡在这两座山前面。有人因为缩进报错被劝退有人因为死循环把电脑卡死有人写完代码但不知道从哪里开始调试。所以这一讲我不会只罗列语法而是把每一个知识点都落到“你写代码时真正会用到的那一步”。所有代码我都会给出完整可运行的版本并且详细说明每一行在干什么、为什么这么写。这一讲适合谁来学如果你是刚学完变量、数据类型、输入输出正处于“能看懂代码但不会自己写”的状态那这一讲就是为你准备的。有基础的老手也能拿来做复习尤其是后面“典型错误与调试技巧”那一节很多坑是写了不少代码之后才真正理解的。2. 分支结构让代码学会做选择2.1 if/elif/else 的完整使用姿势分支结构的核心只有一句话根据某个条件是成立还是成立决定程序走哪条路。现实中做任何判断都要有依据代码里这个依据就是“条件表达式”——它是一个结果只能是真或假的表达式。先看最基本的if结构score 85 if score 60: print(及格了)这段代码在Python里非常直观如果score变量的值大于等于60就执行缩进里的print语句否则什么都不做。这里第一个关键点是缩进。Python不像C语言或者Java那样用花括号划定代码块它用缩进表示“从属于哪一层”。同一个代码块里的每一行必须有相同的缩进量混用空格和Tab在绝大多数编辑器里都会直接报错。下面把情况升级成二选一甚至多选一score 85 if score 90: print(优秀) elif score 60: print(及格) else: print(不及格)elif是“else if”的简写意思是“上面的条件不成立再看这个条件”。程序会从上往下依次判断碰到第一个成立的条件就进入对应代码块进入之后后面的elif和else就都不看了。这里有个初学者非常容易踩的坑条件顺序。假设有人把顺序写反了if score 60: print(及格) elif score 90: print(优秀)当score等于95时程序先判断score 60,成立直接输出“及格”后面那个“优秀”根本不会执行。从人的思维上看95分明明更优秀但程序按顺序只认第一个成立条件。所以写多条件判断时一定要把更严格、更具体的条件放在前面。2.2 条件表达式的细节与逻辑组合条件表达式里常见的比较运算符有、!、、、、。注意判断两个值是否相等用的是两个等号“”一个等号“”是赋值。这个错误出现频率极高而且表现很隐蔽——有时候程序不报错结果却完全不对。比如score 85 if score 60: # 这是赋值不是判断 print(等于60)在Python里这行代码会因为赋值后就执行而直接报SyntaxError但在某些老式语言里会静默地产生错误结果。养成习惯只要写在if后面要比较相等就写两个等号。有时候一个判断条件搞不定需要组合多个条件。比如“成绩大于等于60且小于90”用逻辑运算符score 75 if score 60 and score 90: print(处于及格到良好之间)and表示两个条件都成立才为真or表示任意一个成立就为真not表示取反。可以用括号控制优先级这和你数学里算表达式是一个道理age 22 has_permission True if (age 18 and age 60) or has_permission: print(可以进入)另外还有一个Python里非常实用的“区间写法”if 60 score 90: print(区间判断)这种连写方式在数学里很自然Python也支持很多人用多了就再也回不去写长逻辑组合了。2.3 嵌套分支的代码组织规范分支里面套分支叫嵌套。什么时候需要用嵌套当外层条件决定内层条件是否有意义的时候。举个例子只有用户登录了才需要进一步判断他的角色没登录的话讨论角色就毫无意义。is_login True role admin if is_login: if role admin: print(进入管理后台) else: print(进入普通页面) else: print(请先登录)这段逻辑清晰但嵌套层级一多代码就会越缩越深可读性急剧下降。实际项目里有个经验法则嵌套尽量别超过三层。超过三层要么用逻辑运算符合并条件要么提前写return或抛出错误提前结束分支。比如三层嵌套可以改写成if not is_login: print(请先登录) elif role admin: print(进入管理后台) else: print(进入普通页面)用elif把不同层级平铺开优点是每个分支的执行路径一目了然不需要来回数缩进。这个改写思路来自前端权限控制里的“提前返回”模式在Python里同样适用。3. 循环结构让代码学会重复3.1 while循环的写法与执行原理循环要解决的核心问题是让同一段代码反复执行直到满足某个退出条件。不看清楚这一点写出来的循环要么一次都不执行要么就是死循环。最基本的while循环count 0 while count 5: print(当前计数:, count) count count 1执行流程是这样的先判断count是否小于5成立则进入循环体打印当前的count然后执行count count 1执行完一轮后再次回到while那一行重新判断当count变成5时条件不成立循环结束。执行结果当前计数: 0 当前计数: 1 当前计数: 2 当前计数: 3 当前计数: 4可以看出循环体里做完事一定要有一个语句让条件逐渐向“不成立”靠拢。上面例子里的count count 1就是为了让count最终大于等于5。如果把这行去掉count永远是0条件永远成立程序就会无限循环下去这在终端里直接表现为光标跳闪但永远不结束。我见过不少初学同学写出过这样的死循环他们以为程序“自己会知道什么时候该停”。不对程序不知道。循环必须由你自己负责给出一个会变化的退出条件。相当于你开了一个定时闹钟但没设什么时候响——它只能一直转下去。与此相关的还有一个常见需求循环体内希望跳过某一次或者强制结束循环。这里就是break和continue的用武之地。count 0 while count 10: count count 1 if count 3: continue # 跳过这一次后面的print不执行直接进入下一轮 if count 7: break # 强制结束整个循环 print(当前编号:, count)执行结果会打印1、2、4、5、6。3被跳过到了7直接跳出循环后面的8、9、10也不打印了。continue的意思是忽略本轮剩余部分但循环还要继续break的意思是我不管循环条件还成不成立整个循环到此为止。3.2 for循环与range的搭配实现for循环在Python里主要用于遍历序列——字符串、列表、元组或者用range生成的一段数字序列。最经典的用法for i in range(5): print(第, i, 次)range(5)生成的是0、1、2、3、4这5个数字注意起始是0不包含5。这个“左闭右开”的区间设计也是初学者容易懵的地方。机械地记一下range(n)生成n个数字从0开始到n-1结束。range还能指定起始和步长for i in range(2, 10, 2): print(i)这里输出2、4、6、8。起始2到10之前结束步长2。步长可以是负数比如range(5, 0, -1)生成5、4、3、2、1。为什么Python要把range设计成不包含终点一个直接的好处是当你不关心取值范围只关心循环次数时range(n)恰好循环n次而不需要写成n1。另一个好处是和列表索引天然对齐——列表长度为len(list)最后一个索引是len(list)-1所以遍历列表时写range(len(list))索引从0到len(list)-1完美覆盖所有元素。遍历列表本身更推荐直接用for x in list这样不需要关心索引fruits [苹果, 香蕉, 西瓜] for fruit in fruits: print(fruit)但如果你既要索引又要元素可以用enumeratefor index, fruit in enumerate(fruits): print(index, fruit)3.3 循环变量的作用域与命名建议循环变量比如i在循环结束后依然存在于当前作用域里这在Python和部分语言不太一样。上一段for i in range(5)执行完后i的值是4打印它不会报错。垂直滚动代码的时候如果同一个文件里后面恰好用了叫i的变量可能会拿到这个遗留值。所以循环变量尽量选有意义的名称比如for user in users而不是无脑用i。3.4 实现嵌套循环九九乘法表实战掌握了单层循环嵌套循环可以让一个循环作为另一个循环的循环体。经典案例是打印九九乘法表。但先把丑话说在前面第一次写嵌套循环的人十有八九会在内层循环的变量名、重置时机上出问题。完整实现for i in range(1, 10): for j in range(1, i 1): print(f{j} × {i} {i * j}, end ) print()输出效果1 × 1 1 1 × 2 2 2 × 2 4 1 × 3 3 2 × 3 6 3 × 3 9 ...这段代码的执行顺序值得反复揣摩。外层循环i从1到9每当i确定一个值内层循环j从1跑到i内层跑完后执行print()完成换行然后外层i再取下一个值。内层循环每轮都会从j1重新开始这是嵌套循环正常工作必须依赖的性质——每轮内层循环都会重新初始化自己的循环变量。当初我在课堂上讲这段代码时总会有同学问为什么不直接print行字符串因为写这段代码的目的就是让你体会内外层循环各自的职责以及在循环体里做计算的模式。这才是以后处理复杂问题的基本功。4. 完整程序实操从需求到代码一步一步实现4.1 用例一登录重试次数限制程序需求用户输入用户名和密码如果正确就显示欢迎信息并结束如果错误提示剩余重试次数最多能试3次全部失败则提示账号已锁定。这个程序把分支、循环、break、状态标志位全部串起来了correct_user admin correct_password 123456 max_attempts 3 for attempt in range(max_attempts): input_user input(请输入用户名: ) input_password input(请输入密码: ) if input_user correct_user and input_password correct_password: print(登录成功欢迎回来) break else: remaining max_attempts - attempt - 1 if remaining 0: print(f用户名或密码错误还剩 {remaining} 次机会) else: print(尝试次数已用完账号已锁定)这里为什么用for而不是while因为重试的次数上限是固定的3次循环次数提前知道用for更简洁。注意break的位置只有登录成功才break失败时通过remaining合计剩余次数。计算remaining用的max_attempts - attempt - 1每次循环attempt是当前第几次从0计数所以第一次失败剩余3-0-12次第二次失败剩余3-1-11次第三次失败剩余0输出锁定提示。运行时会发现一个问题如果前两次失败最后一次尝试再失败时else分支内的remaining为0不会执行那个带剩余次数的print而是走“尝试次数已用完”。这个分支逻辑是闭环的。如果你希望失败信息覆盖“输入错误”和“机会用尽”两种情况直接用while循环也一样可以表达attempt 0 while attempt max_attempts: input_user input(请输入用户名: ) input_password input(请输入密码: ) if input_user correct_user and input_password correct_password: print(登录成功欢迎回来) break attempt 1 print(f用户名或密码错误剩余 {max_attempts - attempt} 次机会) else: print(尝试次数已用完账号已锁定)while版本有一个很容易被忽视的表达技巧for版本里用户到底在哪一次输错需要靠计算来判断while版本中保持一致——不管你成功还是失败每失败一次次数就减一。4.2 用例二计算平均值的健壮版本需求依次输入多个整数以输入“结束”为止计算平均值并输出。同时要防止用户输入非数字值导致程序崩溃。total 0 count 0 while True: value input(请输入一个整数输入q结束: ) if value.lower() q: break if not value.isdigit(): print(输入无效请重新输入整数) continue number int(value) total total number count count 1 if count 0: average total / count print(f共输入了 {count} 个数字平均值为 {average:.2f}) else: print(没有输入任何有效数字)这个例子里有两个典型设计一是while True加break适合退出条件“不确定什么时候出现”的场景二是isdigit()做输入校验防止int(value)收到非数字字符而抛异常。循环里用continue处理非法输入可以保证只有合法的数字才进入求和逻辑。有人会问为什么不能先算int(value)再用try/except当然可以但那一讲还没讲异常处理这里用isdigit()是更基础的防线。注意isdigit()对负数会返回False“-5”会被拒绝。如果想支持负数需要改成更完整的校验或者干脆用try/except。数据结构里处理用户输入永远比想象中的复杂这种防御性编程的意识从一开始就要有。4.3 用例三判断质数并输出1到N之间的所有质数质数判断是分支循环练习里最常见的题目因为它的逻辑天然需要嵌套。先写单个数字判断num 17 is_prime True if num 2: is_prime False else: for divisor in range(2, num): if num % divisor 0: is_prime False break if is_prime: print(f{num} 是质数) else: print(f{num} 不是质数)range(2, num)需要理解如果num是2range(2, 2)是空范围循环不会执行is_prime保持True所以2能被正确判断为质数。如果num是一个非质数比如9循环会跑到divisor3此时9 % 3 0置is_prime为False并break节省了后面不必要的工作。可以做一些效率优化判断到平方根即可。因为如果一个数有大于它平方根的因子那必然有小平方根因子与之对应。代码改成import math num 17 is_prime True if num 2: is_prime False else: limit int(math.sqrt(num)) 1 for divisor in range(2, limit): if num % divisor 0: is_prime False break再加一层外层循环输出2到100之间的所有质数import math primes [] for num in range(2, 101): is_prime True for divisor in range(2, int(math.sqrt(num)) 1): if num % divisor 0: is_prime False break if is_prime: primes.append(num) print(primes)这里还有一个容易出错的点is_prime这个标志位必须在每个num判断之前重新赋值成True。把is_prime True拿到外层循环外面会导致第一个质数判断完成后后续所有数字都被错判。这种“标志位忘记重置”的问题非常典型。5. 典型错误与调试技巧实录5.1 高频错误速查表错误现象常见原因修复方式if判断后代码块不执行条件表达式写错如把写成检查条件表达式比较相等用两个等号程序陷入死循环循环体内没有修改循环条件在循环体内让条件变量逐渐接近终止条件缩进错误IndentationError混合使用空格和Tab或缩进量不一致统一使用4个空格编辑器设置Tab展开为空格结果少循环一次对range的区间特性理解错误记住range(n)不包含n最末值为n-1嵌套循环结果错乱内层循环变量未重新初始化确认内层循环使用独立的变量名或每次都能重新赋值标志位残留外层循环没重置标志变量把标志变量的赋值放在外层循环体内开头5.2 逻辑错误实例复盘猜数字游戏有一次我在课堂上给学生演示猜数字游戏要求猜1到100之间的随机数每猜一次提示太大或太小直到猜对为止。学生的第一版代码是这样import random secret random.randint(1, 100) while guess ! secret: guess input(请猜一个数字: ) guess int(guess) if guess secret: print(太大了) elif guess secret: print(太小了) print(猜对了)这段代码的问题非常隐蔽while条件里用到的guess变量在循环第一次执行之前还没有被定义。程序运行到while guess ! secret时直接报错。这不是逻辑错误是变量初始化缺失。修复方式很简单进循环前先让用户输入第一个猜测值或者把guess初始化成一个肯定不会等于secret的值。这个实例要说的核心观点是循环条件是整个循环的第一道闸门闸门开启之前你要保证闸门涉及的变量已经存在并且是合理的初始值。再看一个更隐蔽的错误。同一个游戏的另一种写法import random secret random.randint(1, 100) guess 0 while guess ! secret: guess input(请猜一个数字: ) guess int(guess) if guess secret: print(太大了) elif guess secret: print(太小了)这个能运行但有个边界问题如果随机数恰好是0那第一轮判断guess ! secret就不成立循环一次都不跑直接输出“猜对了”。这就是bug。实际修正把初始化值设为一个明显不可能的值比如-1或者先让它输入一次再进循环。5.3 调试技巧用print观察执行路径新手调试分支循环最好用的工具其实还是print。在循环体开头打印当前循环变量的值在分支入口打印进入了哪个分支都能快速定位逻辑问题。比如调试质数输出程序时可以在外层循环里临时加一行print(当前判断数字:, num)内层循环如果发现整除打印divisor。看过几轮输出后基本就能确定标志位重置的问题出在哪。控制台里看循环执行过程要比在脑子里空想快得多。等程序改对以后把这些临时打印删除即可。很多开发者用这种“print大法”调试了几千个问题依然是最高效的手段之一。5.4 新手避坑心得写循环和分支时我最后给你几条实操经验。第一条件少的判断用if就够条件多要按从窄到宽的顺序排列。判断学生的成绩等级时“优秀”要在“及格”前面逻辑上谁更具体就先判断谁。第二确定循环次数用for range不确定的用while True加break。不要反着来固定循环次数偏用while容易多写一段计数器代码退出条件复杂偏用for range最后总要借助break才能跳出代码绕了一圈。第三凡是输入操作无论来自键盘还是文件先想清楚“输入构造是否合法”。就算没有系统学过异常处理也尽量养成用isdigit()校验数字输入的习惯。很多项目卡在用户乱输入导致崩溃其实根子就在最开始的防御性编程没做。第四逻辑上拿不准就跑起来看。print临时调试产生的输出会告诉你程序正在执行的每一步。自己动手跑几遍效果胜过看了一百遍教学视频。验证一段代码能不能跑永远靠运行而不是靠盯着屏幕左边分析。把这一段代码分行执行在关键的地方打印输出很快就能看到程序的真实路径。这比任何高级调试器都更能锻炼直觉也是这一讲里最需要你亲手去做的练习。