简介广州大学计算机课程配套的C习题答案以单个doc文档形式整理面向正在学习C程序设计的学生及需要巩固基础语法细节的开发者。内容围绕变量与数据类型、运算符与表达式、控制结构、函数、数组和指针等章节展开以选择题带解析的方式覆盖标识符规则、八进制与十六进制常量、运算符优先级、自增自减表达式求值、指针与引用赋值等高频考点并配有数学表达式转C算术表达式、用逻辑表达式表示整除与区间条件等实践练习有助于读者梳理易错概念、提升编程表达准确性。资源包体积仅1.55MB包含1个doc文件携带方便目前已有442人学习适合作期末复习或自学查漏补缺的参考资料。1. 这份 C 习题答案不该只拿来抄先讲清楚它帮你解决什么问题不少刚碰 C 的同学都有过这种经历书看完了语法似乎都懂一打开编译器的习题就卡住——选择题四个选项看着都对程序跑出来的结果跟手算的完全不一样。网上搜答案搜到的要么只有最终结果没有推导要么代码风格跟教材不一致抄上去还报错。这份某高校计算机 C 课程的习题与解答资源覆盖了从基础概念、数据类型、运算符优先级到控制结构、指针引用的完整知识点每一道题都给了明确的答案和代码验证。它不是让你背答案交作业用的而是当你做完题、对完答案之后能把每个选项背后的语法规则真正吃透的参考。适合正在学 C 基础、准备期末考或者刚入门的同学也适合那些想快速排查自己知识盲区的自学者。下面我按章节把题目背后真正值得关注的点拆开讲。2. 把选择题变成知识点清单数据类型、常量与标识符的七个易错判断2.1 选择题里的高频考点其实是编译原理的入门习题 1.1 的第 2 题问源程序变为目标程序必须经过什么答案是编译。不少同学在这里选错成汇编或者解释本质上是没分清这几个概念的关系。编译是把整个源文件翻译成目标代码文件汇编是把汇编语言转成机器码解释是逐行翻译逐行执行。C 是编译型语言所以必然走编译这一步。这道题背后牵出的知识点是一个 .cpp 文件从编写到运行经历了预处理、编译、汇编、链接四个阶段。后面第 5 题说的“溢出”属于运行错误也是这个链条上的概念——编译时编译器只检查语法和类型溢出这种值域问题要等程序跑起来才知道。另一个容易踩坑的是第 4 题int a0; double x5.16;哪个是编译错误。答案是aa%x。%运算符要求两个操作数都是整型这里 x 是 double类型不匹配编译直接报错。但xa/x和xx*a为什么合法因为 C 有隐式类型转换int 会被提升成 double 参与运算。考试时这种题考察的就是类型转换规则和运算符的操作数要求。我一般会提醒学生遇到这种题不要只看类型还要看运算符。第 6 题考察关键字识别sizeof、case、extern都是关键字。这里有个常见误判把IF和Static当关键字因为 C 是大小写敏感的if才是关键字IF只是一个普通标识符Static同理。第 7 题合法的标识符是_abcnew是关键字不能用π不是 ASCII 字符集C 标准标识符只允许字母、数字和下划线且不能以数字开头。2.2 常量与数值类型的陷阱全在这几道题里第 9 题到第 11 题是数值常量表示的集中考察点。八进制常量以 0 开头所以015合法0a0不是八进制里没有 a080非法8 不在八进制数字范围内0x10是十六进制前缀 0x。十六进制常量的坑在第 10 题0xg非法因为 g 不是十六进制数字。浮点常量的判断规则更细-1e3.5非法因为指数部分必须是整数e-5非法因为 e 前面必须有数字5.0e(14)非法因为指数不能是表达式。而12.34、-1e5、0.1E-12这样的合法写法要记住数字部分可以带小数点指数部分用 e 或 E 加上正负号和整数。字符常量的判断也有讲究。第 12 题答案是\101这是转义字符表示法\101是八进制 ASCII 码对应大写字母 A。a是字符串常量不是字符常量name这种单引号里多个字符在 C 里是非法的a没有引号是标识符。这里有个值得注意的点\101这种写法在考试里反复出现它考察的是转义序列\后面跟八进制数这个语法。实际上 C 还支持\x41这种十六进制转义但教材里一般只强调八进制写法。常量类型合法示例非法示例判断依据八进制整型015080、0a0以 0 开头只能含 0-7十六进制整型0x1f、0xaf0xg以 0x 开头只能含 0-9 和 a-f浮点型12.34、-1e5、0.1E-12-1e3.5、e-5指数必须是整数e 前必须有数字字符常量\101a、name单引号包裹单个字符支持转义2.3 交换变量的四种写法暴露了赋值与内存的本质第 13 题问哪种写法不能交换 a 和 b 的值答案是ab; ba;。这个错误很经典——先把 b 赋给 aa 原来的值就丢了再执行 ba 时两边都是原来的 b 值。正确的三种写法里ta; ab; bt;是引入临时变量最直观aab; ba-b; aa-b;是利用算术运算交换不需要临时变量。这里要提醒的是算术交换在整数溢出时可能出问题但作为考试题它考察的是赋值语句的执行顺序理解。第 15 题考察指针赋值p1a是错误的因为 p1 是int*类型a 是 int 类型两者类型不匹配直接把变量的值赋给指针变量在编译期就会被拒绝。而*p2b是给指针指向的内存赋值p2p1是指针变量之间的赋值c*p1*(*p2)是间址取值的算术运算。这道题背后的知识点是指针变量存的是地址给它赋值只能赋地址值或者同类型指针变量不能直接赋普通变量的值。第 16、17 题考察引用pbpa是把 pa 引用的变量的值赋给 pb 引用的变量不是改变引用关系——引用一旦初始化就不能再绑定到其他变量。第 17 题里通过引用交换 ra 和 rb本质是交换了 a 和 b 的值所以结果是 a3、b5。// 交换变量的三种正确写法验证 #include iostream using namespace std; int main() { int a 3, b 5; // 方法一临时变量 int t a; a b; b t; cout a a b b endl; // a5 b3 // 方法二算术交换 a 3; b 5; a a b; // a8 b a - b; // b3 a a - b; // a5 cout a a b b endl; // a5 b3 // 方法三引用交换 a 3; b 5; int ra a, rb b; t ra; ra rb; rb t; cout a a b b endl; // a5 b3 return 0; }这块的逻辑是引用在定义时必须绑定到变量之后对引用赋值操作的是它绑定的那个变量本身。所以tra取出的是 a 的值rarb把 b 的值赋给 arbt把之前保存的 a 的值赋给 b。这样一轮操作下来a 和 b 的值确实交换了。参数说明里值得注意的一点是int raa这个初始化过程不产生新的内存ra 就是 a 的别名这在函数传参时可以避免拷贝开销。3. 运算符优先级与表达式求值错得最多的题全在这几道里3.1 前缀后缀自增自减永远是最容易翻车的点习题 1.1 的第 20、21、22 题集中在自增自减运算符上。ij的值是 3这里编译器采用贪心匹配原则从左到右尽量多地匹配运算符所以解析成(i)ji 先参与运算后自增结果是 123。ij的值是 4i 先自增变成 2再加 j 得 4。第 22 题问哪个表达式合法答案是ab它被解析成(a)b。(a)非法因为(a)的结果是右值不能再次自增ab中间没有运算符语法错误ab会被解析成(a) b(a)是右值不能自增编译错误。这里要展开讲的是运算符的副作用和序列点问题。C 标准里同一个表达式中对一个变量多次自增自减是未定义行为比如a a这种写法不同编译器结果可能不同。考试题里一般不会出这种未定义行为但自己写代码时要避免。第三道高频题是第 23 题i||--jk初始 i0、j1、k2。这里涉及两个规则一是优先级高于||所以先算--jk二是短路求值||左侧i先执行i 变成 1值为真整个表达式短路右侧不再执行。所以结果是 1。这种题的关键是短路求值会导致一部分表达式不执行从而影响变量的最终值。第 24 题同理x||y中 x 初始为 1x后 x2 为真y不执行y 保持 1所以 x2、y1。3.2 复合赋值与逗号表达式的求值顺序第 26 题x x - x * xx 初始为 5。这种复合赋值运算符的结合性是右结合的所以等价于x (x - (x * x))。先算x * x 25再算x - 25也就是x x - 25 -20最后算x -20x -20 (-20) -40。这里最核心的认知是复合赋值运算符右边的表达式是在当前 x 值的基础上计算的每一步操作都会改变 x 的值下一步操作是基于新的 x。第 28 题逗号表达式( x 4 * 5, x * 5 ), x 25。逗号运算符从左到右依次求值整个表达式的值是最后一个子表达式的值。内层括号里先执行x 20再求x * 5 100这个值被丢弃然后外层求x 25注意此时 x 已经被赋值为 20所以结果是 202545。这道题考察的是逗号表达式的两个特征从左到右求值、结果取最后一个表达式以及赋值对后续表达式的影响。// 验证第 26 题的逐步求值 #include iostream using namespace std; int main() { int x 5; // 等价于 x (x - (x * x)) int t1 x * x; // t1 25 x x - t1; // x 5 - 25 -20 x x x; // x -20 (-20) -40注意这里 x 已经被修改 cout x x endl; // 输出 -40 return 0; }这段验证代码的逻辑是把复合赋值拆成单步操作来理解。t1 x * x先计算乘法然后x x - t1执行减法赋值最后x x x执行加法赋值。每一步都基于上一步更新后的 x 值所以最终结果是 -40 而不是 0。参数说明里要注意如果把最后一步写成x t1会得到 -40 吗不会因为t1是 25x已经是 -20x t1 5所以这一步必须用x x才能模拟出复合赋值里x (某个值)的真实含义——这个“某个值”是x - t1的结果也就是 -40而不是 25。3.3 关系表达式的连续比较陷阱第 25 题是很多人的盲区数学关系 1x5 在 C 里不能写成1x5因为 C 里是左结合的1x先求值得到一个 bool 值然后这个 bool 值再跟 5 比较。如果 1x 为真结果是 15 也为真如果 1x 为假结果是 05 也为真——所以无论 x 取什么值1x5恒为真。正确答案是1x x5或者用!(x1)!(x5)。后一种写法用到了德摩根律的变形。这道题的价值在于写代码时连续比较必须用逻辑运算符连接这是 C 与数学表达式的根本差异。第 27 题条件表达式ab?a:cd?c:d条件是右结合的所以解析成ab ? a : (cd ? c : d)。a1、b2ab为真整个表达式的值是 a也就是 1。如果 ab 为假才会去计算cd?c:d。这种嵌套条件表达式读起来费劲实际开发中我一般建议用括号显式标明层级或者直接用 if-else 替代可读性更好。4. 控制结构专项if、switch、循环的执行流程与常见误写4.1 条件语句的悬挂 else 与短路陷阱习题 2.1 的第 1 题给了int i0, x1, y0问哪个语句能让 i 变成 1。if(x||y) i正确因为 x 为真||短路i 自增。if(xy)中 x 为真但 y 为假整体为假if(xy)中 1 不等于 0if(!x)中 x 非零取反为假。这道题的关键是理解、||的短路特性左侧为假时右侧不执行||左侧为真时右侧不执行。第 2 题的函数关系式分段判断答案是 c 选项。这里要注意的是分段函数的边界条件处理x0时返回 -1x0时返回 0x0时返回 1。c 选项写成if(x0)再嵌套if(x0)逻辑等价且覆盖了所有情况。a 选项的问题是if(x0)内嵌套if(x0)当 x0 时外层为真进入内层x0为真执行y0看起来没错但 x0 时最后会执行外层的 else 吗不会因为 if-else 配对时 else 与最近的 if 配对这里的 else 与if(x0)配对。这种悬挂 else 问题在多层嵌套时特别容易出错我一般建议用花括号明确块结构。第 3 题是 switch 的穿透问题int i 2; switch(i) { case 1: i; case 2: i--; // 从 case 2 进入 case 3: i; break; // 穿透执行 case 3 case 4: --i; default: i; }i 初始为 2进入 case 2执行i--后 i1没有 break继续穿透到 case 3执行i后 i2遇到 break 退出。结果 i2答案选 b。这个题的坑在于switch 的 case 默认是穿透的必须显式写 break 才能跳出。实际编码中漏写 break 是最常见的运行时逻辑 bug 之一尤其在 Windows 平台下编译不会报任何错误。4.2 三种循环语句的边界条件对比习题 2.1 第 4 到第 7 题集中考察循环次数。第 4 题while(!x i3)x0、i0条件为真进入循环执行x后 x1i后 i1此时!x为假循环只执行 1 次。这是短路的一个变体应用!x变成假后后面的i3不再影响结果。第 5 题do-whilei初始 3进入循环i--后 i2条件i!1为真再执行一次i--后 i1条件为假退出共执行 2 次。do-while 和 while 的核心区别是前者至少执行一次这个特性在读文件等场景里很实用。第 6 题的 for 循环是个很有意思的陷阱for (i0, j5; ij; ) { cout i j endl; i; j--; }循环条件是ij这是一个赋值表达式不是比较。j 初始为 5赋值后 i5 非零为真循环执行 5 次直到 j 减到 0i0为假退出。第 7 题的 b 选项do { k; } while(k0)死循环因为 k 从 0 开始递增永远大于等于 0。这里有个细节int 溢出是未定义行为但实际运行时 k 溢出后会变成负数在某些编译器上可能结束循环所以死循环的答案是 b 而不是 d。d 选项for(; i; i--)中 i 从 3 递减到 0 退出不是死循环。4.3 阅读程序题的输出推导方法习题 2.2 的第 3 题是 continue 的经典用法int i 1; while (i 10) { if (i % 3 ! 1) continue; else cout i endl; }i 从 1 开始i后 i22%32 不等于 1continue 跳过 cout。接着 i33%30 不等于 1继续i44%31输出 4。一路推下去输出的是 4、7、10。这道题考察的是 continue 的作用位置它跳到循环体的末尾不是跳到条件判断处。i在条件判断里就已经执行了continue 不会跳过它。第 4 题是 do-while 加 breakint i 0, j 5; do { i; j--; if (i 3) break; } while (j 0);i 从 0 开始每次循环 i 加 1、j 减 1当 i4 时i3成立break 跳出。此时 j1输出i4 j1。第 5 题的 for 循环for(i1, j5; ij; i)内层j--i 和 j 同时向 3 靠拢i3、j3 时条件ij为假退出。第 7 题嵌套循环加 continuefor(i0; i3; i) { x; for(j0; j3; j) { if (j % 2) continue; x; } x; }内层循环中 j 为偶数时 x 加 1j 为奇数时 continue 跳过。外层循环每次 x 加 2内层加 2 次所以每次外层循环 x 加 44 次循环共加 16加上内层 j 偶数的 2 次结果 x16。5. 指针与引用的验证方法从语法辨析到完整程序验证5.1 三种访问方式对比名访问、引用访问、间址访问习题 1.5 的思考题 4 给出了一个验证程序分别用名访问、引用访问、间址访问三种方式操作同一个变量。这道题值得认真跑一遍因为它揭示了一个关键点指针和引用在语法上都能修改原变量的值但底层机制不同。指针变量本身是一个独立的变量存储的是地址需要解引用才能访问目标引用是目标变量的别名不分配单独的内存空间。验证程序里couta输出的是 a 的地址cout*(a)通过地址访问输出值cout*pa是指针解引用coutra是直接使用引用。习题 1.5 思考题 3 里有一段对比说明引用必须在定义时绑定之后不能重新绑定指针可以在程序运行过程中改变指向。这个特性决定了它们在函数参数传递中的不同应用场景。常见做法是需要修改实参且不允许空值时用引用需要指向不同对象或可能为空时用指针。// 验证三种访问方式 #include iostream using namespace std; int main() { int a; cout a; cin a; // 从键盘读入 a 的值 a a 5; // 名访问直接用变量名修改 cout a endl; // 输出变量 a 的地址 cout *(a) endl; // 地址访问通过取址再解引用输出值 int *pa a; // 定义指针变量 pa存储 a 的地址 cout *pa endl; // 间址访问通过指针解引用输出值 int ra a; // 定义引用 ra绑定到 a cout ra endl; // 引用访问直接用引用名输出值 return 0; }这段代码的逻辑分层很清晰名访问直接操作地址访问通过取地址再用*解引用指针访问先用int* paa保存地址再用*pa取值引用访问int raa绑定后直接用。参数说明里值得注意的是cin a设置了初始值后续aa5修改了值所以三种访问方式输出的都是修改后的值。如果你想看看修改前和修改后的区别可以把aa5这行注释掉再跑一次。5.2 字符型常量与 ASCII 码的嵌套验证习题 1.1 第 12 题里的\101实际上是八进制转义101 转换成十进制是 65正好是 ASCII 码表中大写字母 A。这在输入验证场景里很常见需要处理控制字符时用转义序列表示比手工拼不可见字符更可靠。源码里\n是新行\t是制表符\101是特定 ASCII 字符。C 转义字符表里\x开头接十六进制数也合法但教材里更强调八进制写法因为历史原因很多教科书沿用了 C 语言早期习惯。5.3 引用与指针的 const 约束对比习题 1.5 思考题 5 给出了只读约束的多种形式const int a5是常量标识符int* const pa是指针常量p 本身不能改指向const int* pa是指向常量的指针不能通过 p 修改 a 的值但 p 可以指向其他变量const int* const pa两者都不可变const int raa是常引用不能通过 ra 修改 a。这些组合在函数传参时非常实用函数参数写成const int*或const int可以承诺不修改传入的数据同时避免拷贝大对象的开销。判断一道题是否理解到位可以看能不能回答这个问题int a5; const int* pa; *p10;合法吗不合法编译器会报错因为 p 是指向常量的指针不能通过它修改 a 的值。但a10合法因为 a 本身不是常量。这个区分是考试和面试都喜欢问的点。6. 避坑记录程序能编译但结果不对问题出在哪6.1 赋值号当等号用现象判断两个变量是否相等时if(ab)编译通过程序输出了“9 等于 9”两个输入值明明不等。原因ab是赋值表达式C 里 if 语句会把任何表达式当作条件判断赋值表达式的值是赋完后的值。b 输入 9非零即为真所以走了正确分支。解决条件判断必须写if(ab)。我一般会用编译器的-Wall警告选项开启后if(ab)会提示assignment instead of comparison。如果你用集成开发环境也要注意有没有开警告级别默认配置下这种错误很容易漏掉。6.2 switch 遗漏 break 导致穿透现象程序输入成绩等级 C预期输出“70-79”实际输出连着输出了“70-79”和“60-69”两行。原因case 语句没有 break执行完当前分支后继续往下执行。解决每个 case 末尾补上 break或者用 return 直接返回。检查源码时我会先扫一遍所有 case 的结尾看到case 1: ... case 2:这种紧挨着的写法就要警惕。有些场景反而可以利用穿透来合并多个 case比如多个值映射到同一个结果时写case A: case a:。6.3 变量定义放在 if-else 语句体里导致未定义现象编译报错undeclared identifier提示 a 没有定义。原因if(cy) int a1; else int a0;中 a 的作用域在语句块内部出了块就没了后面的couta引用不到。解决把变量声明提到 if-else 之前语句体内只做赋值。这是 C 变量作用域的基本规则但在考试题里容易被忽略。实际编码时我习惯把所有变量声明放在作用域开头避免这种问题。6.4 浮点数直接比较相等现象判断ab c时数学上成立但程序输出 false。原因浮点数在计算机里是二进制近似存储0.10.2 的结果不是精确的 0.3而是 0.30000000000000004直接比较等于会失败。解决用差的绝对值跟一个很小的阈值比较fabs(ab-c) 1e-9。工程上常用std::abs配合std::numeric_limitsdouble::epsilon()来做近似比较。考试里一般不会出这种题但自己写代码时几乎一定会遇到。从那以后我每跑一个 C 程序不管题目多简单都会先开警告选项、再用边界值测一遍输入尤其是 0、负数和超大数最后才下结论说程序没问题。以前总觉得自己手算的结果一定对被这几道题的隐性坑虐过几次之后才明白编译器通过只是第一步逻辑正确才是更难的功课。希望这份习题答案的拆解能帮你省下一点踩坑的时间把每个知识点背后的原理都过一遍而不是只把答案抄下来。本文还有配套的精品资源点击获取