
1. 从一道练习题说起为什么“程序设计基础”这门课值得反复刷很多人第一次接触编程都是从“程序设计基础”这门课开始的。教材翻到第二章往往就是一堆练习题求圆的面积、判断闰年、打印九九乘法表、计算阶乘、斐波那契数列……当年我也觉得这些题目“没什么用”直到后来带新人、做项目、排查线上问题时才发现这些看似简单的练习题其实是在训练一种最底层的能力——把现实问题翻译成计算机能执行的步骤。“程序设计基础 程序设计练习题”这个主题表面上看是学生作业实际上它覆盖了编程入门阶段最核心的几件事变量与数据类型、运算符与表达式、流程控制顺序、分支、循环、函数的初步使用、以及最基本的输入输出。这些内容放到任何语言里都通用C、C、Java、Python 只是语法外壳不同思维内核完全一致。这篇文章适合三类人看第一类是在校学生正在被第二章练习题折磨想搞懂每道题背后的套路第二类是转行自学编程的人需要一套系统的练习路径来打基础第三类是已经工作但想回头补基本功的开发者你会发现当年跳过的练习题恰恰是现在写业务代码时最容易犯错的点。我写这篇东西不是要给你一份标准答案而是想把这几十道练习题按“能力训练目标”重新拆解一遍告诉你每道题到底在练什么、容易踩什么坑、怎么从“能跑通”进阶到“写得对”。下面我会按四个板块展开先讲整体设计思路再拆核心知识点和实操要点然后给出一套完整的练习实现流程最后整理一份常见问题排查表。全程用大白话代码以 Python 和 C 两种语言对照因为这两种语言最能体现“基础语法”的差异。2. 程序设计练习题的整体设计与训练思路拆解2.1 为什么第二章练习题总是这几类题型你翻遍市面上主流的程序设计基础教材第二章的练习题几乎逃不出这几个类型数值计算、条件判断、循环控制、简单函数封装。这不是编者偷懒而是因为编程入门阶段的能力成长路径是固定的。第一层是数据操作能力。你得先学会把数据存进变量知道整数和浮点数有什么区别知道和%分别是什么含义。对应题型就是“输入半径求圆面积”“输入两个数交换它们的值”“求一个整数的各位数字之和”。第二层是逻辑分支能力。程序不能只会一条路走到黑得会根据条件走不同分支。对应题型就是“判断闰年”“判断成绩等级”“求三个数中的最大值”。第三层是重复执行能力。人手动算一百次会疯但计算机最擅长的就是重复。对应题型就是“求 1 到 100 的和”“打印乘法表”“判断素数”。第四层是问题分解能力。把一个大问题拆成几个小步骤每个步骤封装成一个函数。对应题型就是“用函数实现阶乘”“用函数判断完数”。这四层能力是递进关系跳不过去。我见过太多人直接去学框架、学项目结果写出来的代码连基本的边界判断都没有输入 0 或者负数就崩溃。根子就在第二章这些练习题没练扎实。2.2 练习题背后的“隐藏考核点”每道练习题除了表面要求其实还藏着几个不写在题目里的考核点。我拿“判断闰年”举例。表面要求输入一个年份输出它是不是闰年。隐藏考核点一闰年的完整规则。很多人只记得“能被 4 整除就是闰年”但完整规则是能被 4 整除且不能被 100 整除或者能被 400 整除。这个“且”和“或”的组合考的是布尔逻辑运算符的优先级和括号使用。隐藏考核点二输入合法性。如果用户输入的是负数年份或者 0 呢题目没说但一个合格的程序应该处理。这就是边界意识。隐藏考核点三输出格式。是输出True/False还是输出“是闰年/不是闰年”还是输出1/0不同教材要求不同考的是你对题目要求的精确理解。再比如“求 1 到 100 的和”隐藏考核点包括循环变量的初始值和终止条件是i 100还是i 100、累加变量的初始值必须是 0、以及是否能用公式n*(n1)/2来优化。最后这一点尤其重要它考的是你有没有“算法优化”的意识——虽然对 100 来说两种方法没差别但如果是 1 到 10 亿呢循环要跑很久公式一瞬间就出结果。2.3 语言选择对练习题的影响同一道练习题用不同语言写难度和关注点完全不同。我建议初学者至少用两种语言各写一遍感受差异。用 C 语言写“输入两个数交换值”你必须理解指针或者临时变量的概念因为 C 的函数参数是值传递。用 Python 写同样的题你可以直接a, b b, a因为 Python 有元组解包。这个差异背后是“内存模型”和“变量引用”的深层知识。用 C 写“求字符串长度”你得用char数组和\0结束符。用 Python 写直接len(s)。这个差异背后是“底层内存表示”和“高级抽象”的区别。所以我的建议是用 C 理解原理用 Python 快速验证逻辑。第二章的练习题先用 C 写一遍搞清楚内存里到底发生了什么再用 Python 写一遍体会高级语言帮你省掉了哪些事。两遍下来你对“程序设计基础”的理解会超过只写一遍的人。3. 核心知识点拆解与实操要点3.1 变量、数据类型与输入输出最容易翻车的第一关第二章练习题的第一类坑几乎都出在输入输出上。我见过太多人代码逻辑全对但结果就是不对最后发现是输入格式没处理好。输入处理的三个关键点第一输入类型转换。Python 的input()永远返回字符串你要做数值计算必须转成int或float。C 的scanf需要指定格式符%d对应整数%f对应浮点数写错了就是乱码或者 0。第二多值输入的分隔。题目说“输入两个整数”可能是空格分隔可能是逗号分隔可能是一行一个。Python 里input().split()默认按空白分隔如果要按逗号分隔得写input().split(,)。C 里scanf(%d %d, a, b)会自动跳过空白但如果你写成scanf(%d,%d, a, b)那输入就必须带逗号。第三输出格式的精确控制。题目要求“保留两位小数”Python 用print(f{area:.2f})C 用printf(%.2f, area)。题目要求“每个结果占一行”那就别忘了换行符。这些细节在考试和作业里都是扣分点在工作中就是 bug。注意浮点数比较不要用。比如判断“三角形是否成立”你算出两边之和等于第三边理论上应该成立但浮点运算可能有微小误差。正确做法是判断差值是否小于一个极小值比如abs(a b - c) 1e-9。数据类型选择的实操心得涉及金额计算不要用浮点数用整数分或者Decimal类型否则会出现0.1 0.2 ! 0.3的经典问题。循环计数器用整数不要用浮点数因为浮点数累加会有精度漂移。大数计算注意溢出。C 的int通常是 32 位最大约 21 亿算阶乘到 13 就溢出了。Python 的整数是任意精度不会溢出但会变慢。3.2 分支结构布尔逻辑与边界条件的实战分支结构看起来简单if-else谁不会写但练习题里真正拉开差距的是条件表达式的完整性和边界处理。拿“判断成绩等级”这道题来说。题目要求90 分以上优秀80 到 89 良好70 到 79 中等60 到 69 及格60 以下不及格。新手写法if score 90: print(优秀) if score 80: print(良好) if score 70: print(中等) if score 60: print(及格) if score 60: print(不及格)这段代码的问题输入 95 分会同时打印“优秀”“良好”“中等”“及格”。因为每个if都是独立的不互斥。正确写法应该用elifif score 90: print(优秀) elif score 80: print(良好) elif score 70: print(中等) elif score 60: print(及格) else: print(不及格)这个例子说明一个关键点分支条件是有顺序的。elif的隐含意思是“上面的条件都不满足才检查我”。所以条件的排列顺序必须从严格到宽松或者从大到小。布尔运算符的优先级坑C 语言里的优先级高于||Python 里and的优先级高于or。这意味着a || b c实际是a || (b c)。如果你想要(a || b) c必须加括号。我强烈建议只要表达式里同时出现与和或就加括号不要依赖优先级记忆。边界条件的三个检查点等号是否包含。score 90和score 90差一个 90 分。端点值是否覆盖。输入 0、输入负数、输入最大值程序是否还正常。浮点数比较。判断“大于等于”时浮点数可能因为精度问题差一点点考虑用容差。3.3 循环结构从“能跑”到“跑得对”的关键跨越循环是第二章练习题的重头戏也是错误率最高的地方。我总结循环题目的三大类错误边界错误、累加错误、死循环。边界错误求 1 到 100 的和循环写成for i in range(1, 100)结果只加到 99。Python 的range(a, b)是左闭右开不包含b。C 的for (i 1; i 100; i)同样不包含 100。要包含 100Python 写range(1, 101)C 写i 100。累加错误累加变量没有初始化或者初始化位置不对。比如在循环内部写sum 0那每次循环都把之前的结果清空了最后只剩最后一次的值。正确做法是在循环外面初始化sum 0。死循环循环变量没有更新或者更新方向反了。比如while (i 10)里面忘了写i或者写成了i--。死循环在本地跑可能只是卡住在服务器上跑就是 CPU 打满非常危险。循环嵌套的实操要点打印九九乘法表是经典的嵌套循环题。外层循环控制行内层循环控制列。关键点在于内层循环的终止条件依赖于外层循环变量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行只打印i个表达式。如果写成range(1, 10)就会打印完整的 9x9 矩阵不符合乘法表的形状。实操心得写嵌套循环时先用小规模数据手动模拟一遍。比如外层只循环 2 次内层循环 3 次在纸上画出每次循环变量的值确认边界正确后再扩大规模。循环控制语句的使用场景break找到目标后立即退出循环。比如判断素数一旦发现能整除就break不用继续检查。continue跳过本次循环剩余代码进入下一次。比如求 1 到 100 的偶数和遇到奇数就continue。else子句Python 特有循环正常结束没有被break时执行。判断素数时特别有用。3.4 函数封装从“一坨代码”到“可复用模块”第二章后期会引入函数的概念。练习题通常要求“用函数实现某某功能”。很多人觉得函数只是把代码包起来其实函数的核心价值是接口设计和职责分离。拿“判断素数”来说。新手写法是把所有逻辑塞进main函数int main() { int n, i, is_prime 1; scanf(%d, n); for (i 2; i n; i) { if (n % i 0) { is_prime 0; break; } } if (is_prime) printf(是素数); else printf(不是素数); return 0; }这种写法的问题判断素数的逻辑和输入输出混在一起如果我想在另一个程序里复用判断素数的逻辑只能复制粘贴。正确做法是拆成函数int is_prime(int n) { if (n 2) return 0; for (int i 2; i * i n; i) { if (n % i 0) return 0; } return 1; } int main() { int n; scanf(%d, n); if (is_prime(n)) printf(是素数); else printf(不是素数); return 0; }这样is_prime函数可以独立测试、独立复用。而且注意循环条件从i n优化成了i * i n因为如果n有因子必然有一个小于等于根号n。这个优化把时间复杂度从 O(n) 降到了 O(√n)。函数设计的三个原则单一职责一个函数只做一件事。is_prime只判断素数不负责输入输出。明确返回值判断类函数返回布尔值C 里用int的 0/1计算类函数返回计算结果。参数精简参数不要太多超过 4 个考虑封装成结构体。4. 完整实操流程从零刷完第二章练习题4.1 环境准备与练习节奏安排工欲善其事必先利其器。刷第二章练习题不需要复杂的 IDE但需要一套顺手的工具链。Python 环境装一个 Python 3.8 以上版本配一个 VS Code 或者 PyCharm Community。VS Code 轻量装个 Python 插件就能跑。PyCharm 功能全但启动慢。我建议初学者用 VS Code因为它的调试功能足够用而且启动快适合频繁改代码试错。C 环境Windows 上用 MinGW 或者 Dev-CMac 上用 Xcode Command Line ToolsLinux 上直接gcc。编辑器同样推荐 VS Code装 C/C 插件。练习节奏我建议按“每天 5 道题每道题写两遍”的节奏来。第一遍用 C 写重点理解内存和指针第二遍用 Python 写重点理解逻辑和抽象。每道题控制在 20 分钟内超时就先看提示不要死磕。死磕一道题三小时不如看答案后自己重写三遍。代码管理每道题建一个文件夹里面放main.c和main.py。用 Git 管理每完成一道题提交一次。这样一个月后你回头看能看到自己的进步轨迹。4.2 典型题目实操求圆的面积题目输入半径计算并输出圆的面积保留两位小数。C 语言实现#include stdio.h #define PI 3.1415926535 int main() { double r, area; printf(请输入半径); scanf(%lf, r); if (r 0) { printf(半径不能为负数\n); return 1; } area PI * r * r; printf(面积%.2f\n, area); return 0; }关键点解析scanf读double必须用%lf不是%f。%f对应float用错了会读到错误的值。#define PI定义常量比在代码里写魔法数字好。负数半径做了判断这是边界处理。Python 实现import math r float(input(请输入半径)) if r 0: print(半径不能为负数) else: area math.pi * r ** 2 print(f面积{area:.2f})Python 用math.pi而不是自己定义精度更高。**是幂运算符r ** 2就是r的平方。f{area:.2f}是格式化字符串.2f表示保留两位小数。两种语言的差异对比对比项C 语言Python圆周率来源手动定义或M_PImath.pi输入读取scanf(%lf, r)float(input())平方运算r * rr ** 2格式化输出printf(%.2f)f{:.2f}错误处理手动判断手动判断或异常捕获4.3 典型题目实操判断素数并输出因子题目输入一个正整数判断是否为素数如果不是素数输出它的所有因子。这道题综合了循环、分支、函数和边界处理是第二章的“集大成”题目。Python 实现def get_factors(n): factors [] for i in range(1, n 1): if n % i 0: factors.append(i) return factors def is_prime(n): if n 2: return False for i in range(2, int(n ** 0.5) 1): if n % i 0: return False return True n int(input(请输入一个正整数)) if n 0: print(请输入正整数) elif is_prime(n): print(f{n} 是素数) else: print(f{n} 不是素数因子有{get_factors(n)})关键点解析is_prime里循环到int(n ** 0.5) 1这是 O(√n) 优化。get_factors里循环到n因为要列出所有因子。两个函数职责分离is_prime只判断get_factors只列因子。C 语言实现#include stdio.h #include math.h int is_prime(int n) { if (n 2) return 0; for (int i 2; i (int)sqrt(n); i) { if (n % i 0) return 0; } return 1; } void print_factors(int n) { printf(%d 的因子, n); for (int i 1; i n; i) { if (n % i 0) printf(%d , i); } printf(\n); } int main() { int n; printf(请输入一个正整数); scanf(%d, n); if (n 0) { printf(请输入正整数\n); } else if (is_prime(n)) { printf(%d 是素数\n, n); } else { printf(%d 不是素数\n, n); print_factors(n); } return 0; }C 版本需要#include math.h才能用sqrt编译时可能要加-lm链接数学库。(int)sqrt(n)做了强制类型转换因为sqrt返回double。实操心得判断素数的循环上界用i * i n比i sqrt(n)更好因为避免了浮点数运算和类型转换而且不会因为浮点精度问题漏掉边界。比如n 25sqrt(25)可能返回4.999999取整后变成 4就漏掉了因子 5。4.4 典型题目实操打印斐波那契数列前 N 项题目输入 N输出斐波那契数列的前 N 项。斐波那契数列定义F(1)1F(2)1F(n)F(n-1)F(n-2)。Python 实现迭代版n int(input(请输入项数)) if n 0: print(项数必须为正整数) else: a, b 1, 1 for i in range(n): print(a, end ) a, b b, a b print()关键点a, b b, a b是 Python 的元组解包一行完成“更新 a 为 b更新 b 为旧 a 加旧 b”。如果用传统临时变量写法temp a a b b temp b效果一样但 Python 的写法更简洁而且不需要临时变量。C 语言实现迭代版#include stdio.h int main() { int n; printf(请输入项数); scanf(%d, n); if (n 0) { printf(项数必须为正整数\n); return 1; } long long a 1, b 1, temp; for (int i 0; i n; i) { printf(%lld , a); temp a b; a b; b temp; } printf(\n); return 0; }C 版本用long long防止溢出因为斐波那契数列增长很快第 50 项就超过int范围了。%lld是long long的格式符。递归版对比def fib(n): if n 2: return 1 return fib(n - 1) fib(n - 2)递归版代码简洁但时间复杂度是 O(2^n)算到第 40 项就要好几秒。迭代版是 O(n)算到第 100 项都是瞬间。这就是“能跑”和“跑得好”的区别。第二章练习题通常要求用循环实现就是为了训练这种效率意识。5. 常见问题与排查技巧实录5.1 输入输出类问题速查表问题现象可能原因排查方法解决方案程序一运行就结束没有等待输入或输入被跳过检查是否有input()或scanf加上输入语句C 里检查scanf返回值输入后结果不对类型转换错误打印输入变量的值和类型Python 用type()C 用printf(%d)验证输出多了一行空行格式化字符串带了\n又用了print自动换行检查输出语句Python 用end取消自动换行浮点数输出很多位没有格式化检查输出格式用%.2f或f{:.2f}中文输出乱码编码问题检查文件编码和终端编码统一用 UTF-8Windows 终端用chcp 650015.2 循环与边界类问题排查问题一循环次数不对。最常见的原因是range的右边界不包含或者 C 的for条件用了而不是。排查方法在循环里打印循环变量看第一次和最后一次的值。问题二累加结果偏小或偏大。检查累加变量初始化位置必须在循环外面。检查循环是否提前break了。问题三死循环。检查循环变量是否更新更新方向是否正确。在循环里加一个计数器超过 10000 次就强制退出并打印警告。问题四嵌套循环结果形状不对。检查内层循环的终止条件是否依赖外层变量。打印乘法表时内层应该是range(1, i 1)而不是range(1, 10)。避坑技巧写循环之前先在纸上写出循环变量的变化序列。比如for i in range(1, 5)写出i 1, 2, 3, 4确认这是你想要的。这个习惯能避免 80% 的边界错误。5.3 函数与递归类问题排查问题一函数返回值不对。检查是否有return语句检查return的位置是否在条件分支里导致某些路径没有返回值。C 语言里如果函数声明返回int但没有return会返回一个随机值非常危险。问题二递归栈溢出。递归必须有终止条件而且递归深度不能太大。Python 默认递归深度约 1000超过就报RecursionError。斐波那契数列用递归算第 1000 项会栈溢出必须用迭代。问题三函数修改了不该修改的变量。Python 里可变对象列表、字典作为参数传递时函数内部修改会影响外部。如果不想影响传副本list.copy()。C 语言里数组作为参数传递的是指针函数内部修改会影响外部数组。问题四全局变量和局部变量同名。函数内部如果给变量赋值Python 会认为它是局部变量除非用global声明。这会导致UnboundLocalError。排查方法检查函数内部是否对外部变量做了赋值。5.4 独家避坑经验分享经验一不要用比较浮点数。我踩过这个坑判断一个数是否等于 0.1 0.2结果永远不相等。正确做法是abs(a - b) 1e-9。经验二整数除法要小心。Python 3 里/是浮点除法//是整数除法。C 语言里两个整数相除是整数除法5 / 2等于 2 不是 2.5。要得到浮点结果至少一个操作数要转成浮点5.0 / 2。经验三数组越界不会报错但会出诡异结果。C 语言里访问arr[10]而数组长度是 10编译器不报错运行时可能读到垃圾值也可能崩溃。排查方法在访问数组前打印索引和数组长度确认索引在0到length - 1之间。经验四scanf的返回值要检查。scanf返回成功读取的项数如果输入格式不对它可能返回 0 或 EOF而变量保持原值。不检查返回值会导致程序用错误的数据继续运行。经验五代码写完先自己走一遍。拿一个具体输入在纸上模拟程序的执行过程记录每个变量的值。这个“人肉调试”方法比直接跑代码更能发现逻辑错误因为你会被迫思考每一步。6. 从练习题到真实项目的能力迁移第二章练习题刷完之后你获得的不只是几十道题的答案而是一套“把问题翻译成代码”的思维框架。这套框架在真实项目里的体现是拿到需求先想输入输出是什么再想边界条件有哪些然后设计数据结构和算法最后才写代码。我见过很多工作几年的人写业务代码时还是“想到哪写到哪”没有输入验证没有边界处理没有函数拆分。根子就是入门阶段跳过了这些练习题或者只是抄了答案没有真正理解。如果你正在刷第二章我的建议是每道题至少写两遍第一遍自己写第二遍看答案后重写。每道题都问自己三个问题输入有哪些可能的情况输出格式是否完全符合要求有没有更高效的解法这三个问题问下来你的基本功会比只刷一遍的人扎实得多。最后分享一个我自己的习惯每学完一章把这一章所有练习题重新做一遍但这次不看题目要求只看函数名或者输入输出示例自己反推题目要考什么。这个“反向练习”能帮你从“会做题”升级到“会出题”而“会出题”才是真正掌握了知识点。