C语言课后习题复习指南:核心考点、易错点与自查清单 简介《C语言程序设计第四版》何钦铭课后习题与答案解析PDF面向高校计算机专业学生、备考C语言考试的初学者以及需要同步巩固课堂内容的任课教师帮助他们解决课后练习没有答案可对照、知识点零散难以串联的问题。内容按教材章节组织覆盖标识符与关键字规则、变量声明、流程控制、函数、数组与字符串等基础模块第一章辨析合法与非法标识符并列出C语言关键字及释义第二章围绕printf/scanf格式控制、运算符优先级、if-else分支等选择题给出逐项解析并延伸素数判别流程图、1—100内能被6整除整数求和等编程题的代码与运行结果。全包共1个PDF文件约2.67MB篇幅紧凑按章检索方便适合课后自测与考前速查。目前已有12328人学习下载可作为课堂作业批改之外的对照参考帮助读者从答案反推解题思路并定位易错语法点。1. 知识地图C 语言课后习题复习到底在补什么C 语言的课后习题看着零散实际是一条链子词法标识符、关键字→ 类型与表达式整除、取余、优先级→ 输入输出格式串→ 控制结构分支、循环→ 简单算法累加求和、素数判定、分段计费。整份习题答案的价值不在那个结果数字而在这条链子的接缝处——期末卷和机试题九成都在接缝上设卡很多人刷完一本习题错的还是同一类地方。这份梳理适合两类人跟着教材一节一节学、想要一副能对照自查的骨架以及题刷了不少却说不清自己为什么总在格式串和边界上翻车的人。下面按这条链子拆成可复习的模块。2. 词法与类型标识符、关键字和类型转换的复习重点这一层是整门课的地基也是最容易被当成「送分题」草草跳过的一层。实际批改和自测里标识符判断题的错误率并不低原因不是不会而是记得半对——比如把「首字符必须是字母或下划线」记成「不能用下划线开头」。把规则钉死后面的变量声明、函数命名才不会反复返工。2.1 标识符的三条硬规则与判错速查表C 语言里的标识符是给变量、函数、数据类型、语句标号起的名字。规则只有三条只能由字母、数字、下划线组成第一个字符必须是字母或下划线区分大小写。外加一条隐含规则关键字不能拿来当标识符。习题里那组经典判断用下面这张表过一遍就清楚了。标识符是否合法卡在哪一条total合法全小写字母首字符合法_debug合法下划线开头被允许Counter1合法数字不在首位begin_合法下划线结尾没有限制LargeTall不合法不在字母/数字/下划线集合内1st_year不合法数字开头double不合法关键字有专门用途Total 与 total两个不同变量大小写敏感判断这类题有个三十秒流程先扫首字符再扫字符集合最后查一遍关键字表。三步走完还拿不准的基本都是自己把规则记串了。顺带提醒一句下划线开头的名字虽然合法但在团队代码里通常留给内部实现日常命名还是用有意义的英文单词更稳。提示合法的标识符不等于好的标识符。a1、b2、x这类名字在习题里没问题写到二十行以上的程序里就开始给自己挖坑了。2.2 关键字按用途分组比死背一长串省力关键字是语言规定、有专门含义的标识符硬背一整列很容易背了后面忘前面。按用途分成五组回忆时先想组、再想词命中率会高很多。分组关键字记忆抓手基本类型char short int long float double void signed unsigned声明变量和函数返回值类型构造struct union enum typedef自己拼新类型存储类与限定auto static extern register const volatile管生命周期、链接属性、只读与易变流程控制if else switch case default for while do break continue goto return分支、循环、跳转、返回运算相关sizeof求类型或变量占的字节数几个常被记混的点值得单独拎出来sizeof是运算符不是函数sizeof(int)在编译期就有结果typedef是给已有类型起别名不是定义新变量const修饰的变量只读但仍然占存储static修饰函数内的局部变量时它的生命周期贯穿整个程序运行期而不是每次进函数重新开始volatile是告诉编译器别把这个变量优化掉每次都要老老实实去内存里取。还有一类常被误当成关键字的define、include是预处理指令不是关键字末尾也不加分号。2.3 类型转换1/k 与 1.0/k 的分水岭表达式里只要有一处类型不匹配结果就会静悄悄地变味编译器一句警告都不给。整型除整型结果仍是整型这是 C 语言里最朴素也最容易忘的一条。#include stdio.h int main(void) { int k 4; /* 整型 / 整型结果仍是整型向零取整输出 0 */ printf(1/k %d\n, 1 / k); /* 一侧是 doublek 被自动提升为 double输出 0.25 */ printf(1.0/k %.2f\n, 1.0 / k); /* 强制类型转换是等价写法意图更明确 */ printf(cast %.2f\n, (double)1 / k); /* 赋值时的截断不是四舍五入是直接丢弃小数部分 */ double d 3.99; int n d; /* n 为 3不是 4 */ printf(n %d\n, n); return 0; }这段代码里三处细节对应三类考题。第一处是自动类型提升的方向低精度向高精度靠int遇到double就先变成double再算所以1.0/k得到 0.25 而不是 0。第二处是强制转换的写法(double)1只作用于紧挨着的那个操作数写成(double)(1/k)就白转了。第三处是赋值截断double赋给int时小数部分直接被砍掉3.99 变 3这一点在「平均值保留一位小数」那类题里格外要命——如果中间过程全程用整型算最后再补格式符小数位永远是 0。局部变量声明了不初始化它的值是这块内存里残留的内容不是 0全局变量和静态变量才有默认清零的待遇。这一点在后面循环累加题里会反复出现sum忘了写 0累加结果就是一堆看不懂的数。3. 表达式与输入输出优先级、整除取余和格式串表达式和输入输出是习题里出题密度最高的一段因为它能在一行代码里同时考优先级、类型和格式三件事。复习时不必背完整的优先级大表抓住「算术 关系 赋值」这条主线再补上逻辑运算和自增的几个特例就够了。3.1 运算符优先级的三层级与典型选择题教材阶段涉及的运算符主要是算术、关系、赋值三类它们的优先级从高到低排列加上括号就能完全控制求值顺序。层级运算符结合方向典型误判高*/%-左结合以为比*低所以先算加法中!左结合把和混用低-等赋值右结合以为赋值先于比较那道「优先级最低的是哪个」的选择题答案落在赋值运算符上*和%属于算术属于关系属于赋值三层一比就出来了。稍难一点的是if (k 0)这种写法——赋值表达式的值就是被赋的那个值所以条件为假程序走else分支最终输出k0#flag1。这类题想不翻车唯一的办法是看到if括号里出现单个就本能地警觉。3.2 整除与取余% 只认整数/和%是一对容易互相干扰的运算符。123/100得 1因为取的是商901%10得 1因为取的是余数625%5得 0因为它正好被整除。四个选项里唯一不等于 1 的就是最后一个。负数方向也有讲究C99 起整数除法向零取整-5/2是 -2 不是 -3取余的符号跟着被除数走-5%2是 -1。还有一条硬规定%两侧必须是整型想对浮点数取余得用math.h里的fmod。判断偶数写成n % 2 0是最常见的形式注意别写成n % 2 0那是一个把赋值当条件的典型错误。3.3 printf 与 scanf 的格式串对照格式串不匹配是机试里最冤的失分点——逻辑全对输出乱码或者直接崩掉。下面这张对照表值得抄在草稿纸第一行。格式符printf 对应类型scanf 对应类型备注%dintint *十进制整数%fdoublefloat 会自动提升不用于 doubleprintf 里%f通吃浮点%lfdoubledouble *scanf 读 double 必须用%lf%ccharchar *读字符时前面的空白和回车要留意%schar 数组char 数组名不需要取地址符%x%o无符号整数无符号整数 *十六进制、八进制%%输出一个百分号同理没有对应参数printf的函数原型是printf(格式控制字符串, 输出参数1, ..., 输出参数n)参数个数必须和格式符一一对应少一个就是未定义行为。字符串要用双引号包住语句末尾要有分号——那道「改正几处错误」的选择题答案之所以是 2就是这两个地方各错一次。#include stdio.h int main(void) { int n; double x; printf(Enter n, x: ); /* 提示串不带换行输入在同一行 */ scanf(%d%lf, n, x); /* %d 配 int*%lf 配 double*两个都要取地址 */ printf(n %d, x %.2f\n, n, x); /* 输出 double 用 %f 即可 */ return 0; }这段代码里两处最容易写漏scanf的两个参数都必须带漏掉不会报编译错误运行时直接崩读double用%lf用%f在很多环境里读到的是垃圾值。如果用逗号分隔输入格式串里也得写逗号scanf(%d,%lf, n, x)对应的输入是3,1.25写成空格就匹配失败。判断读入是否成功可以看scanf的返回值——成功读入几个变量就返回几返回 0 或 EOF 说明格式没对上。3.4 输出宽度与小数位数%.1lf、%.3f、%5.2f、%-5d这几个写法在习题答案里高频出现含义拆开看很简单点号后面是小数位数点号前面是最小宽度负号表示左对齐。%.1lf输出保留一位小数%5.2f表示总宽至少 5 个字符、其中小数占 2 位不够宽就在左边补空格。要注意宽度只是显示格式不改变变量本身的值也不做四舍五入之外的取舍判断——具体舍入行为依赖实现答题时按常规四舍五入写即可。4. 控制结构与典型算法题从流程图到可运行代码控制结构这一块习题的套路非常固定给一段流程描述让画流程图再让写代码最后让给出运行结果。三个环节考的是同一件事的不同侧面——想清楚、写出来、算准确。把这三步当成一套动作练熟这一章的题基本不会再丢分。4.1 三种基本结构与流程图的对应关系顺序结构是一条直线从上到下依次执行分支结构是菱形判断框有两条出边循环结构则一定有一条往回指的箭头形成闭环。以「判断 n 是否为素数」为例流程图里能看到全部三种结构读入 n、令 i 从 2 开始这部分是顺序判断n % i 0成立与否这是分支i 每次加一后回到判断处这是循环。画图时最容易漏的是循环退出后那条线——它必须接到「输出结论」否则图就不完整。4.2 分支if-else 的括号配对与悬空 elseif-else 的题目错得最多的不是逻辑是符号。if(xy){ ... }后面接else{ ... }两个大括号必须成对else后面不能带分号也不能缺左括号——那道补充if-else语句的选择题正确选项就是}else{因为前一个分支的右括号要闭合新分支的左括号要打开。悬空else是另一个经典陷阱else永远与它前面最近的、尚未配对的if结合缩进写得再漂亮也不影响编译器怎么认。真遇到多层嵌套又想让else归属外层唯一可靠的办法是加花括号。4.3 循环for 的执行顺序与边界条件for语句的执行顺序是固定的初始化只做一次然后判条件条件成立就执行循环体最后做增量再回到判条件。这个顺序决定了「循环体执行了几次」和「循环结束后循环变量等于几」这两类问题的答案。#include stdio.h int main(void) { int i, sum 0; /* 求 1 到 100 之间所有能被 6 整除的整数之和 */ for (i 1; i 100; i) if (i % 6 0) sum sum i; /* 只有满足条件才累加 */ printf(sum%d\n, sum); /* 输出 sum816 */ return 0; }这段代码里三处值得盯住sum必须初始化为 0否则累加的是内存里的残留值条件写i 100才是 1 到 100写成i 100会漏掉 100if后面没有花括号时只有紧跟的那一条语句属于ifsum sum i是它管着的printf不在循环里。把这个程序的if条件换成别的除数就是一道新题所以与其背答案 816不如把结构记牢。4.4 循环内外的打印时机i4 那一行怎么来的有一类填空题给出带printf的循环让写出完整输出错的人往往不是算错累加是没分清哪次打印在循环里、哪次在循环外。#include stdio.h int main(void) { int i; double s 0; for (i 1; i 4; i) { s s 1.0 / i; printf(i%d#s%.3f\n, i, s); /* 循环内i 取 1、2、3 各打印一次 */ } printf(i%d#s%.3f\n, i, s); /* 循环外i 已变成 4s 停在最后一次累加值 */ return 0; }1.0/i保证是浮点除法这一点决定了结果不是 0。i 为 1 时 s 得 1.000i 为 2 时加上 0.5 得 1.500i 为 3 时加上约 0.333 得 1.833。循环结束后 i 自增到 4条件i 4不成立循环退出但 i 的值仍然是 4所以最后一行打印i4#s1.833。很多人卡在这里是以为循环变量出了循环就「消失」了——在 C 语言里定义在循环外的变量当然还在即便定义在for (int i ...)里它的作用域也只是到循环结束为止值依然可以读只是出了作用域就不可见了。4.5 素数判定为什么写 i*i n 而不是 i n-1按定义素数只能被 1 和自己整除。最直白的写法是从 2 一直试到 n-1只要中途有一次整除就判非素数。这个写法逻辑没错但效率差而且边界容易写错。#include stdio.h int is_prime(int n) /* 约定 n 1 */ { int i; if (n 1) return 0; /* 1 和负数不是素数 */ for (i 2; i * i n; i) /* 只需试到根号 n用乘法避免开方误差 */ if (n % i 0) return 0; return 1; } int main(void) { int n; scanf(%d, n); printf(%s\n, is_prime(n) ? yes : no); return 0; }关键在i * i n这个条件。如果 n 有大于根号 n 的因子那它必然还有一个小于根号 n 的配对因子所以试到根号 n 就够。写成i sqrt(n)也能跑但要引入math.h还得提防浮点误差把边界判错。另外 n2 这个边界要专门想一遍循环条件2*2 2不成立直接返回 1判定正确——这正是很多人在循环里加一堆特判反而写错的地方。4.6 分段计费类题if-else 链的区间顺序阶梯电价那道题的核心不是算电费是分支顺序。用电量分成三段小于 0 属于非法输入0 到 50 按基础价超过 50 的部分加价。三个区间必须从小到大排列否则前面的分支会把后面的情况提前吃掉。#include stdio.h int main(void) { double x, y; printf(input x:); scanf(%lf, x); /* x 为月用电量单位千瓦时 */ if (x 0) printf(Invalid Value!\n); /* 非法输入单独挡在最前面 */ else if (x 50) y 0.53 * x; /* 第一阶梯全额按基础价 */ else y 0.53 * 50 (x - 50) * 0.58; /* 第二阶梯超出部分涨价 0.05 */ if (x 0) printf(电费 %.2f 元\n, y); return 0; }写法上有两个选择一是把非法输入那一支放最前面后面就不用再判二是像上面这样最后统一输出但要加个x 0的守卫。两种都能过混着写就容易在x 0时输出一个没赋过值的y——这正是「局部变量未初始化」那个坑的实战版。另外(x - 50) * 0.58一定要先减再乘写成x * 0.58 - 50就完全错了。5. 易错点习题里最容易翻车的五处刷完一轮题回头统计错的往往集中在五个位置。这五处有个共同特征编译器不一定报错运行时也不一定崩就是结果不对属于真正的黑匣子型错误。下面每条都按「错在哪 → 为什么会错 → 怎么补」拆一遍。5.1 词法与表达式层的三处第一处和混用。易漏点是把if (k 0)写成if (k 0)。为什么漏因为两者在语法上都合法编译器最多给个警告甚至警告也被忽略掉了而赋值表达式的值恰好是被赋的那个值k 0让条件恒为假程序静悄悄地走else。怎么补养成「条件里出现单个等号就重读一遍」的习惯编译时打开警告选项把-Wall加上这类问题会被直接点出来。第二处scanf漏掉取地址符或者%lf与%f用反。易漏点在于读入语句写完就过不去核对参数。为什么漏因为scanf(%d, n)在不少环境下只是读到错误位置输出时才发现数值离谱而错误现象离出错行很远。怎么补把读入语句当成固定模板写一个格式符对一个地址int配加%ddouble配加%lf写完逐个数一遍格式符个数和参数个数是否相等。第三处整除与类型转换。易漏点是用整型算平均值。为什么漏因为sum / 4在数学上没错只是 C 语言把它算成了整数除法写成sum / 4.0才对或者把sum声明成double。怎么补凡是涉及「平均值」「比例」「精确到小数点后几位」的题动手前先问一句中间结果会不会被截断在草稿上把关键表达式的类型标出来比事后调printf格式符有效得多。5.2 控制流与格式串层的两处第四处循环边界与循环变量的终值。易漏点是i n和i n随手写、循环结束后还想用 i 的原值。为什么漏因为边界差一在小题里体现不出来数据规模一大就少算一项而循环变量在退出时已经完成了最后一次自增值比最后一次进入循环体时大 1。怎么补写循环前先把区间写成闭区间的数学形式再翻译成代码涉及「循环结束后输出」的题单独在纸上推一遍 i 的终值。第五处格式串与参数不匹配。易漏点是格式符数量和参数数量对不上或者%d对应了一个double。为什么漏因为printf是可变参数函数编译器不会按原型检查多一个少一个都能编过运行时输出的是随机值。怎么补写printf时按「几个%、几个参数」默数一遍输出浮点统一用%f输出整数统一用%d不在同一行里混着来。注意这五处里有四处不会产生编译错误。把编译器警告当回事是性价比最高的一道防线-Wall -Wextra一开能挡掉其中至少两处。6. 复习清单三轮自查把课后题变成手感复习到考前最有效的不是再刷一遍新题而是把已经做过的题按错误类型重新过一遍。可按下面这张表三轮走第一轮按知识点扫第二轮按错误类型扫第三轮只扫错题。轮次覆盖内容自查动作通过标准第一轮标识符、关键字、类型转换合上书默写三条标识符规则和五个分组的关键字能举出合法与不合法各三个例子第二轮表达式、输入输出、控制流把错题按「格式串 / 边界 / 类型 / 符号」四类归档每类不超过两道未解决第三轮综合题求和、素数、分段计费不看答案重写一遍再对比差异能说清每一行为什么这么写动手环节建议配一条编译命令把警告全打开让编译器替你查出那些「能跑但不对」的代码# -Wall -Wextra 打开常用警告-stdc99 固定语言标准避免不同环境行为不一致 gcc -Wall -Wextra -stdc99 -o demo demo.c # 如果程序读入后结果离谱先用 -g 编译再单独跑一次确认是逻辑问题还是输入问题 gcc -g -Wall -o demo demo.c ./demo第一轮的价值在于把「以为自己会」变成「确实能写出来」。默写关键字分组时如果卡在extern和static的区别上别急着翻答案先自己想extern是说这个变量或函数在别处定义static在函数内是静态局部变量、在文件作用域是内部链接。想通了再对照记忆才落得住。第二轮的关键是分类而不是重做。同一道题错两次很常见但如果两次错的是同一类原因那就说明这个点没通。把错题按格式串、边界、类型、符号四类归档哪一类堆得最高就先攻哪一类比平均用力有效得多。归档时顺手在题号旁边写一句「错因」这句话比答案本身有用。第三轮只做一件事不看答案从空白文件重新写一遍。写完再对照差异处就是真正的薄弱点。如果重写时卡在某个函数怎么用说明第一轮的默写没到位如果写得出来但输出不对说明第三轮的编译警告没打开。提示复习清单不要写得太长。三轮下来能覆盖的题量有限宁可把二十道题吃透也别把两百道题各扫一遍。错题本上留到最后还看不懂的那几道才是真正需要找人问的。我自己最开始学的时候习惯是把答案抄一遍就算复习完了结果一到上机就原形毕露——代码能编过输出全是乱码回头查半天才发现是scanf少了个。从那以后我每次写完带输入输出的程序都强制走一遍「格式符个数对参数个数、每个地址变量对类型」的自查宁可多花两分钟也不愿意再经历一次那种找不到原因的翻车。希望帮到你。本文还有配套的精品资源点击获取