Python循环结构详解:从while到for,实战凯撒密码加密
2026/9/15 5:02:41 网站建设 项目流程

1. 先聊聊:为什么循环是入门Python的第一道分水岭

我自己带过不少新人,发现一个很有意思的规律:学变量、学数据类型,大家进度都差不多,但一讲到循环结构,就开始有人掉队了。原因很简单——循环不是"记住语法就行"的知识点,它要求你换一种思考方式:从"我要写每一步"变成"我要描述规则,让计算机自己重复执行"

你想一下,如果要你打印100个数字,你不会真的写100行print。你要告诉计算机的是"从1开始,每打印一个就加1,直到超过100就停",这就是循环。再比如处理一个Excel表格里的5000行数据,写循环和不写循环,工作量是天壤之别。

这篇文章不打算把循环结构讲成教科书式的语法罗列,我就站在一个实际写代码、踩过不少坑的人的角度,把while、for、break、continue这些东西彻底掰开揉碎。顺便用搜索热度里那个"while循环实现凯撒密码加密"做一次完整实战,代码可以直接抄走改改。

不论你是刚装好Python、准备配置VSCode环境开始写第一行代码,还是已经写过几天脚本但对循环总有种"会用但讲不清"的感觉,这篇文章都适合你。看完之后不敢说让你成为循环大师,但至少写循环的时候,心里是踏实的。

2. 循环前的热身:先搞清楚两种循环的定位

2.1 while循环:只要条件成立,就一直转

while循环的核心逻辑就一句话:先判断条件,条件成立就执行,执行完再回来判断,直到条件不成立为止

count = 0 while count < 5: print("当前计数:", count) count += 1

这段代码的逻辑过程是:

  • 第一次进来,count=0,判断0<5,成立,打印,count变成1
  • 再判断1<5,成立,打印,count变成2
  • 一直重复,直到count=5,5<5不成立,循环结束

很多新人第一次看这段代码,最想问的问题是:为什么要写count += 1?不写会怎么样?

不写的话,count永远是0,0<5永远成立,程序就永远不退出。这就是传说中的"死循环"——你的程序卡在那里,什么都不干,就是不停转。这就像你让一个人一直往前走,但没告诉他什么时候停下来,他会走到天荒地老。

2.2 for循环:明确知道要遍历多少个元素

for循环的逻辑是另一套:从一个序列(列表、字符串、range对象等)里一个一个取出元素,取完就结束

for i in range(5): print("当前是第", i, "次循环")

同样是循环5次,for循环看起来更简洁,因为你不必手动维护计数器。这个"别手动维护计数器"的特点,直接让for循环成为处理列表和字符串的首选。

有人可能会问:那while是不是多余的?不是。for循环适合"知道循环多少次"或"遍历已知序列"的场景,while循环适合"不知道循环多少次,只知道什么时候停"的场景

举个例子,写一个猜数字游戏:用户输入一个数字,程序判断是否猜对。你根本不知道用户要猜几次,这时候for循环就不太合适了,while就刚好,因为你只关心"没猜对就继续循环"这个条件。

3. while循环深度解析

3.1 while的标准结构与控制流程

while循环的标准语法比我上面写的稍微完整一点,就两行:

while 条件表达式: 循环体

流程图在我脑子里大概是这样的:判断条件(True/False)→ True就进入循环体执行代码 → 执行完后回到判断 → False就直接跳过整个while块。这个环节最容易被忽略的地方是:循环体里一定要有能让条件趋于False的代码,比如改变计数器的值,或者改变条件的输入。

我见过很多新手写这样的代码:

# 错误示例 i = 0 while i < 10: print(i) # 忘了写 i += 1

运行这个代码,程序会疯狂打印0,你只能按Ctrl+C强制终止。在VSCode的终端里,如果是Windows系统可能还要用Ctrl+Break,非常麻烦。

3.2 我常用的while场景:用户输入验证

实际开发中,while最常见的用途之一就是输入验证。比如你写一个程序,要求用户输入一个1到100之间的分数,用户可能输错很多次,你不想每次输错都让程序崩溃,这时候while就派上用场了:

score = int(input("请输入分数(1-100):")) while score < 1 or score > 100: print("输入无效,分数必须在1到100之间") score = int(input("请重新输入分数(1-100):")) print("有效分数为:", score)

这里while的条件是"score小于1或大于100",意思就是:只要输入不合法,就一直重新输入,直到合法为止。这个逻辑用for循环很不好写,因为你不知道用户会错几次。

