简介:这份资源是西北工业大学C语言上机考试题库的完整文档,面向准备课程上机考核、复习C语言基础的理工科学生,尤其适合需要针对真题进行专项训练的学习者。内容围绕上机考试常见题型展开,涵盖数字组合枚举、整数特性判断、字符串乘法、多边形面积计算、竞赛人选推理、草坪喷水、插入排序等实例,并配有可直接运行的参考代码,便于对照理解嵌套循环、条件分支、数组与字符串处理、ASCII转换及动态内存管理等核心知识点。资源包共1个docx文件,压缩后约4.38MB,正文以源码与题目说明交替编排,结构紧凑、便于逐题查阅和打印练习。目前已有190人学习下载。读者可借此梳理从变量声明、for/while循环、if与switch-case判断到malloc内存分配、大数乘法模拟的完整解题路径,在复盘代码细节的过程中训练逻辑思维与编程实践能力,适合作为考前自测与查漏补缺的题库材料。
1. 一份「西工大C语言上机考试题库完整.docx」在手里,先别急着从头刷
拿到这份文档的人通常有两种状态:一种是考前三周,想按题号从头刷到尾;另一种是刷了几十题,发现题型一直在重复,却说不清自己弱在哪。西工大上机考试的题型分布相对稳定,数值计算、数组、字符串、指针、文件读写反复出现,题目本身不难,失分多出在输入输出格式、数组越界和边界条件上,也就是大家嘴上说烂了的 C语言基础知识。
所以这份 docx 的正确用法不是“刷完”,而是“拆开、分类、跑通、自测”:先统计每类题有多少道,按考点密度排序,再配合本地 gcc 把每道题变成可编译可运行的文件,最后用脚本批量比对输出。这套流程对西工大校内上机、计算机二级C语言题库、OpenJudge 作业和华为OD上机考试都适用。下面按题库拆解、环境配置、题型实现、考场收尾四步展开。
2. 从上机题库反推考点:把 docx 拆成按题型检索的练习清单
2.1 先统计题型分布,再决定刷题顺序
题库完整不等于要全刷。我一般先按“输入形态 + 输出形态 + 涉及知识点”三个维度给题目打标签,统计完再决定顺序。下面这张表是这类上机题库里最常见的题型分布,权重按卷面经验估的,实际以本校本年度考纲为准:
| 题型 | 典型题面关键词 | 卷面权重 | 主要考点 |
|---|---|---|---|
| 数值计算 | 累加、阶乘、素数、水仙花数 | 15% | 循环、if语句的用法、类型转换 |
| 一维/二维数组 | 冒泡排序、求最大值位置、矩阵转置 | 25% | 下标边界、双重循环 |
| 字符串处理 | 逆序、统计单词、删除字符 | 25% | 字符数组、strcpy用法、'\0' |
| 指针与函数 | 交换两个数、数组传参、函数指针 | 15% | 地址传递、指针函数与函数指针 |
| 文件读写 | 读文件排序后写回 | 10% | fopen/fscanf/fprintf、EOF |
| 递归与结构体 | 汉诺塔、学生成绩排序 | 10% | 递归出口、结构体数组 |
排序原则很简单:字符串和数组先刷,因为它们占一半分数且容易拿满;指针类题目放在中间,用来暴露理解漏洞;文件读写最后补,格式固定,背下来就能用。顺序错了,最容易出现的情况是前十天都耗在递归上,考场上却因为字符串没处理'\0'丢分。
2.2 用 Python 把 docx 切成按题号命名的 .c 文件
docx 里的题目是连续文本,直接看很容易漏掉输出格式要求。把它切成一个个骨架文件,才能真正“跑起来”。常见做法是用 python-docx 抽段落,再用正则按题号切分:
# split_docx.py —— 把题库 docx 切成按题号命名的 C 骨架文件 from docx import Document import re, pathlib SRC = "西工大C语言上机考试题库完整.docx" OUT = pathlib.Path("problems") OUT.mkdir(exist_ok=True) doc = Document(SRC) # 一个段落可能包含多行,先全部拼起来,再按题号切 text = "\n".join(p.text for p in doc.paragraphs) # 匹配 "1." "第1题" "1、" 这类题号开头,允许 1~3 位数字 pattern = re.compile(r"(?:^|\n)\s*(?:第)?\s*(\d{1,3})\s*[.、.]\s*") hits = list(pattern.finditer(text)) for i, m in enumerate(hits): start = m.end() end = hits[i + 1].start() if i + 1 < len(hits) else len(text) body = text[start:end].strip() no = m.group(1).zfill(3) # 题干保留成注释,下面挂一个空 main,方便逐个填实现 skeleton = ("/*\n" + body[:1200] + "\n*/\n\n#include <stdio.h>\n\nint main(void) {\n \n return 0;\n}\n") (OUT / f"p{no}.c").write_text(skeleton, encoding="utf-8") print("切出题目数:", len(hits))代码逻辑:先用Document读全部段落并拼成一个长字符串,因为题号有时跨段落;pattern用非捕获组匹配“第1题”“1.”“1、”三种写法,finditer拿到每一处题号的位置;下一处题号的起点就是本题终点,这样切片不会互相吞掉;zfill(3)保证文件名排序和题号一致,p007.c排在p010.c前面。
参数上有两点要注意:body[:1200]是防止个别题面超长把注释块撑爆,按需调;编码统一utf-8,Windows 上如果 docx 里混了全角空格,题号匹配会失败,把正则里的\s换成[\s\u3000]更稳。
2.3 给每道题补一个最小可运行骨架
切完的文件里只有题目注释和一个空 main,接下来统一补骨架,把输入输出结构固定下来。很多人卡在“知道思路但写不出能过的程序”,问题基本出在骨架不熟:
#include <stdio.h> #include <string.h> int main(void) { int n; if (scanf("%d", &n) != 1) return 0; /* 判题机可能不给输入,直接退出 */ int a[1005]; for (int i = 0; i < n; ++i) scanf("%d", &a[i]); /* TODO: 按题面处理 a[] */ for (int i = 0; i < n; ++i) printf("%d ", a[i]); return 0; }scanf的返回值必须判断,空输入或格式不符时它返回 EOF,不判断就是死循环或脏数据;数组开到 1005 而不是 1000,是给“不超过 1000 个元素”的题面留冗余,数组越界是上机最常见的运行时错误;printf的尾空格要按题面决定,多数在线判题机忽略行尾空格,但人工看结果的卷面不忽略,写完先对照样例逐字符数一遍。
3. vscode配置c语言环境与题库的批量编译调试
3.1 MinGW-w64 与四个必调编译参数
本地跑题至少要有 gcc。Windows 上装 MinGW-w64(或 WSL 里的 gcc)后,把bin目录加进 PATH,gcc --version能出版本号就算通了。格式化、补全可以交给 VSCode 的 C/C++ 扩展,真正影响判题结果的是编译参数:
| 参数 | 作用 | 上机场景为什么需要 |
|---|---|---|
-std=c11 | 指定语言标准 | 考试环境多用 C11/C99,避免for (int i...)编译不过 |
-Wall | 打开常规警告 | 未初始化变量、scanf类型不匹配会当场暴露 |
-g | 生成调试信息 | 配合 gdb 断点查指针越界 |
-O0 | 关闭优化 | 单步调试时变量不会被优化掉 |
.vscode/tasks.json里把这几项写死在args,比每次手敲命令省事:
{ "version": "2.0.0", "tasks": [ { "label": "gcc build current file", "type": "shell", "command": "gcc", "args": ["-std=c11", "-Wall", "-g", "-O0", "${file}", "-o", "${fileDirname}/${fileBasenameNoExtension}"], "group": {"kind": "build", "isDefault": true}, "problemMatcher": ["$gcc"] } ] }${file}是当前打开的 .c 文件,输出名用fileBasenameNoExtension去掉后缀,这样p007.c编译成p007,和题库文件名一一对应,出问题时能直接定位到题号。problemMatcher选$gcc后,警告会进“问题”面板,点一下跳到对应行,比翻终端输出快很多。
提示:用 Dev-C++ 或 Code::Blocks 时同样要把语言标准设成 C11,并关掉“编译时自动保存”,否则改到一半的文件会被编译成半成品,报错行号对不上代码。
3.2 用输入重定向喂样例,别手敲测试数据
题库样例的输入会越来越长,手敲测试数据是效率黑洞。把样例存成.in文件,用重定向喂进去:
mkdir -p case cat > case/p007.in <<'EOF' 5 3 1 4 1 5 EOF gcc -std=c11 -Wall -g -O0 p007.c -o p007 ./p007 < case/p007.in # 先肉眼看输出对不对 ./p007 < case/p007.in > case/p007.out # 存成期望输出,供后续批量比对<把文件内容接到 stdin,等价于在终端逐行敲;>把 stdout 存成文件,判题脚本直接拿它做比对。这里有个容易忽略的细节:重定向之后程序里的scanf不读键盘,但调试断点照样能停,所以不必走“先用键盘调通再换文件”这种多余流程。
如果题目要求读多组数据直到 EOF,用while (scanf("%d", &n) == 1)结构,测试文件末尾不要留空行,否则某些写法会多输出一行。样例里的行数、列数也要和题面严格一致,少一行数据、多一个换行,结果都可能对不上。
3.3 用脚本批量跑题库并比对输出
单个题跑通之后,把整个题库跑一遍才有意义。下面这个脚本按题号找.c和.in,编译、执行、比对,把结果汇总成表:
# judge.py —— 批量运行题库并比对期望输出 import pathlib, subprocess root = pathlib.Path(".") report = [] for cin in sorted(root.glob("case/*.in")): no = cin.stem # 例如 p007 src = root / f"{no}.c" if not src.exists(): report.append((no, "MISSING_SRC", "")) continue exe = root / no cp = subprocess.run(["gcc", "-std=c11", "-Wall", "-g", str(src), "-o", str(exe)], capture_output=True, text=True) if cp.returncode != 0: # 只留最后一行报错,前面多是连锁信息 report.append((no, "COMPILE_ERROR", cp.stderr.strip().splitlines()[-1][:80])) continue exp = cin.with_suffix(".out") with open(cin, encoding="utf-8") as f: run = subprocess.run([str(exe)], stdin=f, capture_output=True, text=True, timeout=5) # 防止死循环卡住 if not exp.exists(): report.append((no, "RUN_OK_NO_EXPECTED", "")) continue got = [l.rstrip() for l in run.stdout.strip().splitlines()] want = [l.rstrip() for l in exp.read_text(encoding="utf-8").strip().splitlines()] report.append((no, "PASS" if got == want else "WA", f"{len(want)}->{len(got)} 行")) for no, st, note in report: print(f"{no:8s} {st:20s} {note}") print("总计", len(report), "题")关键点在两处:timeout=5防止死循环把你卡住,上机时死循环就是零分,本地必须先暴露;比对前用rstrip()清掉行尾空格和空行,因为不同平台对行尾处理不一致,但如果题目明确要求保留空格,就得改回精确比对。状态码分得粗,但足够定位下一步看什么:
| 状态 | 常见原因 | 先查什么 |
|---|---|---|
| COMPILE_ERROR | 变量未声明、缺头文件、C99 语法 | 只看第一条报错,后面多是连锁 |
| WA | 边界条件、输出格式、数组越界 | 拿样例手算一遍,对比行数和空格 |
| 超时触发 | 循环缺出口、scanf未判返回值 | 看循环条件和 EOF 处理 |
| RUN_OK_NO_EXPECTED | 没准备期望输出 | 用题面样例补.out文件 |
4. C语言高频题型的写法:从冒泡排序c语言到文件读写
4.1 数组与冒泡排序c语言:两个决定对错的细节
排序题几乎每套题库都有。冒泡排序c语言的写法本身没什么花头,但有两个细节决定过不过:内层循环的上界写不写- i,以及相等元素要不要交换。
/* 升序冒泡:n 为元素个数,a 为待排序数组 */ void bubble_sort(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 t = a[j]; a[j] = a[j + 1]; a[j + 1] = t; swapped = 1; } } if (!swapped) break; /* 已经有序,提前结束 */ } }内层上界n - 1 - i表示每轮结束后尾部已有 i 个元素归位,写上只是省比较次数,不写也不会错,但完全有序的输入下会多跑一轮;swapped标记让最好情况降到 O(n),题库里给了“已排序数据”测试点时靠它。比较用>不用>=,相等元素不互换位置,输出与标准答案一致的概率更高;题面要求“从大到小”就把>改成<,别顺手改成倒序输出,那是两回事。
数组还有一个隐蔽的坑:函数参数写int a[]实际退化成int *a,函数内sizeof(a)算出来是指针大小而不是数组长度,所以长度必须单独传,bubble_sort的签名里必须有n。二维数组传参要写全第二维,void f(int a[][5], int n),写成int **a会直接崩。
4.2 c语言指针与字符串:strcpy用法、逆序和函数指针
指针题的失分点集中在“以为在改值,其实在改副本”。下面这张表是从错题里总结出来的对照:
| 写法 | 实际行为 | 正确做法 |
|---|---|---|
void swap(int a, int b) | 交换的是形参副本 | 传地址,内部用*a、*b交换 |
char *s = "abc"; strcpy(s, "x"); | 写入字符串字面量,未定义行为 | 用char s[10] = "abc";或 malloc |
strcpy(dst, src)不检查长度 | dst 溢出 | 先确认strlen(src) + 1 <= sizeof(dst) |
free(p); free(p); | 二次释放 | 释放后置p = NULL; |
strcpy用法的核心就一句:它把源串连同结尾的'\0'一起复制,不做长度检查,目标缓冲区必须自己保证够大。字符串逆序 C 语言 PTA 类题目里,常见做法是双指针原地交换:
#include <string.h> void reverse(char *s) { if (s == NULL) return; size_t i = 0, j = strlen(s); if (j == 0) return; --j; /* 指向最后一个有效字符,不是 '\0' */ while (i < j) { char t = s[i]; s[i++] = s[j]; s[j--] = t; } }j初始取strlen(s)再自减,是因为strlen返回的是不含'\0'的长度,直接用s[j]会读到结束符,逆序结果第一位变成'\0',输出看起来“少了一个字符”。循环条件用i < j,中间字符不必自我交换;人工阅卷时这一行常被当成边界理解是否到位。
函数指针和指针函数只差一个字,判断方法是看括号:int (*f)(int, int)是函数指针,f先和*结合,说明它是指针,指向返回 int 的函数;int *f(int, int)是返回int *的函数。排序题里常用它做比较器:
int cmp_asc(const void *a, const void *b) { int x = *(const int *)a, y = *(const int *)b; return (x > y) - (x < y); /* 返回 -1/0/1,避免大数相减溢出 */ } /* qsort(a, n, sizeof(int), cmp_asc); */qsort的比较函数必须返回 int,形参是const void *,要先转成具体类型再解引用;两个大数直接相减可能溢出,用(x > y) - (x < y)更稳,考试数据一般不大,但知道这个写法能少踩一次坑。
4.3 c语言文件读写操作代码:把题库用例跑成一份报告
文件读写题出现频率不算最高,但格式最固定,背下来就能用。典型题面是“读入若干整数,排序后写入输出文件”:
#include <stdio.h> #define MAXN 1005 int main(void) { FILE *fin = fopen("data.txt", "r"); if (fin == NULL) { /* 一定要判空,路径错了当场暴露 */ perror("fopen data.txt"); return 1; } int a[MAXN], n = 0; while (n < MAXN && fscanf(fin, "%d", &a[n]) == 1) ++n; /* 读到文件尾自然停 */ fclose(fin); for (int i = 0; i < n - 1; ++i) for (int j = i + 1; j < n; ++j) if (a[j] < a[i]) { int t = a[i]; a[i] = a[j]; a[j] = t; } FILE *fout = fopen("out.txt", "w"); if (fout == NULL) { perror("fopen out.txt"); return 1; } for (int i = 0; i < n; ++i) fprintf(fout, "%d\n", a[i]); fclose(fout); return 0; }fscanf的返回值是成功读入的项数,等于 1 说明这次读到一个整数,读到末尾返回 EOF,用它做循环条件比!feof(fin)可靠——feof要到“读失败之后”才置位,先判断再读会多处理一次旧数据,这是文件题最经典的错误。fopen模式要分清:"r"只读、文件不存在返回 NULL;"w"覆盖写、文件不存在会新建;追加用"a"。文本模式下 Windows 会把\n转成\r\n,题面要求严格字节数时改用"rb"、"wb"。
文件名如果通过argv传进来,argv[0]是程序名,数据从argv[1]开始,先判断数量再取,避免空指针解引用:
int main(int argc, char *argv[]) { if (argc < 2) { fprintf(stderr, "usage: %s <input>\n", argv[0]); return 1; } FILE *fp = fopen(argv[1], "r"); if (fp == NULL) { perror("fopen"); return 1; } /* ... */ }5. 上机考场的收尾技巧:把自测压缩成一分钟跑完的流程
前面四步解决的是“写得出”,考场上的问题往往是“写得完、对得上”。我的做法是把自测压成一个动作:每道题在本地准备好.in和期望.out,写完立刻跑judge.py,看到 PASS 就翻页,看到 WA 只看差异行数,不重新通读代码。下面这张表按 120 分钟上机、4 到 6 道题估算:
| 剩余时间 | 该做的事 |
|---|---|
| 90 分钟以上 | 正常读题,先写输入解析和输出格式,再补算法 |
| 40~60 分钟 | 放弃优化写法,用最笨的双重循环,保证先过样例 |
| 20 分钟 | 只补边界:n=0、n=1、全相同元素、超长字符串 |
| 10 分钟 | 检查scanf返回值、数组上界、printf行尾空格 |
有几个动作值得固定成肌肉记忆。每写完一个函数就在 main 里打一行printf输出中间结果,验证完删掉,比开调试器快;内存管理类题目注意 malloc 与 free 配对,申请n + 1个字符时要给结束符留位,少了就等着越界;if条件把常量写左边(if (0 == n))能在漏掉一个等号时直接编译报错,这个习惯在时间紧的时候能救命。
还有一个细节容易被高估:很多人以为本地跑通就等于考场跑通。上机环境经常只有 Dev-C++ 或者干脆是命令行,考前一周把编译器从 IDE 换到gcc -std=c11 -Wall手敲一遍,确认自己没有补全也能写出#include <string.h>和strlen的正确拼写,比多刷十道题管用。
本文还有配套的精品资源,点击获取