资讯动态

C语言上机题库刷题指南:结构拆解、批量自测与判题对齐

发布时间:2026/10/23 2:02:01 来源:尧图企业网站定制
简介面向准备吉林大学程序设计C语言课程上机与笔试的学生这份题目汇总覆盖期中、期末两个阶段适合新手入门后集中训练也便于考前按知识点查漏补缺。内容以可编译的C/C源码为主涉及链表创建与指定节点删除、递归求解、数组与字符串处理、排序算法、素数判断、日期合理性判断、文件操作、结构体定义及公式计算等典型考点并附一张笔试真题图片辅助复习。压缩包共25个文件包含23个cpp、1个c和1个jpg整体约2.26MB源码按题目拆分便于逐题阅读、调试和对照思路。已有363人学习适合用于课程实验、上机模拟和期末复盘也能帮助读者从题目类型与代码实现中梳理常见易错点积累可复用的编程解题经验。1. 上机课前一晚那份 C 语言题库到底怎么用上机课前一晚A 同学把题库压缩包解开几十个文件夹、上百道 C 语言题目外加一堆.c参考答案和.txt样例数据他盯着屏幕半小时最后打开的是「输入两个整数求和」。这不是笑话是这类程序设计题库资源最常见的开局题量够但没人告诉你题目怎么分层、样例怎么喂给程序、参考答案和判题机差在哪。这份上机题目集真正值钱的地方不是题多而是它把从变量、分支、循环到数组、字符串、函数、指针、结构体、文件操作的主线题目串成了一条线配着输入输出样例能直接当刷题清单用。刚学 C 语言、要应付课程上机和期末机考的人适合拿它练手想搭个小判题系统或做教学素材的开发者也能把它当题库底座。2. 题库结构拆解从目录骨架到考点难度分层拿到一份上机题目最忌讳的是从第一个文件开始顺着做。题目集通常是按章节或按知识点散落的你不先建立索引做到指针那一章才发现循环还没练熟回头补的成本很高。合理的做法是先摸清文件组织再决定刷题顺序最后才动手写代码。这一步花半小时后面能省好几天。2.1 目录骨架题库文件夹怎么摆才不抓瞎原始题库常见的形态是「一章一个文件夹 若干题面文档 若干参考答案」命名随意1.doc、上机题2.docx、test.c混在一起。我在本地会先重排一遍让每道题成为一个能被脚本处理的最小单元题面、样例输入、样例输出、参考实现各归各位。这样后面无论是手刷还是接自动判题都不用再返工。c-bank/ ├── 01-basics/ # 变量、输入输出、运算符 │ ├── 1001-sum-two/ │ │ ├── statement.md # 题面描述、输入格式、输出格式 │ │ ├── in1.txt # 样例输入 │ │ ├── out1.txt # 样例输出 │ │ └── ref.c # 参考实现 │ └── 1002-circle-area/ ├── 02-control/ # 分支与循环 ├── 03-array-string/ # 一维二维数组、字符数组与字符串 ├── 04-function-recursion/ # 函数、递归、变量作用域 ├── 05-pointer-struct/ # 指针、动态内存、结构体、链表 ├── 06-file/ # 文件读写 └── index.csv # 题号、标题、考点、难度、是否已通过题号建议四位数前两位对应章节后两位是章内序号一眼能看出这道题属于哪个模块。index.csv是整个题库的大脑字段至少留id,title,topic,level,done用 Excel 或任何文本编辑器都能维护后面写脚本读它生成刷题计划也不会被格式卡住。题面统一转成 Markdown图片单独放assets/避免 docx 在不同机器上打开排版错乱——这是血泪经验Word 里的公式和表格换个版本就能给你表演一次消失。2.2 考点分层哪些题该先做哪些题该最后啃上机题目集的难度分布通常不均匀前面几章是送分的输入输出中间突然上强度指针和链表那几道能卡一整晚。我一般按「考点依赖」而不是按题号顺序刷先保证基础模块全绿再碰需要前置知识的题。下面这张映射表可以直接抄进index.csv的topic和level字段。题号段模块核心考点难度前置要求10xx基础语法scanf/printf、格式符、取整与取余A无20xx分支循环if-else嵌套、for/while、累加累乘、素数判定A/B10xx30xx数组与字符串一维遍历、冒泡/选择排序、字符数组、strlen/strcmpB20xx40xx函数与递归形参与实参、值传递、递归出口、汉诺塔、阶乘B/C30xx50xx指针与结构体指针与数组关系、malloc/free、单链表增删改查C40xx60xx文件操作fopen/fscanf/fprintf/fclose、按行读、统计B/C40xx难度 A 是「读懂题就能写」B 是「思路清楚但边界容易错」C 是「要么想不出要么写得出来调不通」。刷题策略上A 类全部过一遍建立手感B 类重点练C 类允许看参考实现再默写一遍——上机考试里 C 类题通常只占一两道把时间全砸进去不划算。2.3 样例数据约定一组输入对应一组输出这类题库最容易踩的坑是样例输入输出和题面描述对不上题面说「多组数据读到文件末尾」样例文件里却只有一组题面说「第一行是组数 T」样例文件第一行却是数据本身。我处理的办法是给每道题固定一套样例命名规则多组数据就编号in1/out1、in2/out2一组数据里的多行原样保留不做任何删改。1001-sum-two/ ├── in1.txt # 1 2 ├── out1.txt # 3 ├── in2.txt # 100 -100 └── out2.txt # 0样例文件里末尾那个换行一定要留。很多判题器是逐 token 比较末尾有没有换行不影响但也有判题器做逐字节 diff你手写的out1.txt少了末尾换行本地自测就永远对不上白折腾半天。我在整理时统一用编辑器打开文件末尾看一眼确保每行以\n结束这件事听起来琐碎实际能省下大量「明明答案对但就是不过」的玄学排查时间。3. 本地跑通编译命令、批量自测与判题环境对齐题目能看懂和代码能在判题机上过是两件事。上机环境一般是 Linux GCC而你在自己电脑上可能用的是某个 IDE 的一键运行两者的默认标准、栈大小、警告级别都不一样。中间这层差异不抹平就会出现「本地全过、提交全挂」的经典翻车。3.1 编译参数一条能提前暴露问题的命令不要用 IDE 的默认运行按钮对付上机题它通常不给你开警告。我固定用下面这条命令把该暴露的问题在编译期就暴露出来gcc -stdc11 -Wall -Wextra -Werror -O2 -g -o main ref.c参数逐个说清楚改的时候心里有数-stdc11锁定 C11 标准。上机环境如果用的是更老的 GCCfor (int i 0; ...)这种写法可能直接报错先按标准写兼容性最好。-Wall -Wextra打开常用和额外的警告。未使用的变量、printf格式符与参数类型不匹配、隐式声明函数都会在这里冒出来。格式符不匹配是上机丢分重灾区%d传了long long在某些平台就是垃圾值。-Werror把警告当错误。这一条在平时刷题时建议开逼自己写干净代码如果题目本身用了gets这类已被移除的函数导致编不过再临时去掉它。-O2开优化。不开优化时某些未定义行为比如越界读、未初始化变量在本地表现正常开了优化立刻现原形早点发现比考场上发现好。-g保留调试信息。配合gdb用段错误时能直接定位到行号。-o main输出可执行文件名。固定叫main后面写自测脚本时不用改路径。编译完之后不要急着手敲输入用输入重定向把样例文件喂进去再把输出重定向到文件和标准答案比。这一步是后面自动化自测的基础。3.2 批量自测脚本一次跑完全部样例一道题一组样例手敲还能忍几十道题几组样例手敲必然出错而且你没法确认自己上一次是不是少看了一行输出。我一般放一个run.sh在题库根目录遍历每个题目文件夹编译、逐个样例执行、比对输出。#!/usr/bin/env bash # run.sh编译并跑完当前题目目录下的所有样例 set -u cd $(dirname $0) || exit 1 srcref.c exe./main pass0 fail0 # 编译失败直接退出先把语法问题解决掉 if ! gcc -stdc11 -Wall -Wextra -O2 -g -o $exe $src; then echo [编译失败] $src exit 1 fi for in_file in in*.txt; do [ -e $in_file ] || continue out_file${in_file/in/out} # in2.txt - out2.txt [ -e $out_file ] || { echo [跳过] 缺少 $out_file; continue; } # 超时保护死循环的题不能把终端卡死 timeout 3 $exe $in_file .actual.txt 2 .stderr.txt rc$? if [ $rc -eq 124 ]; then echo [超时] $in_file; fail$((fail1)); continue fi if [ $rc -ne 0 ]; then echo [运行时错误 rc$rc] $in_file; head -n 5 .stderr.txt; fail$((fail1)); continue fi # -w 忽略行尾空白差异-B 忽略空行差异更贴近实际判题器行为 if diff -w -B $out_file .actual.txt /dev/null; then echo [通过] $in_file; pass$((pass1)) else echo [失败] $in_file diff -w -B $out_file .actual.txt | head -n 10 # 只打前 10 行差异避免刷屏 fail$((fail1)) fi done echo ---- 通过 $pass失败 $fail ---- rm -f .actual.txt .stderr.txt # 有任何失败就以非零状态退出方便接 CI [ $fail -eq 0 ]几处设计说明timeout 3是必要的。上机题里while (scanf(...) ! EOF)写错、while条件里变量不自增导致死循环的情况太常见没有超时保护脚本跑到一半就挂住。退出码 124 是timeout命令约定的超时标志单独判断一下。diff -w -B里的-w忽略所有空白差异-B忽略空行。判题器一般不做逐字节比对特殊题除外用这两个参数能过滤掉「答案对但行尾多一个空格」的假失败。输出目录只保留前 10 行差异。很多题的错误输出能有几千行全打出来反而看不见关键的那一行。最后用[ $fail -eq 0 ]作为脚本退出码这个设计是为了后面接 CI 或批量跑整个题库时能一眼看出哪道题挂了。把这份脚本放到题库根目录加个外层循环遍历所有*/ref.c就是整套题库的一键体检。实际用下来靠它能抓出至少三分之一的隐性错误。3.3 多文件与 Makefile题目变大之后大部分上机题是单文件一个main.c解决战斗。但函数与结构体那几章开始出现「实现一个链表库」的题题面会要求你拆成list.h和list.c这时候单条gcc命令就不够用了。常见做法是写一个极简 Makefile把编译规则固化下来CC gcc CFLAGS -stdc11 -Wall -Wextra -O2 -g TARGET main OBJS main.o list.o $(TARGET): $(OBJS) $(CC) $(CFLAGS) -o $ $^ %.o: %.c $(CC) $(CFLAGS) -c $ -o $ run: $(TARGET) ./$(TARGET) in1.txt clean: rm -f $(OBJS) $(TARGET)$是目标名$^是所有依赖$是第一个依赖这三个自动变量记住就够用了。run目标把「编译 喂样例」合成一步改完代码make run就能看到结果比反复切终端敲两条命令顺手。判题环境不支持 Makefile 的场合手动展开成一条gcc -o main main.c list.c就行不会有额外依赖问题。3.4 和判题环境对齐那几个容易忽略的差异判题机的环境和本地至少有三处不一样提前对齐能省掉大量返工。第一处是栈大小。判题机常用 8MB 默认栈本地可能更大。题目里写int a[1000000];放在main里本地跑得飞起提交直接段错误。解决办法是把大数组移到全局或者加static全局区的空间比栈宽裕得多。用ulimit -s可以先看自己机器的栈限制心里有个数。第二处是警告被当成错误。有些判题平台编译时带-Werrorscanf返回值没用到、有未使用变量都会直接判编译错误。本地编译时就带上这组参数能提前发现。第三处是时间和内存限制。上机题一般给 1 秒用for循环做 O(n²) 的题n 到 10 万就必然超时这不是代码写错了是算法选错了。看到题面数据范围先估一下复杂度这一条比任何调试技巧都管用。4. 典型题目拆解四类高频考点的参考实现题库里题目再多落到考点上就那么几类。挑四类出现频率最高、也最容易写错的题把参考实现逐段拆一遍。这些代码可以直接当模板记考场上改输入输出就能用。4.1 输入输出与格式控制多组数据的正确姿势这类题看着送分实际是丢分最多的一类。典型题面是「输入包含多组数据每组一行两个整数输出它们的和」很多人的写法是这样/* 错误示范不判断返回值读到 EOF 后 a、b 保持旧值循环停不下来 */ while (scanf(%d %d, a, b) ! EOF) { printf(%d\n, a b); }这段代码在多数平台上能跑但一旦输入里有非数字字符scanf一个都没读进去就直接返回变量保持上一次的值循环原地空转直接超时。稳妥的写法是判断实际读入的个数#include stdio.h int main(void) { int a, b; /* scanf 返回成功赋值的变量个数期望两个都读到所以和 2 比较 */ while (scanf(%d %d, a, b) 2) { printf(%d\n, a b); /* 每组输出单独一行别在末尾加空格 */ } return 0; }scanf的返回值是成功匹配并赋值的项数这一点是理解所有输入类题目的钥匙。多组数据用 2第一行给组数T的题就用for (int i 0; i T; i)配合scanf即可。输出侧要注意三点每行以\n结尾、不要输出多余提示语「请输入」这三个字能让判题器判错、浮点数按题意格式输出题面说保留两位就写%.2f别自作主张多打几位。4.2 字符串处理读整行、去换行、判回文字符数组和字符串那一章的题难点不在算法在「怎么把一行带空格的文本完整读进来」。scanf(%s)遇到空格就断gets在新标准里已经被移除正确的选择是fgets。下面这段是「判断一行字符串是否为回文忽略大小写和非字母数字字符」的参考实现#include stdio.h #include string.h #include ctype.h #define MAXL 1024 /* 双指针从两端往中间收跳过非字母数字忽略大小写 */ int is_palindrome(const char *s) { int i 0, j (int)strlen(s) - 1; while (i j) { while (i j !isalnum((unsigned char)s[i])) i; while (i j !isalnum((unsigned char)s[j])) j--; if (tolower((unsigned char)s[i]) ! tolower((unsigned char)s[j])) return 0; i; j--; } return 1; } int main(void) { char line[MAXL]; if (fgets(line, sizeof line, stdin) NULL) return 0; /* 读不到就直接结束 */ size_t n strlen(line); if (n 0 line[n - 1] \n) line[n - 1] \0; /* 关键一步去掉 fgets 留下的换行 */ puts(is_palindrome(line) ? YES : NO); return 0; }几个必须说清的点fgets会把行尾的\n一起读进缓冲区不去掉的话长度判断、回文比较、字符串拼接全都会错一位。这是字符串题第一号坑。isalnum和tolower的形参要求是unsigned char能表示的值或EOF。直接传char时若字符是负数中文、扩展 ASCII行为未定义。加一层(unsigned char)强制转换是标准做法不是多此一举。sizeof line这里line是数组不是指针能得到完整大小换到函数参数里就不行了会退化成指针大小。这个差别在写库函数时一定要清楚。双指针的边界条件统一写i j不要一处ij一处ij否则奇数长度的串会多比较一次甚至越界。4.3 指针与结构体单链表的建立、遍历与释放指针和链表是上机题里最硬的一块也是最能拉开差距的。题目通常要求「输入若干整数以 -1 结束用链表存储并逆序输出」本质考的是头插法和内存释放。参考实现如下#include stdio.h #include stdlib.h typedef struct Node { int val; struct Node *next; } Node; /* 头插法新结点插在表头读入顺序和链表顺序相反 */ Node *push_front(Node *head, int v) { Node *p (Node *)malloc(sizeof(Node)); if (p NULL) { /* 动态内存一定要判空判题机内存小时会真失败 */ perror(malloc); exit(1); } p-val v; p-next head; return p; /* 返回新的表头 */ } /* 释放整条链表 */ void destroy(Node *head) { while (head ! NULL) { Node *next head-next; /* 先存下一个再 free 当前顺序反了就是使用已释放内存 */ free(head); head next; } } int main(void) { Node *head NULL; int x; while (scanf(%d, x) 1 x ! -1) { head push_front(head, x); } for (Node *p head; p ! NULL; p p-next) { printf(%d , p-val); } putchar(\n); destroy(head); head NULL; /* 释放后置空避免后续误用野指针 */ return 0; }逻辑上分三段建表、遍历、销毁。头插法让「逆序输出」这件事自然发生不需要额外反转链表这是这道题的巧劲所在如果题目要求保持原序就改成尾插多维护一个tail指针插入时tail-next p; tail p;。容易错的地方集中在释放阶段。destroy里那行Node *next head-next;必须写在free(head)之前顺序反了就是先释放再读成员典型的释放后使用本地可能不崩开了-O2或换个分配器立刻出问题。另一个常见错误是free(head)之后还去head-next所以循环里要重新赋值head next别想着省事。4.4 文件读写与递归两块小题里的硬骨头文件操作那章的题目往往长这样「文件data.txt中存放若干整数统计个数并求和结果输出到屏幕」。核心是打开、判空、循环读、关闭四步#include stdio.h int main(void) { FILE *fp fopen(data.txt, r); if (fp NULL) { /* 打开失败必须判否则后面 fscanf 全是空转 */ perror(data.txt); /* perror 会把系统错误原因一起打出来 */ return 1; } int x, sum 0, cnt 0; while (fscanf(fp, %d, x) 1) { /* 返回值同样是成功读入的项数 */ sum x; cnt; } fclose(fp); /* 打开就要配对关闭别指望程序退出时自动刷 */ printf(count%d sum%d\n, cnt, sum); return 0; }fscanf的返回值和scanf语义完全一样读到文件末尾返回EOF读到不匹配的字符返回已成功读入的项数。很多人习惯写while (!feof(fp))这是个经典误用feof要等读操作失败之后才置位循环体会多跑一次最后那个数会被重复统计。判断返回值是唯一稳妥的写法。递归那章的题以汉诺塔、斐波那契、全排列为代表写的时候只盯两件事递归出口和递归式。比如汉诺塔出口是n 1时直接搬递归式是「先把上面 n-1 个从 A 借助 C 移到 B把最大的从 A 移到 C再把 n-1 个从 B 借助 A 移到 C」。把这三句话翻成代码就是对的不需要在脑子里模拟每一步。递归深度超过几万层会爆栈题目数据范围大的时候老老实实改写成迭代或者加记忆化数组别硬扛。5. 避坑排查上机题最容易翻车的五类现场题目做不出来是能力问题做出来了提交不过才是真的折磨。下面这五类问题在题库里出现频率极高每一条都按「现象 → 原因 → 解决」写清楚遇到了直接对照排查。5.1 输入读数类多组数据与换行残留现象一第一组样例能过第二组开始输出全错或者直接不输出。原因多半是混用了scanf和fgets/gets。scanf(%d, n)只把数字读走行尾的\n还留在输入缓冲区里紧接着的fgets读到这个换行就认为「读到空行」直接返回。解决办法是统一输入方式要么全用scanf要么全用fgets读整行再用sscanf解析别混着来。真要在scanf后接字符读取中间加一句getchar();把这个换行吃掉。现象二程序在本地跑得好好的提交之后超时。原因是没有判断scanf返回值遇到输入结束或脏数据时循环不退出原地空转。解决办法是把循环条件写成while (scanf(%d, x) 1)期望读几个就和几比较。这条在题库的多组数据题里能排进错误率前三。现象三编译时报gets未声明或者直接链接失败。原因是gets从 C11 起已从标准中移除它不检查缓冲区长度本身就是个安全隐患新版本编译器默认不提供。解决办法换成fgets(buf, sizeof buf, stdin)然后手动去掉行尾换行。不要为了让它编过去去网上抄一段「自定义 gets」的补丁换个环境照样挂。5.2 数组、指针与内存越界和悬空指针现象本地运行正常判题机返回段错误或运行时错误。最常见的原因是局部大数组撑爆栈。int a[1000000]占 4MB几个这样的数组叠在一个函数里就超过默认栈限制。解决方式是把数组声明到函数外变成全局变量或者加static关键字再不行用malloc动态分配并在末尾free。这道题型的正确姿势是「数据量大的数组不放栈上」。现象链表题本地输出正常提交后随机崩溃或答案错乱。常见原因是释放顺序写反或者free之后继续访问成员。检查destroy函数里是不是先取了next再free当前结点。另一个隐蔽原因是结构体指针没初始化就使用Node *p; p-val 1;这种直接对野指针赋值有时能跑通有时就崩属于纯粹的运气问题。现象字符串题输出末尾多出几个乱码字符。原因是字符数组没有以\0结尾。char s[10];手工逐字符赋值后忘了补结束符printf(%s)就会一直往后读读到内存里碰巧出现的 0 才停。解决办法是初始化char s[10] {0};或者赋值完成后显式写s[n] \0;。5.3 编译与判题环境差异格式、精度与标准现象自己的答案和标准答案肉眼完全一致判题就是不通过。先查两处。一是行尾多余空格printf(%d , x)循环里最后一个数后面多了个空格逐字节比对的判题器会判错改成用标志位控制空格或者最后一个数单独处理。二是文件末尾没有换行某些判题器要求最后一行也以\n结束。现象浮点数题答案差 0.01被判错。原因通常是用了float而不是double累加多次之后误差被放大。解决办法是浮点运算一律用doublescanf用%lf、printf用%f这个格式符的差异很多人记混。如果题目要求保留两位小数输出前加一个极小量比如1e-9再格式化能规避2.675这类数在二进制下表示不精确导致少进一位的问题。现象本地能编译判题机报「隐式声明函数」。原因是调用了没#include的头文件里的函数老编译器会给出警告并猜一个返回类型int新编译器直接报错。解决办法是按函数查头文件strlen/strcpy在string.hmalloc/free在stdlib.hisalnum/toupper在ctype.h一个都别省。6. 二次开发把题库接上自动判题和刷题记录题目刷完一轮这份资源真正的价值才开始显现——它是一个结构规整、样例齐全的数据集稍微改造就能变成一个私人判题系统。我做过的最小版本只用了不到五十行 Python遍历题目目录、编译ref.c、把in*.txt喂进标准输入、把输出和out*.txt做 token 级比较。token 比较的好处是自动忽略行尾空格和空行差异正好绕开上一章那类格式坑。import subprocess, pathlib def judge(exe: str, in_file: str, out_file: str, timeout: float 2.0) - bool: 返回 True 表示通过超时、崩溃、输出不一致都算失败 data pathlib.Path(in_file).read_bytes() want pathlib.Path(out_file).read_text(encodingutf-8).split() # 按空白切分天然忽略行尾空格 try: r subprocess.run([exe], inputdata, capture_outputTrue, timeouttimeout) except subprocess.TimeoutExpired: return False return r.stdout.decode(utf-8, ignore).split() want改造方向按投入产出排我建议从低成本的开始改造方向改动点工作量刷题进度表读index.csv通过的题写回done列低自动判题上面的judge()套一层目录遍历中时间/内存限制resource.setrlimit限制子进程资源中题目发布页index.csv转静态 HTML题面 Markdown 渲染中多语言支持按扩展名选编译命令gcc/g/python各一套高这里有个我踩过的坑值得说判题子进程不要用shellTrue题目路径里带空格或特殊字符时会被拆错也不要把用户代码和判题脚本放在同一个目录输出文件名冲突能让你排查一下午。第一次跑通之后建议拿题库里已经确认正确的参考实现反过来跑一遍全部通过才说明判题器本身没问题这个「用标准答案验证判题器」的步骤从那以后我每次搭判题环境都强制走一遍不省略。题库这种东西价值不在你收藏了多少道题而在你有没有把它变成一个能持续反馈的循环。整理好目录、固定编译命令、写个批量比对脚本这一套下来刷题的效率和你对上机环境的熟悉程度是同步涨的。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