简介:一套面向北京理工大学乐学在线平台的C语言程序设计答案合集,完整覆盖平台内的全部测试用例,适合正在学习C语言基础、准备乐学作业或期末上机考试的高校学生。资源共128个文件,包含65个C++源代码文件和63个可执行exe程序,压缩包整体仅5.19MB,体积轻便;源码方便阅读和二次修改,exe程序可直接运行观察输出结果,便于快速验证答案正确性。目前已有6555人学习使用,目录按题目类型清晰划分,所有代码均编译通过,可直接迁移到自己的工程中。内容涉及日期计算、链表操作、指针应用、素数计数、字符串缩写展开等常见编程练习,几乎每道题都给出了完整可运行的解决方案。对于遇到编译错误或逻辑不对的同学,既可以对照源码排查思路,也能通过exe直观确认预期输出,有效节省调试时间,特别适合需要系统梳理乐学平台题目、快速补齐C语言上机短板的学习者。
1. C语言上机考试考的不是语法,是对测试用例负责
C语言程序设计这门课,笔试和上机能拉开差距的地方,从来不是“会不会写语法”,而是“能不能对隐藏测试用例负责”。某高校在线评测平台上的题目,表面是写代码,实际是拿输入换输出,后台用一组你没见过的数据判分。平时自己写代码能跑通、一上评测就翻车的同学,十有八九栽在边界条件上。这门课的高频考点其实非常固定:指针与数组、函数与递归、结构体与链表、字符串处理,再加上若干经典易错点。这篇按考点分布拆开讲,每个模块配真题和满分答题思路,适合正在备考上机考试、或者要准备C语言岗位面试的人直接拿来背。
2. 指针与数组:三个必考题型与字符串真题的答题模板
2.1 考点拆解:指针与数组到底考什么
指针与数组在笔试卷里通常占 20 到 30 分,在上机题里几乎必占第一题。高频考法只有三种:数组名作为函数参数时发生了什么、指针偏移与下标运算的等价关系、字符串以\0结尾带来的边界问题。选择题爱考第一种,改错题爱考第二种,上机题爱考第三种。
先说数组名做参数。数组名在函数调用时不是“把整个数组复制一份”,而是退化为指向首元素的指针。这意味着函数内部拿不到数组长度,sizeof(arr)在函数里得到的是指针大小,不是数组字节数。所以一个标准的函数原型要写成void func(int arr[], int n),n必须显式传进来。这是面试官最爱追问的一句,能说出来就是一个得分点。
指针偏移与下标的等价关系是*(arr + i)等价于arr[i],两者可以互换。真正容易被问住的是“为什么可以互换”:因为数组在内存里是连续存储的,arr + i就是跳过 i 个元素后的地址。至于字符串,C 语言的字符串就是字符数组,结束标志是\0,遍历时判断s[i] != '\0',而不是数到某个固定长度,这是所有字符串题型的共同前提。
2.2 真题演练:字符串原地逆序的满分写法
某在线评测平台的第一题经常是“将输入的字符串原地逆序输出”。先看标准答案:
#include <stdio.h> #include <string.h> void reverse(char *s) { if (s == NULL) { // 空指针保护,评测数据里可能出现 return; } int left = 0; int right = strlen(s) - 1; // 用 \0 前的位置作为右边界 char temp; while (left < right) { temp = s[left]; s[left] = s[right]; s[right] = temp; left++; right--; } }这道题拿满分的关键是“原地”两个字:不开新数组,直接交换首尾字符。strlen(s) - 1得到的是最后一个有效字符的下标,不是\0的位置,很多第一次写的同学会用strlen(s)做右边界,结果把\0换到前面去了,输出整段乱掉。
空字符串的情况值得单独说。当s是空串时,strlen(s)等于 0,right是 -1,while (left < right)条件不成立,函数直接返回。这就是为什么判断条件用left < right而不是left != right:后者在奇偶长度不同的字符串上会产生不同的行为,前者对所有长度都稳定。评测平台的隐藏用例一定包含空串和单字符,这两个用例能过,这道题基本不丢分。
面试里这题还有个高频追问:“如果不用strlen,自己实现字符串长度的统计怎么写?”答题思路是写一个循环遍历到\0:
int my_strlen(const char *s) { int len = 0; while (s[len] != '\0') { len++; } return len; }这题考核的点不在算法,而在对\0语义的理解。统计长度时只加有效字符数,不把\0算进去,这一点在后面的字符串拼接、复制题里都是通用的。
2.3 常见错误:数组越界为什么在本地看不出来
数组越界是最典型的“本地跑得好好的,一提交就错了”的题。原因在于越界访问的是相邻内存单元,本地运行时相邻位置刚好是空闲或未使用,程序不崩溃,输出看起来也是对的;评测平台上内存布局不同,越界读到的可能是其他变量甚至判题数据,结果就变成随机乱码。
这类题错在哪:循环边界多写了一个等号,比如for (i = 0; i <= n; i++)访问了不存在的a[n];或者字符串处理时把\0位置当成有效字符去修改。为什么错:C 语言本身不检查数组越界,错得再离谱也不报错,全靠程序员自己对边界负责。怎么答对:循环边界一律写i < n,涉及字符串时遍历终点用s[i] != '\0'而不是下标达到某个固定值。写完之后,把长度为 0、长度为 1、长度为最大值的用例各跑一遍,这三类边界用例过完,越界问题基本能堵住。
3. 函数与递归:传参选择和经典递归题的答题套路
3.1 考点拆解:值传递与地址传递怎么选
函数在考试里单独占 15 分左右,递归再占 10 分左右。选择题最常考的是传参:C 语言只有值传递,没有引用传递。所谓“传地址”也只是把地址的值复制一份给形参,只不过这个值可以用来找到实参的内存单元,从而间接修改实参。
判断标准非常固定:函数内只需要读数据、不需要改动实参,直接传值;函数内要修改实参本身,传指针;函数只需要返回一个计算结果,优先用返回值而不是用指针“带出来”。上机题里常见的“写函数交换两个数”“写函数统计并返回个数”“写函数修改结构体内容”,分别对应这三种场景。
有个容易混淆的点:传指针和传数组名的区别。数组名做参数时本身就是地址传递,函数内对数组元素的修改是有效的;但函数内对形参指针重新赋值,不会影响调用方的指针变量。如果要修改“指针本身”,需要传二级指针。这个点在第 4 章的链表题里会再次出现,现在是打基础的时候。
3.2 真题演练:交换函数与斐波那契的标准答案
交换两个 int 变量的值是笔试题里的“送分题”,也是面试官最爱用来开头暖场的题。
void swap(int *a, int *b) { int temp = *a; // 取出 a 指向的值 *a = *b; // 把 b 的值放进 a 指向的内存 *b = temp; // 把原 a 的值放进 b 指向的内存 }调用方式必须是swap(&x, &y),传入两个变量的地址。很多同学知道要写*a = *b,却在调用时写swap(x, y),编译器会警告但照样能编译,运行后两个变量纹丝不动。为什么错:形参a拿到的是 x 的拷贝值,函数内就算把*a改上天,也只是改了一个跟 x 无关的内存位置。怎么答对:写出函数后在调用处画一个内存图,实参和形参的关系一目了然,地址传过去才能通过指针找到原来的变量。
递归题则固定是“求斐波那契数列第 n 项”或“求阶乘”。
long long fib(int n) { if (n <= 1) { // 终止条件必须在最前面 return n; } return fib(n - 1) + fib(n - 2); // 递推关系 }这题的关键是终止条件写在函数体的第一行,并且覆盖n = 0的情况。如果只写if (n == 1) return 1;,当n = 0时函数会调用fib(-1)和fib(-2),无限递归直到栈溢出,评测平台直接判运行时错误。评测数据里一定包含n = 0,这是专门用来卡这种半吊子写法的。
long long的使用也值得说一句。斐波那契数列增长极快,n = 40时已经超过一亿,int会溢出变成负数,平台用大数用例一跑就错。笔试写类型时,涉及递归增长速度的序列,优先用long long,这一点是很多同学的失分点。
3.3 递归答题模板:先写终止条件,再写递推关系
递归题在考场上不要现场想逻辑,直接用固定模板。
第一步,把题目给的递推关系写出来,比如阶乘是f(n) = n * f(n-1),斐波那契是f(n) = f(n-1) + f(n-2)。第二步,找到递归的“出口”,也就是不需要继续递归就能直接返回答案的输入,通常是n = 0或n = 1,写进函数体第一行。第三步,把递推关系翻译成 return 语句,注意子问题的参数必须朝终止条件靠近。
在试卷上,递归题还要求写出调用过程,比如fib(4)会先算fib(3)和fib(2),fib(3)又拆成fib(2)和fib(1)。写上这个调用树,阅卷老师能确认你真的理解递归展开过程。上机时则要注意 n 的范围,40 以内的直接用递归可以通过;n 超过 40 还递归,时间会暴涨,这时应该改用循环迭代。答题前先看题目的 n 范围,再决定写法,这是上机拿分的习惯。
4. 结构体与链表:高频真题与内存相关考点
4.1 考点拆解:结构体变量与结构体指针的分工
结构体和链表是上机题的第二大来源,笔试卷里通常占 15 到 20 分。基础考法集中在两处:结构体成员的访问方式和结构体数组的排序。链表题则是从结构体延伸出来的必考大题。
结构体成员的访问有个规律:用.还是->取决于手里是变量还是指针。定义的是struct node n,就用n.data;定义的是struct node *p,就用p->data,等价于(*p).data。这个点看着简单,但上机题里大量同学在结构体指针上写点运算符,编译器报错后不知道怎么改。
结构体数组的排序题是二级考试的常客。给一个struct student数组,按成绩从高到低排序,排序算法本身和普通数组排序一样,只是比较的是arr[i].score,交换的是整个结构体变量。
struct student { int id; char name[20]; int score; }; void sort_students(struct student arr[], int n) { int i, j; for (i = 0; i < n - 1; i++) { for (j = i + 1; j < n; j++) { if (arr[j].score > arr[i].score) { // 按成绩降序 struct student temp = arr[i]; // 结构体变量可直接整体交换 arr[i] = arr[j]; arr[j] = temp; } } } }这里有个小考点:C 语言允许结构体变量直接整体赋值,temp = arr[i]不需要逐个成员复制。很多从其他语言转过来的同学不敢这么写,非要strcpy每个字段,结果字符串长度超了还出错。结构体整体赋值是合法的,这也是考试里的一道经典改错题。
4.2 真题演练:有序链表插入的完整实现
链表题在评测平台上的经典考法是“向有序链表插入一个新节点,保持链表有序”。标准实现如下:
struct Node { int data; struct Node *next; }; void sorted_insert(struct Node **head, struct Node *new_node) { // 链表为空,或新节点应该插在最前面 if (*head == NULL || new_node->data < (*head)->data) { new_node->next = *head; *head = new_node; return; } // 找到第一个比新节点大的节点位置 struct Node *cur = *head; while (cur->next != NULL && cur->next->data < new_node->data) { cur = cur->next; // 向后移动,直到合适位置 } // 把新节点插到 cur 的后面 new_node->next = cur->next; cur->next = new_node; }为什么头指针要写成struct Node **head而不是struct Node *head?这是这道题最重要的考点。如果新节点需要插到链表头部,意味着头指针本身要指向新节点。函数参数是struct Node *head时,函数内修改head只会改形参,调用方手里的头指针纹丝不动。传二级指针,函数内解引用*head = new_node才能真的改到调用方的头指针。函数签名里出现两个星号,就是为了“修改指针本身”。
插入位置的分情况讨论也有套路。新链表为空时,直接让头指针指向新节点;新节点比头节点小,插到头部;否则用一个cur指针沿途走,找到“下一个节点的值大于等于新节点值”的位置,把新节点插到cur后面。循环条件cur->next != NULL保证了不会越过链表尾部,cur->next->data < new_node->data保证了有序性。评测平台的隐藏用例一定包含插空链表、插头部、插中间、插末尾四类,每类都要手动走一遍。
4.3 内存考点:结构体大小与动态分配
选择题里还有一类必考题:给一个结构体,问sizeof是多少。这里有个反直觉的点,sizeof不等于各字段字节数简单相加,因为有内存对齐。比如:
struct example { char c; // 1 字节 int x; // 4 字节 };很多同学会填 5。在常见 32 位或 64 位环境下,int需要按 4 字节对齐,char后面会补 3 个空字节,sizeof的结果是 8。评测平台的题目如果考到,会在题目里注明“不考虑对齐”或“环境为 32 位”,但答题前最好先确认字段类型,再套规则:结构体总大小是最大成员对齐数的整数倍。
链表题则离不开malloc和free。动态分配后必须判空,这是上机题常被忽略的步骤:
struct Node *p = (struct Node *)malloc(sizeof(struct Node)); if (p == NULL) { return; // 分配失败,评测平台可能构造内存不足的用例 }free之后把指针置空是面试中爱追问的细节,虽然函数结束后局部指针会消失,但对一个已经释放的内存再次free会崩溃,置空后可以避免重复释放。评测平台的内存检测会检查泄漏,每次malloc都对应一次free,这是链表大题拿满分的最后一步。
5. 易错题清单:五个让你在评测平台丢分的常见错误
5.1 scanf 漏写地址符
错在哪:写成scanf("%d", n);,漏掉&。为什么错:scanf需要的是变量地址,不是变量值,漏写后它拿到的地址是一个随机值,程序不一定崩溃,但变量内容变成垃圾数据。怎么答对:写完每行scanf后立刻检查参数列表,只要变量名前面没有&,这条大概率就错了。字符串数组名做参数时不需要&,因为它本身就是地址,这是唯一不会写反的特例。
5.2 用 == 比较字符串内容
错在哪:写成if (str1 == "abc")。为什么错:str1是数组名,退化成地址后,==比较的是两个地址是否相等,不是内容。两个内容相同的字符串,内存地址可能完全不同,结果永远是假。评测平台用不同样例一测,这类写法直接判错。怎么答对:字符串比较一律用strcmp(str1, "abc") == 0,表示相等;返回值非 0 表示不相等。字符用单引号比较可以,str1[i] == 'a'是对的,字符串一定要调strcmp。
5.3 循环边界多写一个等号
错在哪:for (i = 0; i <= n; i++),当 i 等于 n 时访问了a[n],而数组只有a[0]到a[n-1]。为什么错:C 语言不检测数组越界,越界访问可能读到脏数据也可能恰好读到 0,本地测试时结果正常,评测平台换上别的数据就错。怎么答对:循环条件固定写i < n,不要写i <= n。涉及字符串时,遍历条件用s[i] != '\0',不要在循环体里手动为\0赋值。
5.4 递归题漏掉 n = 0 的终止条件
错在哪:求阶乘只写if (n == 1) return 1;,求斐波那契只写if (n == 1) return 1;。为什么错:n = 0时没有出口,递归变成fib(-1)无限延伸,直到栈空间耗尽,评测平台返回运行时错误。所有同学都知道要写终止条件,但只写一半等于没写。怎么答对:写递归函数时先问两个问题——输入最小合法值是多少,这个值能直接返回吗。n = 0能直接返回,就一定要写进条件里。
5.5 字符读取时换行符污染
错在哪:先执行scanf("%d", &n);再执行scanf("%c", &ch);,第二次读到的不是想要的字符,而是上一次输入末尾的换行符。为什么错:%c会把缓冲区里残留的回车当成有效字符读走,程序看似正常,输出却总是莫名其妙错一个字符。怎么答对:
scanf("%d", &n); getchar(); // 把缓冲区里的换行符吃掉 scanf("%c", &ch);或者在%c前面加空格:scanf(" %c", &ch);。空格会跳过任意空白字符,包括换行。评测平台里这类题固定先给数字再给字符,两次输入之间不加处理,一定翻车。
6. 答题技巧:测试用例驱动的三步答题法
上机考试和笔试的最大区别,是答案不是“写对”就行,而是平台说对才行。平台怎么判断对错?用隐藏测试用例跑你的程序。所以备考的核心不是背代码,是培养“给测试用例打工”的习惯。三步走下来,能覆盖九成以上的踩坑点。
第一步,读题后先不急着写代码,把输入输出拆成三个用例:正常用例、边界用例、极端用例。正常用例就是题目示例,边界用例是空串、单字符、数组长度为 1,极端用例是最大值、最小值。比如题目说“输入小于 100 的整数”,就准备 0、1、99 三个数据。把这些写在草稿纸上,代码写完直接对照着验证。
第二步,写代码时给每个可能越界的位置加保护:数组循环统一i < n;字符串处理统一判断\0;malloc后判空;函数入口对指针判空。这些保护不是多余的代码,是专门为评测平台的隐藏用例写的保险。
第三步,提交前把草稿上的用例按顺序跑一遍,四个特殊位置必须覆盖:最大输入、最小输入、空输入、非法输入。这四类恰好对应递归边界、数组边界、字符串空串、指针为空四种情况。评测平台不会手软,但你的草稿纸会。
我在自己上机时也翻过车,某道题本地运行一万次都正常,提交后栽在一个空输入上。从那以后我每次写完都强制把空串和最小值跑一遍,再没让平台给过我难看。这套流程对笔试同样有效:选择题遇到“输出是什么”的题,代入一组特殊值先算出结果再选,比盯着代码心算快得多。希望帮到你。
本文还有配套的精品资源,点击获取