AI 技术博客
返回首页
Python 基础 · 18 分钟阅读

第二章综合测验:进制转换器实战

## 引言 这是第二章「变量、类型与运算符」的最后一篇,也是收卷篇。前十篇依次建立了:变量与引用的真相、数字类型画像、布尔与真值测试、字符串基础、f-string 格式化、类型转换、运算符优先级、位运算、命名规范。本篇做三件事:第一,用 10 道选择题把你拉回每个知识点的"精确版本"——选择题的价值不在答对,而在把你以为懂的东西逼出原形;第二,用一道大题(进制转换器)把字符串、数字、运算符、转换、命名全部串进一个可运行的程序;第三,给出两道挑战练习,为第三章流程控制热身。建议的作答方式:先闭卷写出每题的答案与理由,再对照解析——理由比答案重要,答对但理由写错,说明该篇需要回读。 ## 测验说明 10 道题覆盖全章 10 篇,每题标注出处。选项均为单选。做完全部题目后,再阅读解析部分;解析会指出该题对应回读哪一篇、哪个小节的哪一个概念。 ### 第 1 题(出处:第 1 篇 变量与赋值) ```python a = 10 b = a a = 20 print(b) ``` 输出是?(A)10 (B)20 (C)报错 (D)15 ### 第 2 题(出处:第 2 篇 数字类型) 下列哪个说法正确?(A)`1 / 2` 的结果是整数 0 (B)`1e3` 的类型是 `int` (C)`10 ** 100` 的类型是 `int` (D)`1 + 2j` 是一个浮点数 ### 第 3 题(出处:第 3 篇 布尔与真值) `sum([True, False, True])` 的值是?(A)3 (B)2 (C)`[True, False, True]` (D)报错,布尔不能求和 ### 第 4 题(出处:第 4 篇 字符串基础) `len(r"\n")` 与 `"ab" * 3` 的结果分别是?(A)1 与 `"ababab"` (B)2 与 `"ababab"` (C)2 与 `"aaaabbbb"` (D)1 与 `"aaabbb"` ### 第 5 题(出处:第 5 篇 f-string) `f"{3.14159:.2f}"` 的输出是?(A)`"3.14"` (B)`"3.15"` (C)`"3.14159"` (D)报错,格式说明符不支持 ### 第 6 题(出处:第 6 篇 类型转换) 下列哪个调用会抛出 `ValueError`?(A)`int("10", 2)` (B)`float("1e3")` (C)`int("3.14")` (D)`int("0xFF", 0)` ### 第 7 题(出处:第 7 篇 运算符与优先级) `2 ** 3 ** 2` 与 `-2 ** 2` 的值分别是?(A)512 与 -4 (B)64 与 -4 (C)512 与 4 (D)64 与 4 ### 第 8 题(出处:第 8 篇 位运算) `5 & 3`、`~5`、`0b1010 | 0b0101` 的值分别是?(A)1、-6、15 (B)1、6、15 (C)7、-6、15 (D)1、-6、6 ### 第 9 题(出处:第 9 篇 命名规范) 下列哪个写法正确?(A)`True = 1` (B)`def list(x): return x` (C)`9score = 100` (D)`class_ = "math"` ### 第 10 题(出处:第 6/7/8 篇 综合) `int("3A", 16)`、`5 >> 1`、`234 // 7` 的值分别是?(A)58、2、33 (B)56、2、33 (C)58、3、33 (D)58、2、34 ### 计分与回读建议 10 题每题 10 分,答完先别翻解析,按下表对照自己的分数段安排复习:8 分以下建议重读对应错题的篇目后再做挑战练习;9~10 分可以直接进实战。分数只能说明"哪些点没建起来",回读动作才真正补齐——错题对应篇目一览: | 题号 | 考点 | 回读篇目 | |---|---|---| | 1 | 变量绑定与引用 | 第 1 篇「名字是引用」、不可变对象 | | 2 | 整数/浮点/复数识别 | 第 2 篇类型画像 + 第 6 篇隐式转换 | | 3 | 布尔参与算术 | 第 6 篇易错点 2 | | 4 | 原始字符串与重复 | 第 4 篇转义与字面量 | | 5 | 格式说明符 | 第 5 篇 f-string 精度控制 | | 6 | 字符串转数字边界 | 第 6 篇 ValueError 边界 | | 7 | 幂的右结合与优先级 | 第 7 篇优先级表等级 4/5 | | 8 | 位运算三运算 | 第 8 篇 `&`/`~`/`\|` 小节 | | 9 | 关键字与遮蔽 | 第 9 篇语法关与冲突关 | | 10 | 进制、移位、地板除 | 第 6/7/8 篇对应小节 | ## 解析:每题为什么 **第 1 题:A。** `b = a` 让 b 与 a 绑定同一个 int 对象 10;`a = 20` 是让 a 重新绑定到新对象 20,b 的引用不受影响。int 不可变,赋值即重绑定——这正是第 1 篇"名字是对象的引用"的直译。 **第 2 题:C。** `/` 恒返回 float(`1 / 2` 是 `0.5`),`1e3` 是 float 的科学计数法字面量;只有 `**` 在整数底数整数指数时保持 int,`10 ** 100` 是大整数。`1 + 2j` 是 complex。回看第 2 篇的"int/float/complex 画像"与第 7 篇"`/` 恒得 float"。 **第 3 题:B。** `True` 是 `int` 的子类,参与算术时当 1 用,`sum` 从 0 开始累加:1+0+1 = 2。这正是第 6 篇"布尔混入算术"的易错点 2。 **第 4 题:B。** 原始字符串 `r"\n"` 中的 `\n` 不转义,是两个字符(反斜杠加 n),长度 2;`"ab" * 3` 是字符串重复,得 `"ababab"`。回看第 4 篇的转义与原始字符串。 **第 5 题:A。** `.2f` 表示两位小数的定点表示,`3.14159` 按**舍入到最近**得 `3.14`;`f"{3.14159:.2f}"` 输出 `"3.14"`。注意格式化用的是 round-to-nearest 而非截断(联系第 6 篇银行家舍入话题)。 **第 6 题:C。** `int()` 只认整数语法,`"3.14"` 含小数点直接 `ValueError: invalid literal for int() with base 10: '3.14'`。A 的 `int("10", 2)` 是按二进制解释 = 2,D 的 base=0 按前缀推断 = 255,B 的 `float("1e3")` 合法 = 1000.0。回看第 6 篇"ValueError 边界"。 **第 7 题:A。** `**` 右结合:`2 ** (3 ** 2) = 2 ** 9 = 512`;`**` 优先级高于一元负号:`-(2 ** 2) = -4`。回看第 7 篇优先级表等级 4 与 5 及其推导。 **第 8 题:A。** `5 & 3`:`101 & 011 = 001` = 1;`~5 = -5 - 1 = -6`(补码推论);`0b1010 | 0b0101 = 0b1111` = 15。第 8 篇的"掩码与置位"正是 `&` 与 `|`。 **第 9 题:D。** `True` 是关键字,赋值报 `SyntaxError: cannot assign to True`;以数字开头(`9score`)非法;`def list(...)` 虽语法合法,但遮蔽内置 `list`,属第 9 篇明令避免的坑。`class_` 尾部下划线绕行是标准姿势。 **第 10 题:A。** `int("3A", 16)` = 3×16 + 10 = 58;正数右移是整除 2 的幂,`5 >> 1` = 2;`234 // 7`:7×33 = 231,商 33,余 3。三道小题分别对应进制解析、移位、地板除,正是本篇实战要自动化的三类计算。 ### 速转技巧:二进制与十六进制的四位一组 十进制转十六进制用不上的时候,二进制与十六进制之间的互转有省心捷径:**每 4 位二进制对应 1 位十六进制**(16 = 2⁴)。`0b1111` 对应 `"F"`、`0b1010` 对应 `"A"`。所以 `0x3A` 拆开看就是 `0011` + `1010`,反过来 `11111111` 从右往左每四位一组 `1111 1111`,直接读出 `0xFF`。这个"8421 码"(8+4+2+1 构成一位十六进制)让你在调二进制协议时不用拿起计算器: ```python # 8421 速转验证:0b1011_1100 == 0xBC for nibble_value, want in (("1011", "B"), ("1100", "C")): got = hex(int(nibble_value, 2))[2:].upper() print(nibble_value, "->", got, got == want) # 1011 -> B True ``` 八进制与二进制的对应关系则是三位一组(8 = 2³),同理可推。这个技巧是第 8 篇"位是数据的原生形态"的延续——字节(8 位)正好两个十六进制位,网络抓包工具里清一色的十六进制显示,根源在此。 ## 综合实战:进制转换器 选择题到此为止,现在进入"能跑起来的测验":一个支持**2/8/10/16 进制互转**的交互式命令行工具。它把全章知识用了个遍——字符串切片(第 4 篇)、整除与取余 `divmod`(第 7 篇)、`try/except` 的 ValueError 边界(第 6 篇)、命名规范(第 9 篇)。 ### 核心函数:十进制转任意进制 转换算法一句话:**反复除以目标进制,余数倒序排列**。整数除法留商,`%` 留余——这正是第 7 篇 `a == (a // b) * b + a % b` 恒等式的工程应用: ```python DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" def to_base(n: int, base: int) -> str: """十进制整数转 2~36 进制字符串,支持负数。""" if not 2 <= base <= 36: raise ValueError(f"不支持的进制:{base}") sign = "-" if n < 0 else "" n = abs(n) digits = [] while n: n, r = divmod(n, base) digits.append(DIGITS[r]) return sign + "".join(reversed(digits)) if digits else "0" ``` `DIGITS` 是常量(全大写,第 9 篇),下标即数值:`DIGITS[10]` 是 `"A"`。`divmod` 一次给商与余,`reversed` 倒序拼装,`n = 0` 时返回 `"0"`。36 进制封顶是因为 `DIGITS` 恰好 36 个字符——与 `int(s, base)` 的 base 上限(2~36)严格一致。 ### 反向:任意进制字符串转十进制 ```python def from_base(s: str, base: int) -> int: """2~36 进制字符串转十进制整数,逐字符合法校验。""" s = s.strip() sign = 1 if s.startswith("-"): sign, s = -1, s[1:] elif s.startswith("+"): s = s[1:] if not s: raise ValueError("输入为空") value = 0 for ch in s.upper(): d = DIGITS.find(ch) if d < 0 or d >= base: raise ValueError(f"字符 '{ch}' 不是 {base} 进制数字") value = value * base + d return sign * value ``` 逐位累加是**霍纳法则(Horner's method)**:`"3A"` 读作 `((3) × 16 + 10)`。`DIGITS.find(ch)` 返回下标即数值,`find` 找不到返回 -1,恰好与"非法字符"判定合体。`d >= base` 拦截"数字存在但进制太小"的情形——`"2"` 在二进制里非法,正如测验第 6 题的边界讨论。 ### 主循环:交互式回收站 ```python def convert(): """交互主循环:源进制 -> 数字 -> 目标进制。""" while True: cmd = input("请输入源进制(2/8/10/16,q 退出): ").strip().lower() if cmd == "q": print("再见!") return if cmd not in ("2", "8", "10", "16"): # 白名单校验 print("仅支持 2/8/10/16 进制") continue src = int(cmd) raw = input("请输入要转换的数字: ").strip() try: value = from_base(raw, src) except ValueError as e: print(f"输入无效:{e}") # 非法数字不崩溃 continue dst = input("请输入目标进制(2/8/10/16): ").strip() if dst not in ("2", "8", "10", "16"): print("仅支持 2/8/10/16 进制") continue print(f"结果:{raw} (base {src}) = {to_base(value, int(dst))} (base {dst})") if __name__ == "__main__": convert() ``` 配置文件或命令行程序的标准骨架:**白名单校验**(`cmd not in (...)`,第 3 篇成员测试)→ **转换** → **异常兜底**(`try/except ValueError`,第 6 篇边界)→ **格式化输出**(f-string,第 5 篇)。一串真实运行记录(Python 3.12 实测): ```text 请输入源进制(2/8/10/16,q 退出): 16 请输入要转换的数字: FF 请输入目标进制(2/8/10/16): 10 结果:FF (base 16) = 255 (base 10) 请输入源进制(2/8/10/16,q 退出): 10 请输入要转换的数字: 255 请输入目标进制(2/8/10/16): 2 结果:255 (base 10) = 11111111 (base 2) 请输入源进制(2/8/10/16,q 退出): 8 请输入要转换的数字: FF 输入无效:字符 'F' 不是 8 进制数字 请输入源进制(2/8/10/16,q 退出): 10 请输入要转换的数字: -42 请输入目标进制(2/8/10/16): 16 结果:-42 (base 10) = -2A (base 16) 请输入源进制(2/8/10/16,q 退出): q 再见! ``` 注意三条细节:非法输入被拦截而不是崩溃;负数全程保留符号位;空输入在 `from_base` 里被 `ValueError("输入为空")` 拦下。这些行为全部来自第 6 篇的 ValueError 边界讨论。 ### 双向一致性自检 测验性质的程序最怕"看起来对"。`to_base` 与 `from_base` 互为逆运算,可以交叉验证——每个数转出去再转回来必须原样: ```python for base in (2, 8, 10, 16, 36): for n in (0, 255, -255, (1 << 32) - 1): assert from_base(to_base(n, base), base) == n print("全部 20 组自检通过") # 5 种进制 × 4 个样例 ``` `assert`(断言)让不一致直接中断,是第 3 篇"验证型代码"的又一例证。样例特意包含 0、负数与大整数——0 走"空数字"分支,负数走符号分支,`(1 << 32) - 1` 是 4294967295(第 8 篇位运算的 32 位全 1),覆盖最大位宽。 ### 诚实地审视这个"成品" 转换器能跑,但要承认它的两个工程短板,这本身就是本章该有的批判眼光:其一,**它没有复用 `int(s, base)` 与 `format()`**——标准库做同样的事只需一行(`format(255, "b")` 得 `"11111111"`);自己实现的价值在**理解机制**而不在生产效率,真实项目里优先用内置,眼里有实现才是进阶者的状态。其二,**输入体验粗糙**:目标进制校验在读取之后才做、没有历史记录、非法输入只提示不引导——这些在第三章学到循环与字符串方法后都可以补。工具能用的判断标准不是"写出来了",而是"边界都测过了":把测验第 10 题的三个数值喂给它,再想想还有什么输入会击穿它。 ## 挑战练习 三道题按难度递进,全部用本章 API 就能完成。第一题复习补码观察法:`n & ((1 << bits) - 1)` 把无限位宽的负数截断成 bits 位,正是第 8 篇"掩码截断"技巧;第二题把 to_base、字符串切片与回文判断组合,验证 585 这个"跨进制的巧合";第三题没有现成答案,强迫你拆解十进制小数的位权结构——如果只靠复制粘贴学到这里,这题会卡住你,而这正是它存在的目的。做完再回头读一遍解析,两轮走完,本章的知识点才算真正交割。 1. **补码视图**:写 `twos_complement_view(n, bits=8)`,返回 n 在 bits 位补码下的二进制字符串(提示:`n & ((1 << bits) - 1)`)。验证 `twos_complement_view(-6)` 是 `"11111010"`。 2. **跨进制回文**:写 `is_palindrome_num(n, base)`,判断 n 在给定进制下是否为回文(左右对称)。已知 585 的二进制 `1001001001` 是回文,验证之。 3. **扩展思考**:如果把 `from_base` 的合法输入扩展到**二进制小数**(如 `"101.1"` 表示 5.5),算法与 `DIGITS` 需要哪些改动?先口头设计,再实现(提示:小数点两侧分别处理,右侧的权重是负次幂)。 ## 小结 本章十条主线在今天完成闭环:绑定与引用是第一块地基;数字、布尔、字符串三类基本类型各有各的语义;f-string 让输出精确可控;显式转换与隐式转换管着"类型之门";优先级表是表达式的宪章;位运算把整数拆回比特;命名规范让一切努力可维护。进制转换器把其中的字符串、数字、运算符、转换、异常五条线织在一起,而它离"真实工具"还差最后一公里——流程控制。第三章「流程控制」将从 `if/elif/else` 与 `for/while` 讲起,把今天写在主循环里的 `while True` 和 `continue` 正式扶正;读到这里,你写的每段代码都已经具备本章的全部判断力。用测验里的错题做复习清单,我们第三章见。