注意一下这个场景里的一个关键细节:为什么while前先要写一次input?因为while第一次判断前必须有score的值,如果连输入都没有,条件没法判断。这种"先取一个值,再进入循环判断"的模式在编程里很常见,你以后还会在很多地方见到它。

3.3 写while循环最常见的三个坑

第一个坑刚才说了:忘了更新循环变量,导致死循环。

第二个坑是条件边界写反或写错。比如想打印1到10,你写的条件是while i < 10,结果只打印1到9。想要包含10应该写while i <= 10。这个"等于号边界问题"是所有语言初学者都会遇到的,记住一个排查方法:用程序会在"条件刚好成立"和"条件刚好不成立"的那两个值各走一遍

第三个坑比较隐蔽:循环变量的初始值设置不当。比如你从0开始计数,但实际数据是从1开始的,最后结果总是少一个或多一个。我习惯写循环前先在草稿纸上推演一遍,从初始值到循环结束的每个值变化都写出来,确认没问题再动手。

4. for循环与range()的搭配技巧

4.1 range()函数的三种用法,必须刻在脑子里

range函数是for循环最常用的搭档,它有三种标准用法:

# 用法1:range(stop),从0到stop-1 for i in range(5): print(i) # 0 1 2 3 4 # 用法2:range(start, stop),从start到stop-1 for i in range(2, 6): print(i) # 2 3 4 5 # 用法3:range(start, stop, step),第三个参数控制步长 for i in range(1, 10, 2): print(i) # 1 3 5 7 9

留意到没有,range的结束值永远是取不到的。这是设计上的一致性,Python里很多切片操作也都是"含左不含右"的规则。刚开始你可能觉得别扭,但习惯之后会发现这个规则让很多代码的推导变得统一。

4.2 for遍历字符串、列表、字典

for的精髓不在于range,在于"遍历一切可迭代对象":

# 遍历字符串 for char in "hello": print(char) # 依次输出 h e l l o # 遍历列表 fruits = ["苹果", "香蕉", "橙子"] for fruit in fruits: print(fruit) # 遍历字典(默认遍历键) scores = {"语文": 90, "数学": 85, "英语": 92} for subject in scores: print(subject, scores[subject]) # 遍历字典items for subject, score in scores.items(): print(subject, "的分数是", score)

这几个例子合在一起,其实是Python的一个核心思想:不需要你费劲去算索引,循环本身就会把每个元素递到你手上。新手可能一开始不习惯,反而会写:

for i in range(len(fruits)): print(fruits[i])

这代码没错,但在Python里属于"绕远路"。尤其是在实际开发里,这种写法可读性差,团队协作时别人一眼看不出你要干什么。除非你真的需要用到索引位置,否则直接for item in list才是Pythonic的方式。

4.3 enumerate:既要元素又要索引时的救命稻草

说了除非需要索引,那真的需要索引怎么办?用enumerate函数:

fruits = ["苹果", "香蕉", "橙子"] for index, fruit in enumerate(fruits): print(index, fruit) # 输出: # 0 苹果 # 1 香蕉 # 2 橙子

这个函数会让循环体同时拿到"第几个"和"元素本身"。处理列表逐行输出时特别像打印一份带序号的名单,实用性很高。

实际项目里,enumerate常用于调试时定位是哪一行数据出了问题,你也可以在循环里配合条件判断,只打印出满足某些条件的元素和它的索引,效率比盲找快很多。

5. break、continue与循环else:给循环装个"控制开关"

5.1 break:条件满足就立刻终止整个循环

有时候循环需要"半路下车"。比如寻找第一个能被7整除的数:

for i in range(1, 100): if i % 7 == 0: print("找到了:", i) break # 找到第一个就停,不再往下找

break的执行效果是:无论循环条件还成不成立,循环体有多少行没执行,直接跳出整个循环。这非常像你在一本书里翻目录找某一页,找到就合上书,不再翻后面了。

我用break最多的场景是配合while做一个"退出标志":

while True: command = input("请输入命令(输入q退出):") if command == "q": break print("执行命令:", command)

这里while True本身是个死循环,但因为循环体里有break,所以"死循环"只是表面看起来死,实际是:会在满足一定条件后主动退出。这种模式在网络请求、事件处理里非常常见。

5.2 continue:跳过本次循环,直接进入下一次

continue的语义是:本次循环到这里结束,不再执行continue之后的代码,回到循环开头执行下一次

