很多人把“找第三章答案”当成一件抄作业的事,我不太认同。卡在这一章的人,多半不是不会写代码,而是没搞懂这章到底在考什么。你把题目做对了,却说不清楚为什么对,下一章一上难度马上原形毕露。这篇文章不会给你某种“标准答案全集”——因为我确实没有你的原题,但我可以给你更有用的东西:这一章练习题背后真正要掌握的知识点、常见题型怎么拆解、格式化输出那些极其容易丢分的小细节,以及一套能自己验证答案对错的流程。只要题目用的是这本教材第三章的基本数据类型知识,看完整篇,绝大多数题你都能自己解出来,而且知道每一步在干什么。
1. 为什么多数人做这章练习时会卡住,以及这篇内容能帮你解决什么
1.1 搜索“题目答案”背后的真实困境
我带过不少零基础的学生,发现他们做第三章习题时,场景几乎一模一样:打开作业题,读了三遍题目,感觉自己听懂了课堂内容,但一写代码就不知道从哪里下手。有些题看似简单,比如“输入一个整数,计算它的位数并输出各位数字之和”,但是真上手就会发现,怎么把每一位拆出来?用字符串还是用算术?要不要考虑负数?这些细节课堂上一带而过,到了作业里全是坑。
还有一类题卡在输出格式上。题目明明写着“输出结果保留两位小数,宽度为10,右对齐”,很多学生调用格式化函数时报错,或者格式虽然对了,但题目要求的“逐字一致”没做到,测试点就是过不去。这根本不是不会写代码,而是还没建立起“程序输出要对标题目要求”的严谨习惯。
另一类学生的问题恰恰相反——他们能跑通代码,但不确定自己写的对不对。比如一道题问“3 // 2 的结果是多少”,他们写出了 1,但心里其实在打鼓:如果换成 -3 // 2 呢?如果换成 3.0 // 2 呢?一旦题目换个数字,心里就没底了。这属于知识点没有形成体系,靠记忆单个答案而不是理解运算规则。
1.2 这篇文章的定位:思路、方法与自查,而不是简单抄写标准答案
所以这篇文章的定位很清楚:不提供某个具体版本的逐题答案,而是把第三章程序练习题涉及的全部核心能力拆开揉碎讲清楚。我会先带你过一遍这章大多数习题都在考的底层知识点,包括数字类型的边界行为、字符串索引切片的运作方式、格式化输出的精确控制;然后再给几类典型题目的完整解题思路和一个可以直接参照的实现;最后重点讲怎么验证你的答案对不对——这才是很多学生根本没建立起来的习惯。
说白了,你要是认真跟下来,以后再遇到类似的题,哪怕题目里换了数字、换了表述,你也能自己写出来。这比拿到一份“现成答案”有用得多,因为你真正要做的是把知识变成自己的。
2. 第3章习题背后真正要掌握的核心知识点
先别急着写代码。这一章几乎所有练习都建立在一组基础知识点上,我把它们分成三块:数字类型的运算规则、字符串的操作方法、输入输出与类型转换的细节。这三块交叉组合,基本上就能覆盖百分之八九十的常见题目。
2.1 数字类型与运算符的边界条件
整数、浮点数、复数,这三种数字类型本身不难,难在它们的运算规则和边界行为。作除法时,/永远返回浮点数,哪怕两个整数除得很干净。//是向下取整,不是简单地“去掉小数部分”,负数场景下很容易反直觉:-3 // 2的结果是-2,因为-1.5向下取整得到-2,很多人潜意识里觉得应该是-1,这就是一个非常典型的丢分点。取余%的符号跟随除数(右侧的数),这些规则平时看着不起眼,习题里专门会出。
另一个高频考点是运算符优先级。**的优先级高于*、/、//、%,而且**是右结合的,2 ** 3 ** 2会先算3 ** 2 = 9,再算2 ** 9 = 512,而不是(2 ** 3) ** 2 = 64。这类题目几乎每轮都会出现,做的时候必须把结合方向刻在脑子里。
浮点数还有一个著名的坑:0.1 + 0.2 不等于 0.3。这不是 Python 的 bug,而是二进制浮点数无法精确表示某些十进制小数的必然结果。习题如果让你比较浮点数是否相等,直接用==很容易翻车,常规做法是比较它们的差是否小于一个极小值,也就是所谓“容差判断”。没有学过这个技巧的话,遇到这类题基本只能靠蒙。
2.2 字符串的三板斧:索引、切片、方法链
字符串是第三章练习题的另一个重灾区。索引最容易被忽略的是负数索引:s[-1]是最后一个字符。切片要把握住三个参数的含义:s[start:end:step],起始位置包含,结束位置不包含,步长决定方向。s[::-1]能把字符串倒过来,这几乎是回文题的通用解法。很多学生能写出s[0:3],但稍微换个写法,比如s[3:0:-1],就完全蒙了,其实只要记住“结束位置不取”这条规则就能推理出来。
字符串方法里,strip()、split()、join()、replace()、find()、count()、upper()、lower()、isdigit()、isalpha()这些是习题常客。要注意两点:一是这些方法大部分会返回一个新字符串,不会修改原字符串,所以你要用变量接住返回值;二是方法可以连着用,比如s.strip().split(","),这叫方法链。练习题里经常出现“先清洗、再拆分、再统计”的三步操作,方法链就是为这种场景准备的。
字符串判定类方法容易被忽略,但其实非常重要。比如判断一个字符串是否全是数字用isdigit(),判断是否全是字母用isalpha()。字符统计题里,如果一个字符是空格但既不是数字也不是字母,你要知道用isspace()或者直接判断ch == " "。这些方法的名字都不难记,难的是在题目场景里想到去用它。
2.3 格式化输出与 type/eval 这两个经常被忽略的细节
输入输出部分的习题,几乎都逃不开格式化输出。常见的方式有三种:传统的%占位符、format()方法、f-string 字符串。对于练习题来说,考官想验证的通常是:小数位数、对齐方式、填充字符、宽度、是否换行。这些细节我会在第四章单独用一整节来讲,因为这里特别容易扣分,值得单独展开。你先知道一点:输出题的基本原则是“题目怎么描述,输出必须逐字对齐”,包括空格、冒号、换行符都算数。
type()函数用来查看一个数据的类型,练习题里经常让你判断type(1)、type(1.0)、type("1")的结果分别是什么,答案分别是<class 'int'>、<class 'float'>、<class 'str'>,注意要写完整格式。eval()则是一个能“把字符串当成代码执行”的函数,很多题目会让用户输入1 + 2,然后通过eval(input())直接得到数字 3。这招在需要把输入快速转成数字、甚至计算表达式的题目里非常实用,但也要注意它是一把双刃剑,使用场景通常由题目限定,自己写程序时不要随便对未知输入用它。
3. 常见题型的完整解题思路与一份可参照的实现
3.1 表达式结果分析题的思考顺序
有一类题不要求写完整程序,只要求写出某个表达式的结果。这种题的解题顺序应该固定为:先确定运算符优先级,再确定结合方向,最后代入具体的数做运算。我拿几个典型例子演示一下:
# 示例 1:混合运算 print(2 + 3 * 4 ** 2) # 先 4**2=16,再 3*16=48,最后 2+48=50 print(10 / 3) # 3.3333333333333335,浮点数 print(10 // 3) # 3 print(-10 // 3) # -4,向下取整 print(10 % 3) # 1 print(-10 % 3) # 2,余数符号跟随除数思考顺序的价值在于,它能让你在复杂表达式前不慌。你先把优先级最高的部分用括号在心里框出来,然后一层一层往下剥。这样哪怕题目原题的数字和我给的完全不同,你掌握的也是步骤而不是答案。
# 示例 2:字符串表达式 s = "Python" print(s[0:3]) # Pyt print(s[::-1]) # nohtyP print(s[::2]) # Pto,隔一个取一个 print(len(s)) # 6字符串表达式的分析思路是:先看操作的是索引还是切片,再确认切片参数里的冒号有几个、数字分别代表什么,最后心算一遍。不要一上来就去 IDE 里跑,先自己算,再验证,这样训练效果才最好。
3.2 与 time 库相关的题目怎么处理
第三章的程序题里经常会用到time库,常见的题目方向有两个:一个是“暂停几秒再输出”,用time.sleep(n);另一个是“记录程序运行时间”,用time.time()或time.perf_counter()。
import time # 题目要求:输出当前时间对应的秒数 now = time.time() print(now) # 题目要求:暂停 2 秒后输出 print("开始") time.sleep(2) print("结束了")做这类题时最容易犯的错误是忘记导入模块,或者把函数名拼错。还有一个小细节:time.time()返回的是从某个固定时间点(通常是计算机系统的元年)到现在的总秒数,它包含小数部分,如果题目要求整秒,你需要用int()转一下。另外,如果题目要求“回车后计时”,那你需要先写input(),再取time.time()作为结束时间,这个顺序别写反。
3.3 几个经典小项目:位数求和、字符统计、秒数转换
下面这几个是实现题里反复出现的经典,我把完整代码和逐行解释放在一起。每个版本我都故意写得简洁,方便你对照着理解。
# 题目:输入一个整数,输出它的位数和各位数字之和 num = input("请输入一个整数: ") if num.startswith("-"): num = num[1:] # 去掉负号,否则 '-' 会被当成字符 total = 0 for ch in num: total += int(ch) print(len(num), total)这个解法先把数字当字符串处理,这是最稳妥的办法。如果题目要求用算术方式,那可以用整除和取余循环。但当一个初学者看到“输入数字”就下意识用int(input()),然后发现负数处理起来很麻烦时,不妨想一想:输入本身是字符串,很多字符串方法天然能解决问题。这种思路转换本身,就是做练习题要培养的能力。
# 题目:统计字符串中字母、数字、空格和其他字符的个数 s = input("请输入一段字符串: ") letters = digits = spaces = others = 0 for ch in s: if ch.isalpha(): letters += 1 elif ch.isdigit(): digits += 1 elif ch.isspace(): spaces += 1 else: others += 1 print("字母:", letters, "数字:", digits, "空格:", spaces, "其他:", others)注意我用了elif而不是多个独立的if,这样每个字符只会进入一个分支,结果才不会重复统计。输出格式也可以换成format()写法,我下面就会讲。
# 题目:输入秒数,转换成 时:分:秒 格式 total_seconds = int(input("请输入秒数: ")) hour = total_seconds // 3600 minute = (total_seconds % 3600) // 60 second = total_seconds % 60 print(f"{hour}:{minute}:{second}")这道题考的是整除和取余的组合使用。思路是把总秒数拆成“小时数 + 剩余秒数”,再从剩余秒数里拆“分钟数 + 秒数”。别想着一步到位,分两步拆,逻辑更清晰。如果题目要求补零,比如输出01:05:09,那就要用格式化输出的补零语法,我下一节会具体写。
4. 格式化输出题最容易被扣分,问题大多出在这几个地方
格式化输出看起来简单,实际上在程序练习题里扣分率特别高。我还记得有个学生写数字温度转换那道题,逻辑全对,最后因为输出里多了一个空格,测试点扣了一半的分数。从此我讲格式化输出都会专门强调:输出格式必须逐字对齐,宁可多检查三遍,也不要相信“差不多”。
4.1 % 格式、format 与 f-string 的换算关系
三种格式化方式学哪一种?教材版本不同,建议也不同。但如果你能把三种方式都看懂,做题时遇到哪种写法都不慌。
# 方式一:% 占位符 name = "Tom" age = 18 print("My name is %s, I'm %d years old." % (name, age)) # 方式二:format() print("My name is {}, I'm {} years old.".format(name, age)) print("My name is {0}, I'm {1} years old.".format(name, age)) # 方式三:f-string print(f"My name is {name}, I'm {age} years old.")%s接收字符串,%d接收整数,%f接收浮点数,这是最基础的一组对应关系。format()的{}位置参数可以不写编号,也可以写编号进行重复引用。f-string 在 Python 3.6 之后才可用,如果题目指定的运行环境比较旧,使用前要先确认版本。做题时我建议优先使用老师讲解的方式,但理解另外两种也能提高你在多种环境下的适应能力。
4.2 对齐、补零、小数位、百分号的写法对照表
这一小节的表格建议直接保存下来,做格式化输出题的时候对照着用。语法本身不难,难的是记住“到底哪个符号放在哪个位置”。
| 需求 | % 格式写法 | format 写法 | 示例输出(对数字 3.14159) |
|---|---|---|---|
| 保留两位小数 | "%.2f" % x | "{:.2f}".format(x) | 3.14 |
| 宽度为 8,右对齐 | "%8.2f" % x | "{:>8.2f}".format(x) | 3.14 |
| 宽度为 8,左对齐 | "%-8.2f" % x | "{:<8.2f}".format(x) | 3.14 |
| 宽度为 8,居中 | 不支持 | "{:^8.2f}".format(x) | 3.14 |
| 宽度为 8,补零 | "%08.2f" % x | "{:08.2f}".format(x) | 00003.14 |
| 千分位分隔符 | 较麻烦 | "{:,.2f}".format(12345.6) | 12,345.60 |
| 百分比显示 | "%.1f%%" % x | "{:.1%}".format(x) | 314.2%或31.4%取决于 x |
这里有两个特别容易踩的坑。第一,%格式化里如果要显示百分号本身,需要写%%,否则会报 “not all arguments converted” 之类的错误。第二,format()里的小数位、宽度、对齐符号都写在冒号后面,顺序依次是对齐方式、宽度、小数位,写反了虽然不报错,但结果会不对。我见过很多学生写"{:.2>8}".format(3.14)想右对齐补零,结果格式完全不对,就是这个原因。
补零还有一种常见需求:把整数按固定位数输出,比如123输出成00123。这个用"{:05d}".format(123)也是有效的,d前的05表示“宽度为 5,不够补零”。秒数转换题的01:05:09就是这种写法:
print(f"{hour:02d}:{minute:02d}:{second:02d}")4.3 输出格式与“题目要求”逐字对齐的自查方法
我建议你写完格式化输出后,养成一个固定习惯:把输出结果复制出来,和题目给的示例输出逐字对照,包括空格、换行、中文标点和英文标点。有一个非常简单的做法:先把示例复制成一行字符串,再把你程序的输出也合并成一行,然后用==比较,就能立刻发现哪里的字符不一致。
# 伪代码形态:把输出保存到变量再比较 expected = "My name is Tom, I'm 18 years old." actual = f"My name is {name}, I'm {age} years old." print(expected == actual) # True 或 False用这种方式自查,基本不会出现“提交后才发现输出多个空格”的惨案。如果题目不允许你打印这个布尔值,你就在本地调试完再删掉,提交时只留真正需要的输出语句。
5. 怎么验证你的练习答案对不对:一套可复用的自查流程
很多时候学生跑来问我:“我代码跑通了,但不知道对不对。”跑通不代表正确,尤其对于程序练习题,正确的定义是“在给定的多个测试输入上都得到符合要求的输出”。所以你要训练自己掌握一套自查流程,而不是只在一种输入上跑一次。
5.1 用断言或测试输入跑一遍边界值
我推荐用assert来写简单测试。对小题来说,这比写一个复杂的测试框架更直接。比如你写了一个计算整数位数和的函数,就可以这样验证:
def digit_count(n): s = str(n) if s.startswith("-"): s = s[1:] return len(s), sum(int(ch) for ch in s) # 用断言做边界测试 assert digit_count(0) == (1, 0) assert digit_count(12345) == (5, 15) assert digit_count(-987) == (3, 24) assert digit_count(100000) == (6, 1) print("全部通过")边界值包括:0、负数、最大值、最小值、重复数字、末尾有零,等等。这些情况最考验代码的健壮性。很多学生的代码在正数场景没问题,一遇到负数或者 0 就直接崩,就是因为没有做边界测试。养成一边写一边想“这个函数会遇到什么样的极端输入”的习惯,价值会持续到以后的每一章。
5.2 检查输入输出的类型匹配
第二个自查维度是类型。题目要求输出整数,你输出1.0,严格来说也算错。题目要求输入字符串,你却先int()转换,然后用不了字符串方法,也会卡住。我总结过一个简单的对照表,每次调试前扫一眼:
| 场景 | 输入侧常见问题 | 输出侧常见问题 |
|---|---|---|
| 题目说“输入整数” | 忘写int(),导致类型错误 | 输出print(3)是字符串?不,print总能输出任何类型,但格式可能不符 |
| 题目说“输入一行字符串” | 直接用int(input())导致崩溃 | 需要保留原字符串时不要提前转换 |
| 题目要求保留小数位 | 输入要转float(),转int()会丢小数 | 输出要用:.2f,不能只靠round()之类暗示 |
| 题目要求逐字符判断 | 注意for ch in s是字符,类型是 str | 比较时用ch == ' ',别写成数字 |
这类型的坑,靠读题就能避免一半。做题第一步永远是把输入要求读准,是“一行整数”还是“一个浮点数”?有没有可能包含负数?有没有可能包含空格?这些问题都想清楚了再动手。
5.3 把运行结果与预期结果做逐步对照
最后一个自查动作是“分步核对”。如果你用了中间变量,就把中间变量的值打印出来看一眼。例如字符串统计题里,你可以临时打印每个字符和它对应的判定结果,确认每个if / elif分支是否都进入了正确的方向。
s = "abc 123!!!" for ch in s: if ch.isalpha(): print(ch, "-> 字母") elif ch.isdigit(): print(ch, "-> 数字") elif ch.isspace(): print(ch, "-> 空格") else: print(ch, "-> 其他")验证完逻辑,再把临时打印删掉,输出保留题目要求的格式。这个“先调试后清理”的过程几乎每个专业开发者都会用,只不过他们已经有肌肉记忆了,看起来不费力气。你可以把这一套流程练成自己的固定动作,每次写完题目都走一遍:边界测试、类型检查、分步对照、格式清理。
6. 我在带学生过程中总结出来的练习建议和踩坑提醒
6.1 把例子代码“做手术”而不是只读一遍
第三章的教材里有许多例子代码,很多学生觉得“看懂了”就等于“会了”。我习惯的做法是让学生对例子做三件事:改数字、加需求、删步骤。改数字能帮你验证自己对逻辑的掌握;加需求能让你把学到的知识点组合起来用;删步骤能帮你搞清楚哪些行是核心、哪些行只是辅助。
比如教材里的某个示例用了%格式化,你可以尝试把它改成 f-string,或者反过来。这种“移植”训练看起来简单,其实比重新做十道题都有效,因为你在强迫自己理解语法背后的等价关系。遇到不想改的例子,至少也要在 IDE 里手动敲一遍,不要复制粘贴。复制粘贴会让你产生“我确实会了”的错觉,手动敲一次,错误才会暴露出来。
6.2 刻意练习优先级和边界条件
优先级和边界条件属于那种“看起来简单,永远有人错”的知识点。我给学生的建议是,每天抽五分钟,自己出五道小题口头算一遍:比如3 + 4 // 2、"abc" * 3、-7 // 2、2 ** 3 ** 2、10 % -3。不用开电脑,纯心算,然后再到终端里验证。
这种刻意练习的目的不是让你背答案,而是让你对运算规则形成直觉。等到考场上看到-7 // 2,你不再需要停顿,脑子里会直接浮现“结果是 -4”的图景。同一类题,练习十次和练习一次的区别,在考试时体现得非常明显。
另外一个我反复提醒的点:不要太早依赖自动补全。写字符串方法时,一旦 IDE 自动弹出strip、split、join,你就容易放弃记忆这些方法的名字和参数。平时刻意关掉提示,或者至少在记不准时才打开提示,能帮助你建立真正的长期记忆。
6.3 保存每个小作业的运行截图或输出日志
这一条听起来和“做题”无关,但实际帮助很大。每次完成一个小练习,我建议你用一个文本文件记录三样东西:题目要求、你的核心代码、输出结果。不用写得很长,就是给自己留个索引。等学到后面章节时,你想回头查阅一个格式化写法,直接在文件里搜索就能找到。
更重要的是,这个习惯能让你在复习时看到自己的“错误轨迹”。如果你在第一次做某类题时犯了错,把错误原因一并记录下来,比如“字符串切片结束位置忘减一”,那么下次遇到同类型题,你会比没有记录的人快很多回忆起这个坑。我一直觉得,学习编程最宝贵的资产不是答案本身,而是你踩过的坑和从坑里总结出来的规则。
第三章这一整章,本质上是把你从一个只会跟着例子敲代码的初学者,推向一个能独立分析问题、选用合适工具、验证自己产出的人。练习题的价值也在这里:它们不是为了让你背下某种标准答案,而是让你亲手把知识点转化成解决问题的能力。你花在这个过程里的每一分钟,都会在后面学习程序控制结构时得到回报。