资讯动态

C语言复习题第11-20题详解:从数组下标到链表反转的核心考点

发布时间:2026/10/16 5:26:22 来源:尧图企业网站定制
做C/C复习的时候题目的顺序其实比大多数人想象的重要。50道经典题的11到20题刚好卡在“基础语法已经讲完指针、字符串、结构体刚刚开始混合使用”的位置上。这个区间的题目不算难但它们是筛选分层最明显的一段能把这几道题一遍写对的人后面链表和二叉树基本不太会翻车在这几道题上反复改错的人多半是在数组下标和指针传参的理解上还有漏洞。这篇文章就针对这套复习题里的第11到20题把每道题的命题意图、标准解法和常见错法拆开讲清楚顺带聊聊复习的时候应该怎么分配精力。1. 第11-20题的内容地图考点、难度与题型分布1.1 十道题覆盖了什么内容在开始逐题说之前先看一张整体的考点分布这样刷题的时候心里有底不会写完一道就忘了前面一道。题号核心考查点涉及的主要语法难度第11题数组逆序输出数组下标、循环边界★第12题最值及其位置定位数组遍历、多重条件判断★第13题水仙花数三位数拆位取余、整除、循环枚举★★第14题字符分类统计getchar/scanf、字符判断★★第15题十进制转二进制递归递归函数、终止条件★★第16题冒泡排序优化数组交换、标志位★★第17题结构体成绩排序结构体数组、排序算法★★★第18题单链表反转链表节点、指针操作★★★第19题动态数组平均值malloc/free、内存判空★★★第20题有序数组二分查找区间划分、while边界★★★这几道题有一个很明显的特点单看每一道都不算难但它们把C语言最核心的几块拼图全占了——数组是数据结构的地基指针是C语言的灵魂结构体是组织数据的起点链表和动态内存则是面试题的常客。第11到20题正好把这几块串了起来所以它不像前面1到10题那样纯粹练语法而是开始让你用“程序员的思路”去解决问题。1.2 为什么这个区间值得单独刷一遍看过这套50题的人会发现1到10题基本是三种题型的反复输入输出、简单判断、单层循环。到了第11题题目突然开始“组合”了。比如数组逆序要同时处理下标和循环方向水仙花数要把三位数拆成三个数字再验算结构体排序更是把结构体、数组、排序三种技能揉在一起。从学习节奏上看11到20题是“由单点到综合”的转折段。这个阶段如果只是看答案、抄代码后面的题会越来越吃力反过来如果能在这一组题里养成“先画思路、再写代码、最后自查边界”的习惯进入第21题以后的链表、字符串处理、文件操作等章节时会顺畅很多。我见过不少同学直接跳过这一段去刷后面的题结果在第35题附近卡了好几天回头补的正是这部分的基础。刷这一组题的另一层价值在于它很适合用来“自我体检”。因为每一道题对应的知识点非常明确第11题考数组下标第12题考遍历第17题考结构体第19题考内存管理。只要看你在哪道题上花了最多时间就能定位出自己最薄弱的部分。与其盲目刷完50题不如先拿这10题做一次诊断。2. 逐题拆解五组题目的核心思路与参考代码这一组题目虽然编号是连续的但每道题考的东西差异很大。为了避免看代码看得太散我把11到20题分成了五个小组每组放在同一个小节里讲先讲命题意图再给参考实现最后指出最常见的翻车点。2.1 数组三题第11题到第13题稳扎稳打的下标控制第11题最常见的考法是“输入n个整数把数组逆序输出”进阶版本要求“在原数组内完成交换”。初学者容易犯的错是新建一个数组去拷贝虽然结果对但完全没理解“逆序”的核心其实是下标映射。#include stdio.h #define N 10 int main() { int a[N] {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; // 原数组内逆序交换 a[i] 与 a[N-1-i] for (int i 0; i N / 2; i) { int temp a[i]; a[i] a[N - 1 - i]; a[N - 1 - i] temp; } for (int i 0; i N; i) { printf(%d , a[i]); } return 0; }关键点在于交换次数是N / 2不是N。如果循环写成了i N数组会被换两遍等于白做。很多人在这个细节上第一次出错原因就是没有想清楚“一次交换同时处理两个位置”。如果题目要求的是“输出逆序但不改变原数组”那反而更简单直接从最后一个下标往前遍历输出即可不需要交换。第12题常见的考法是“输入n个数找出最大值和最小值的位置第一次出现的位置”。和单纯求最值不同这题需要记住下标。#include stdio.h int main() { int a[100], n; scanf(%d, n); for (int i 0; i n; i) { scanf(%d, a[i]); } int maxPos 0, minPos 0; for (int i 1; i n; i) { if (a[i] a[maxPos]) maxPos i; if (a[i] a[minPos]) minPos i; } printf(最大值位置:%d 最小值位置:%d\n, maxPos, minPos); return 0; }这个题的坑在初始化。很多人习惯把maxPos和minPos都初始化成0这在大多数情况是对的但如果数组里全是同一个数程序也会正确返回0和0。真正需要注意的逻辑是比较时用的是a[maxPos]而不是写死的a[0]这样才能保证位置会跟着更新。还有一种考法是“要求输出首次出现的位置”这时比较符号用和就好如果要求最后一次出现的位置就要用和。第13题是水仙花数打印所有三位水仙花数即满足“各位数字的立方和等于该数本身”的三位数。核心是拆位。#include stdio.h int main() { for (int num 100; num 999; num) { int a num / 100; // 百位 int b num / 10 % 10; // 十位 int c num % 10; // 个位 if (a * a * a b * b * b c * c * c num) { printf(%d\n, num); } } return 0; }拆位时最容易混淆的是十位num / 10 % 10是先去掉个位再取最后一位很多人写成num % 100 / 10结果虽然也对但理解上容易绕。这里我建议用“整除丢掉低位、取余丢掉高位”的口诀来记。另一个常见问题是有人在这里用pow(a, 3)逻辑没错但pow返回的是浮点数在某些编译器上直接和整数比较会因精度问题出错所以纯整数运算就不要引入库函数。2.2 字符串与递归第14题、第15题两类“看不见”的状态第14题一般是这样输入一行字符统计其中英文字母、数字、空格和其他字符的个数。这题考的其实是两层逻辑第一层是“能不能正确处理带空格的输入”第二层是“字符分类条件写没写全”。#include stdio.h #include ctype.h int main() { char ch; int letters 0, digits 0, spaces 0, others 0; while ((ch getchar()) ! \n) { if (isalpha(ch)) { letters; } else if (isdigit(ch)) { digits; } else if (ch ) { spaces; } else { others; } } printf(字母:%d 数字:%d 空格:%d 其他:%d\n, letters, digits, spaces, others); return 0; }这里要特别注意getchar()的返回值。它返回的是int不是char。原因很简单EOF是-1而合法字符可能是0到255之间的任何值如果定义成char在某些平台上会把EOF截断导致循环无法正确终止。如果题目要求的是“从键盘输入一行”那循环条件写成ch ! \n没问题如果改成“从文件输入直至文件结束”就必须写成ch ! EOF。第15题是“用递归把十进制整数转换成二进制字符串输出”。递归的写法比循环更容易理解但前提是搞清楚递归出口和输出时机。#include stdio.h void decToBin(int n) { if (n 1) { decToBin(n / 2); } printf(%d, n % 2); } int main() { int n; scanf(%d, n); decToBin(n); printf(\n); return 0; }这段代码的核心是递归调用发生在printf之前所以底层递归会先算完再一层层往外输出。实际打印的时候最底层的n是1或0先输出最高位最后才输出最低位正好和二进制数的书写顺序一致。如果把printf放到递归调用之前输出就会反过来变成从最低位开始打印。很多人在这个题上栽跟头不是因为递归不会写而是没处理负数。十进制转二进制在“补码”语义下非常复杂复习资料里一般默认输入为正整数所以题目没说的话你可以在函数入口加一句判断或者直接约定0和正整数。如果面试中被追问到负数需要先转换成无符号数再看二进制位那是另一个层面的考点了。2.3 排序结构体与二分查找第16、17、20题先搭框架再填细节第16题是冒泡排序的经典考法但近些年很多复习资料会要求“对冒泡排序做优化如果某一趟冒泡过程中没有任何交换提前结束”。优化的思路是引入一个标志位。#include stdio.h void bubbleSort(int a[], int n) { for (int i 0; i n - 1; i) { int swapped 0; for (int j 0; j n - 1 - i; j) { if (a[j] a[j 1]) { int temp a[j]; a[j] a[j 1]; a[j 1] temp; swapped 1; } } if (swapped 0) { break; } } }这里的关键是内层循环的上界n - 1 - i。每一趟冒泡都会把当前未排序区间里的最大值“冒”到最右边所以下一趟只需要比较剩下的元素这个上界必须一次比一次小。标志位swapped如果一整趟都没有变化说明数组已经有序可以提前退出避免无意义的扫描。这个优化在“数组基本有序”的场景下特别有效但它的价值不是提高理论复杂度而是体现了一个非常重要的编程意识——不要做已经不需要做的工作。第17题的结构体排序在复习题里通常是这么出的定义一个学生结构体包含学号、姓名和若干门课成绩输入n个学生按总分从高到低排序输出。完整代码比较长只说核心部分。#include stdio.h #include string.h struct Student { char id[20]; char name[50]; int score[3]; int total; }; void sortStudents(struct Student stu[], int n) { for (int i 0; i n - 1; i) { for (int j 0; j n - 1 - i; j) { if (stu[j].total stu[j 1].total) { struct Student temp stu[j]; stu[j] stu[j 1]; stu[j 1] temp; } } } }结构体排序的考点主要有三个一是结构体有没有定义对二是排序时比较的是哪个字段三是交换时能不能直接用一个结构体变量做临时存储。前两点不难第三点是C语言里比较有特色的地方——结构体可以直接整体赋值编译器会为你做逐字段拷贝所以不需要像交换基本类型那样一个个字段换。但如果结构体里嵌套了指针直接整体交换就会变成浅拷贝这是后面第19题附近要重点防的坑。第20题是二分查找命题形式通常是“在有序数组中查找某个值返回下标不存在返回-1”。这道题看着简单实际上边界极其容易错。int binarySearch(int a[], int n, int target) { int left 0, right n - 1; while (left right) { int mid left (right - left) / 2; if (a[mid] target) { return mid; } else if (a[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }这段代码里有两个值得说的细节。第一mid的写法用的是left (right - left) / 2而不是(left right) / 2。表面上看两者数学等价但后者在left right很大的时候可能溢出这个细节在面试和考试里能拉开差距。第二while 条件用了left right这时每轮更新边界必须把mid排除掉写成left mid 1或right mid - 1否则当目标正好等于边界时会陷入死循环。如果改成left right的写法循环结束后还需要额外判断一次a[left]是否等于目标两套写法思路完全不同最怕的是混着用。2.4 链表与动态内存第18题、第19题指针生命周期管理第18题要求创建单链表并实现反转输出。很多资料里这一题分两个版本一个是不改变链表结构递归逆序打印另一个是真正把链表的指针方向反转。后者更有代表性。#include stdio.h #include stdlib.h struct Node { int data; struct Node *next; }; struct Node* reverseList(struct Node *head) { struct Node *prev NULL; struct Node *curr head; while (curr ! NULL) { struct Node *next curr-next; // 先存住下一个节点 curr-next prev; // 当前节点指向前一个 prev curr; // prev 后移 curr next; // curr 后移 } return prev; // 新头节点 }反转链表的考点很纯粹在修改curr-next之前必须先用一个临时变量保存原来的next。否则一旦把curr-next改成prev你就再也找不到后面的节点了。这个“先存后改”的思路在整个指针操作里都是通用原则做第19题乃至后面第30题以后的二叉树时也一直在用。另一种考法是“递归逆序输出而不改结构”那是用到了函数调用栈天然的后进先出特性代码更短但原理上不练到指针反转这么透彻。第19题考动态内存从键盘读入n动态分配长度为n的数组输入n个整数并求平均值最后释放内存。这是C语言里“用多少、要多少”的典型场景。#include stdio.h #include stdlib.h int main() { int n; scanf(%d, n); int *arr (int*)malloc(n * sizeof(int)); if (arr NULL) { printf(内存分配失败\n); return 1; } int sum 0; for (int i 0; i n; i) { scanf(%d, arr[i]); sum arr[i]; } printf(平均值: %.2f\n, (double)sum / n); free(arr); arr NULL; return 0; }最容易丢分的地方有三个第一malloc后没有检查返回值。内存分配不一定每次都成功尤其在嵌入式或内存受限环境里不判空是隐患。第二求平均值时用了(double)sum / n如果写成sum / n整数除法会直接把小数部分丢掉。第三忘记free(arr)。在复习题里忘 free 一般不会让程序运行出错但放大了看就是内存泄漏的基础形态。释放后把arr置空属于好习惯虽然在这段代码里后面已经没什么操作了但这能帮你养成防止“野指针”的条件反射。3. 刷这几道题最容易翻车的五个细节3.1 数组下标边界问题速查第11到13题里反复出现下标问题最容易犯的三类错误是交换数组时循环条件写成i N不是i N / 2遍历数组时从1开始却忘了给a[0]参与比较还有把数组长度n和最后一个下标n-1混为一谈。尤其在二分查找里right n - 1写错成right n的话第一轮mid就可能越界访问。处理边界通用的方法是在写循环之前先手动代入最小规模和最大规模各跑一遍。比如数组长度为1时循环进不进长度为2时交换几次这个习惯比背任何口诀都可靠。3.2 指针与字符串常量的混用第14题字符串统计题很多人会顺便练习用指针遍历字符串。这里有一个非常典型的坑char *p hello;和char str[] hello;看起来都能用但前者指向的是字符串常量内容不应该也不能被修改如果试图执行*(p 1) a在不少环境下会直接崩溃或产生未定义行为。复习时建议建立一条明确的规则需要修改字符内容就用字符数组只是读取才可以用指针指向字符串常量。3.3 递归出口与栈溢出第15题的递归其实很简单但有的同学会把终止条件写成if (n 0)而不是if (n 1)。这会导致二进制转换时多做一层递归最终多打一个前导0。比如输入5正确结果是101写错终止条件可能输出0101。如果题目扩展成“斐波那契数列”那类递归题还可能因为递归层数过深导致栈溢出。复习递归题时建议画一下“调用树”每一层参数是怎么变化的最深一层是什么状态返回时又是怎么一层层往外传结果的。3.4 结构体数组的浅拷贝陷阱第17题的结构体交换整体赋值确实方便但你要知道这背后是逐字节拷贝。如果结构体里有指针成员比如char *name动态分配直接temp stu[j]会让两个结构体里的指针指向同一块内存。后面修改其中一个另一个也会跟着变更危险的是如果释放两次程序直接崩溃。在复习阶段结构体里如果出现指针交换时就要考虑“深拷贝还是浅拷贝”这道题没考这个点但它是后续很多综合题的基础。3.5 忘记释放内存与重复释放第19题强调free但还有另一个高频错误在链表反转或删除操作里释放了某个节点之后还在用它的next。正确顺序是“先保存下一个节点的地址再释放当前节点”。另外free只是把内存归还给堆管理器指针变量本身还保留着旧地址如果不置空这个指针就成了“野指针”。重复释放野指针会导致未定义行为完全不是运行环境报不报错能预测的。4. 复习路线与自查方法怎么把“会做”变成“拿分”4.1 十道题的刷题顺序建议如果你不是第一次刷这套题我建议不要按题号硬刷而是按依赖关系来。先刷第11、13题把数组下标和循环边界的基础打牢再刷第14、15题体会字符处理和递归出口然后做第16、17题练习排序和结构体组合最后集中啃第18、19、20题。第20题二分查找虽然按题号排在最后但它根本不依赖前面任何一道题单独拎出来也能练。反而是第16题冒泡排序先练好第17题结构体排序才能写得舒服因为排序框架是一样的。每个小阶段之间留一点间隔不要在同一天把这十道题全做一遍。原因是相邻的题目考点接近连续做会产生“我已经懂了”的错觉。隔两三天再回头看第11题如果还能在5分钟内写对这一组才算真正过掉。4.2 错题复盘的关键问题刷题之后比代码更重要的是一份“错题记录”。不必抄整段代码而是记录三个东西错在哪一行、为什么错、以后如何避免。我见过很多同学把错误答案删掉只留下正确答案第二天再刷同样的题还是踩同一个坑。正确的复盘方式是保留错误痕迹逼迫自己看清“当时是怎么想的”。自查时问自己四个问题边界条件处理了吗指针有没有可能指向无效内存动态分配的内存释放了吗如果换个输入程序还会不会崩第11到20题的出错点基本都逃不出这四个问题。把这四个问题变成拿到题目后的默认检查动作比多刷十道题更高效。4.3 延伸练习从第11-20题通往后面的35-50题这套50题越往后越综合比如第21题以后常常出现字符串和指针混用的题、文件读写的题、递归与分治结合的题。第11到20题打的基础会在这些题里持续复用。第18题的链表反转是后面很多链表操作题的前置第20题的二分查找是第40题以后“在有序数组中查找插入位置”这类题的简化版本第17题的结构体排序则是文件操作题里数据组织的前提。如果你在第11到20题上花了比较多的时间不用焦虑自己在整套题里进度慢了。这组题真正的价值是“排查”花时间把边界、指针、内存这三件事理清楚后面遇到更复杂的题目时反而会更快。根据我的经验一个人在第11到20题上踩过的每一个坑几乎都在后续的某道题里以更隐蔽的方式重新出现过这组题里没补上的漏洞翻车只是早晚的问题。所以别急着赶进度把这几道题的每个“为什么”都想清楚收益远大于刷完50道题的数量。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