for i in range(1, 11): if i % 2 == 0: continue # 偶数跳过,不打印 print(i) # 只打印1 3 5 7 9

这个场景你可能会想,用if+not的组合也能实现,确实是。continue真正的价值在于:当循环体有很多行代码,而你只想在特定条件下跳过后面那些行。不用continue的话,你得把后面所有代码都包进一个巨大的if里,缩进一层又一层,代码又丑又难改。

我自己的一个习惯是:如果continue和if/else都能实现,优先选可读性更好的那种,不为了用continue而用continue。如果后面的代码不多,直接if条件判断更直白;如果后面有十几行甚至几十行代码,continue能帮你避免一次地狱级缩进。

5.3 while/for后面的else:没人告诉你的隐藏特性

Python里循环后面可以跟一个else,这个语法比较冷门,但很有用——else里的代码在循环正常结束时执行,如果是被break结束的,else不执行。看例子:

for i in range(2, 10): if i % 3 == 0: print(f"{i}能被3整除") break else: print("2到9之间没有能被3整除的数")

这个代码会break掉,所以else里的print不会输出。再换个例子:

for i in range(2, 5): if i % 10 == 0: break else: print("没有找到能被10整除的数")

2、3、4都不能被10整除,循环正常走完,else里的内容就会输出。

说实话,这个特性一开始我也觉得鸡肋,但在**"查找失败"**的场景里非常好用。比如在一堆数据里搜索关键词,找到就break,没找到就走else提示"无结果"。比用标志变量记录"是否找到"再判断要省好几行代码。

6. 嵌套循环与性能意识

6.1 嵌套循环的逻辑:外层跑一次,内层跑一整轮

嵌套循环的本质是"循环里套循环"。外层每执行一次,内层就要从头到尾跑一遍:

for i in range(1, 4): # 外层循环 for j in range(1, 4): # 内层循环 print(i, j)

输出结果是9行,因为外层3次,内层每次3次,3×3=9。这个乘法关系一定要想明白,它是理解后续算法复杂度的起点。

嵌套循环最常见的应用是打印乘法口诀表

for i in range(1, 10): for j in range(1, i + 1): print(f"{j}×{i}={i*j}", end="\t") print() # 外层每循环一次,换一行

注意这里内层循环的范围是range(1, i+1),意思是:第几行就打印几个式子。这就是靠外层循环的i值动态控制内层循环的次数。

6.2 循环次数爆炸的隐患

如果你有两层循环,每一层都有1000次,那么总循环次数就是1000×1000=100万次。Python跑100万次不慢,但如果里面还有复杂操作,或者变成三层循环,性能就会肉眼可见地下降。

所以写嵌套循环时,我几乎下意识会问自己三个问题:

  • 内层循环能不能提前break?能的话一定要加
  • 能不能把内层循环改成集合查找或字典查找?很多"双循环找交集"的场景,用集合能一行搞定
  • 数据量会不会很大?如果会,要不要考虑用numpy之类的库替代纯Python循环

7. 完整实战:用while循环实现凯撒密码加密

7.1 什么是凯撒密码

凯撒密码是一种非常古老的加密方式,原理很简单:把字母按照字母表顺序往后移动固定的位数。比如把字母表向右移动3位,a变成d,b变成e,c变成f,以此类推。到了末尾的x、y、z,就绕回开头变成a、b、c。

明文字符串"hello"用密钥3加密后就变成"khoor"。解密就是反向移动,知道密钥就能还原。

7.2 核心思路拆解

实现凯撒密码的难点不在于加密逻辑本身,而在于处理字母的循环绕回。字母z加了3位,不能变成ASCII表里的某个奇怪字符,要绕回到c。

我拆解一下思路:

  1. 先拿到每个字符的ASCII码(Python内置函数ord),比如ord('a')是97,ord('z')是122
  2. 给这个码加上密钥,得到新码
  3. 判断新码是否超过了122,如果超过就减去26让它绕回a-z的范围
  4. chr函数把新码转回字符

但在动手写代码之前,还要处理一个细节:如果字符串里有大写字母、空格、标点符号怎么办

7.3 用while循环实现

既然这个实战项目点名要while循环,我就全程用while来写,不偷懒:

def caesar_encrypt(text, shift): result = "" index = 0 while index < len(text): char = text[index] # 只处理英文字母,其他字符(空格、标点、数字)原样保留 if char.isalpha(): ascii_offset = 65 if char.isupper() else 97 new_ascii = (ord(char) - ascii_offset + shift) % 26 + ascii_offset result += chr(new_ascii) else: result += char index += 1 return result plaintext = input("请输入要加密的明文:") key = int(input("请输入加密密钥(整数):")) ciphertext = caesar_encrypt(plaintext, key) print("加密结果:", ciphertext)

这里有个很关键的技巧:(ord(char) - ascii_offset + shift) % 26 + ascii_offset

我给你拆解一下这个公式为什么这么写。假设字符是'a',ascii_offset=97,shift=3。计算过程是:97减97等于0,0加3等于3,3对26取模还是3,3加97等于100,100对应的字符就是'd',正确。

再试'z',ascii_offset=97,ord('z')=122,122减97等于25,25加3等于28,28对26取模等于2,2加97等于99,99对应'c'。你看,z成功绕回开头变成了c,不需要用if去判断是否越界。

取模运算(%)是处理循环绕回的第一选择,相信我,别用if硬判断,代码又短又不容易出错。

7.4 while中的计数器管理

整个循环里最关键的是最后那个index += 1。我故意把这三行代码拉开距离写,是为了让你看清:while循环里,你用什么变量控制循环,那个变量就一定要在合适的地方更新

在这里,index从0开始,每次处理一个字符就加1,当index等于字符串长度时,条件index < len(text)不成立,循环结束。和for循环的range自动管理计数器不同,while的这个"手动感"反而让你更清楚地看到循环是怎么推进的。

7.5 顺手加一个解密函数

加密有了,解密就是把shift换成负数,其实同一个函数就能搞定:

def caesar_decrypt(text, shift): return caesar_encrypt(text, -shift) decrypted = caesar_decrypt(ciphertext, key) print("解密结果:", decrypted)

因为加密是往右移动shift位,解密就是往左移动shift位,也就是往右移动-shift位。数学上完全对称,所以一个函数直接复用。这个设计也提醒我写函数时尽量保持"通用性",不要写在某个场景里就绑死。

7.6 运行效果演示

我在VSCode终端里跑了一下这段程序:

请输入要加密的明文:hello world 请输入加密密钥(整数):3 加密结果: khoor zruog 请输入要加密的明文:Python is fun 请输入加密密钥(整数):5 加密结果: Udymts nx kzs

第一行里h→k、e→h、l→o、l→o、o→r,空格原样保留,w→z、o→r、r→u、l→o、d→g。第二行里大写P→U,其余小写字母分别移动5位,空格保留。加密结果完全符合预期。

这个项目如果让新人从头写,卡壳的地方基本集中在两个:一个是chrord这两个函数之前没用过,另一个就是取模运算处理字母绕回这个思路。真明白之后,凯撒密码就是个小玩具级别的程序,但它能把while循环、字符串处理、条件判断、数学运算全部串起来,所以我一直觉得它是很好的练手项目。

8. 循环相关的常见问题排查速查表

我在带人的过程中,整理了一份循环相关的高频问题表,新手按图索骥效率会高很多:

症状可能原因排查方法
程序运行后卡住不动,CPU占用高死循环:循环变量未更新或条件永远为True检查循环体里有没有改变条件的语句;临时在循环体里加print看执行情况
循环次数比预期少一次边界条件用了<但应该用<=推演边界值,看程序是否会处理最后一个元素
循环次数比预期多一次等于号边界处判断错误检查循环条件的小于号/大于号两侧是否应该包含等于
只在最后一轮循环报IndexError列表索引越界,循环内用的索引超出列表长度检查len和索引的关系,range(len(list))是可以的,但list[i+1]要小心最后一位
循环里有break但没生效break被写进了if的嵌套块外面检查break的缩进层级,break必须和if在同一层级且在循环体内部
修改列表时循环结果诡异在循环过程中删除或添加列表元素导致索引错乱不要在for循环中直接修改列表长度,可以用列表推导式或先复制一份再处理
函数里的while循环不退出return和break没有正确放置确认循环体内是否在条件满足时遇到了return或break
VSCode终端里循环输出太快看不清缺少延时或输出内容太多临时加import time; time.sleep(0.1)或者只print部分变量

这张表里,第一个"死循环"是出现频率最高的问题,几乎每个初学循环的人都至少遇到一次。我的建议是:先别急着删代码,按Ctrl+C强行终止程序,然后回去检查循环体里有没有让条件趋于False的语句。这个方法比开调试器还快。

还有一个我踩过挺多次的坑是"for循环内修改列表"。举个最经典的例子,你想删除一个列表里所有等于3的元素:

nums = [1, 3, 2, 3, 4, 3] for num in nums: if num == 3: nums.remove(num)

运行完之后,列表变成了[1, 2, 4, 3],最后一个3还在。原因是循环过程中列表的长度变了,Python的for循环是从头到尾按索引推进的,删除元素后列表元素整体前移,循环索引却照样往后移动,于是跳过了邻近的未处理元素。

这种问题就不会出现在while循环里,因为你用的是手动索引:

nums = [1, 3, 2, 3, 4, 3] i = 0 while i < len(nums): if nums[i] == 3: nums.pop(i) # 删除后,索引不增加,因为后面的元素已经前移了 else: i += 1

删除时i不增加,这样才能保证每个元素都检查到。但说实话,Python里处理这种需求更优雅的方式是列表推导式:

nums = [num for num in nums if num != 3]

一行搞定,而且完全避开索引问题。这也从侧面说明:while循环的"手动索引"既是它的灵活之处,也是它的风险来源

9. 调试循环的实用技巧与心得

9.1 打印法调试:最简单但别小看它

我在实际调试循环时,90%的情况是用print打印关键变量的值。不要觉得打印法低级,它其实是理解循环执行过程最快的方式。当你不知道循环为什么多跑了一次时,在循环开头打印一下计数器:

i = 0 while i < 5: print(f"循环开始,i = {i}") if i == 2: print("命中if分支,i=2") i += 1 print(f"循环结束,i 更新为 {i}")

看输出里的i值变化,你立刻就能定位问题出在哪一轮,走的分支是什么。这比盯着代码脑内模拟高效得多,尤其是嵌套循环时,打印出"外层第几次、内层第几次"能帮你迅速定位循环层级之间的逻辑错误。

9.2 VSCode断点调试:当打印法不够用的时候

当循环逻辑比较复杂,或者你需要在特定条件下暂停检查所有变量时,用断点调试就更合适了。VSCode里操作很简单:在行号左侧点一下出现红点,然后按F5启动调试,程序运行到这一行就会停住。左侧面板可以看所有变量的当前值,顶部的调试工具栏能控制"继续""单步跳过""单步进入"。

调试循环时,我最常用的两个调试操作是"单步跳过"和"断点命中计数"。单步就是一行一行地走,观察循环变量的变化节奏;断点命中计数是在红点上右键设置"命中条件",比如设置i == 3时才停下来,不用从第一个断点开始一步步按到第100次。

9.3 在循环里写注释的习惯

这个习惯是我被坑了几次之后才养成的。凡是写法上有"隐含逻辑"的循环,我都会写一行注释说明循环的退出条件是什么:

while index < len(text): # index逐个字符递增,当index等于text长度时退出循环 # 异常字符原样保留,避免破坏原始格式 ...

注释不用长,一两句话点明"为什么这么写"就够了。很多循环代码过了一个月自己也看不懂,"当时这个条件为什么要写<=而不是<"——有了注释,回忆成本急剧下降。

10. 最后分享几个让我编码效率翻倍的小习惯

先聊循环里的命名习惯。我见过不少人写循环时用i、j、k,但代码一复杂就分不清哪个对应哪个了。我现在写循环时会刻意用有语义的变量名:遍历列表用item,按索引遍历用idx,处理的字符用char。代码读起来顺了很多,出错也少了。

再聊优先选择最自然的循环类型。我的经验是:能确定循环次数就优先用for,不确定或用标志位控制的场景再用while。不要嫌for看起来简单就强行用while,也不必什么场景都套while True。让代码"像它本来的样子"是最舒服的。

还有一个小技巧是把循环逻辑提取成函数。如果一个循环体内部超过十行,我倾向把它单独写成函数,循环只做驱动,函数只做处理。这样测试循环时可以单独测函数,不用每次跑完整段程序。

说实话,循环结构这个知识点,学语法本身没有难度,真正区分代码水平的是能不能在合适的场景用合适的循环,以及能不能一眼看穿循环中的逻辑漏洞。我写这篇文章,最想传达的不是某个语法有多重要,而是希望你养成一种"用循环表达重复规则"的思维方式。

凯撒密码的完整代码我已经放在上面了,你可以直接复制动手跑一跑。改改密钥、试试中文和英文混排的字符串、或者把while改成for再看看效果,都是很好的练习方向。循环这个东西,光看永远觉得简单,真上手写到报错、调通、跑出预期结果,你才算真正学会。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询