简介:《(完整版)西工大C语言上机考试题库.doc》是一份面向西北工业大学C语言上机考试的完整题库,内容紧扣机考常见题型,适合正在备考西工大C语言上机测试的本科生使用,也适合需要强化C语言实践能力的学习者。资源打包为单个doc文档,共1个文件,大小约5.68MB,打开即可浏览全部题目与参考代码,方便打印和考前冲刺。目前已有198人学习下载。文档内收录了多道典型编程例题,覆盖数字组合与倍数判断、整数各位数字求和与8相关判定、大整数A*B乘法运算、正N边形面积计算、五人参赛逻辑推理、草坪喷水问题以及插入排序等,每道题均配有可直接运行的C语言源程序,便于对照理解循环嵌套、数组操作、字符串处理、函数模块化、取模与分支判断等核心知识点。这些题目与西工大上机考试风格契合,可作为考前集中刷题、查漏补缺的实用参考,帮助读者快速熟悉常见考点并提升上机调试效率。
1. 上机题库到底在考什么:从一道三位数题说起
提到 C 语言上机考试,很多人第一反应是“刷题”,但刷什么题、按什么顺序刷,才真正决定成绩。我拆了一份网上流传的某高校 C 语言上机考试题库,一个 doc 文档,里面大概二十道题,覆盖数字拆解、大数运算、排序、递推、逻辑推断、几何公式六个方向。这份资源不是教程,是考题合集,适合期末上机前一周集中刷手感,也适合从业者回头补一补被 IDE 惯坏的手写能力。有些题看似简单,真在无补全环境里从空文件开始敲,才会发现自己的问题。我把每道题背后的算法母题、可复现代码和踩过的坑逐层拆开,你照着复现一遍,比盲目刷十套模拟卷有用。
2. 把题库拆成四类算法母题:先会辨认再谈默写
上机考试和笔试最大的区别是:笔试考“看懂”,上机考“写对”。同一道题,你知道思路但代码编译不过,和完全不会一样是零分。所以我建议拿到题库后第一件事不是逐题做,而是归纳母题。这份题库里的题看着杂,归纳完只有四类,每一类都有固定的套路和边界参数。
2.1 数字拆解与条件验证:枚举加剪枝是最高频的
题库里最先出现的“三位数乘 2 乘 3”题目,是这类题的标准样本。一个三位数 a,要求 a 的各位数字互不相同,且每位都不为 0,同时 a*2 和 a*3 的结果仍然是三位数,并且三个数共 9 个数位刚好覆盖 1~9 各一次。看起来条件很绕,翻译成代码就是三层 for 循环枚举百位、十位、个位,每层加一个剪枝条件。
剪枝在这里不是优化,是正确性保证。比如 i 的循环范围写成i <= 3而不是i <= 9,就是因为 a 最大只能是 333,否则 a*2 就变成四位数了。这类题反复出现,是因为它同时考察三件事:循环边界的推导、数位拆分的写法、多条件组合的布尔表达式。边界推导是最容易丢分的地方,很多人把 i 循环到 9,运行结果看着没区别,但输出里混进了 400 多、500 多的数,一检查才发现 b、c 已经是四位数,输出错得离谱。
同类题还有“车牌号”——求一个形如 aabb 的四位数,它是某个整数 n 的平方。这里 n 的范围同样要推导:四位数的平方根在 33 到 99 之间,代码里写成n <= 100多算一次不影响结果,但如果你不知道这个范围从哪来的,遇到类似题就容易把范围写大或写小。我的习惯是先把上下界在草稿纸上用计算器按一遍,再写进循环条件。
2.2 大数运算:int 装不下时用字符串模拟竖式
题库里专门有一组大数题:大数乘法、大数加法、大数减法、大数除法。这类题的出现频率非常高,也是拉开差距的地方。原因很直接:int 在 32 位系统上最大只能表示约 21 亿,long long 也只有 19 位十进制,而题目给的大数经常是几十上百位,直接存进数值类型必然溢出。
解决思路是换成字符串存储,用竖式模拟手工计算。以大数乘法为例,乘数 a 有 ca 位,乘数 b 有 cb 位,乘积最多 ca+cb 位。分配一个长度 ca+cb 的 int 数组,两层循环把每一位的乘积累加到固定下标,最后统一处理进位。这个竖式法在大数加法里也一样,只不过加法不需要错位相乘,只要对齐末位逐位相加再处理进位即可。
这类题真正的难点是符号处理。题库代码里用a[0] == '-'判断负数,然后在调用计算函数时传&a[1]跳过符号位;两个数同号就传去掉符号的字符串,异号就先输出一个负号再传。这个思路本身不难,但很多人写着写着就把符号和数字混在一起处理,导致 strcmp 比较长度时把负号也算了进去,结果完全错乱。我的建议是:符号判断统一放在 main 函数里,计算函数只收无符号字符串。
2.3 排序与递推模板:默写比临场推导快得多
插入排序在这份题库里属于“白给分”的题,但它在考场上的意义不是考你懂不懂排序,而是考你能不能在不查资料的情况下默写出来。上机考试的时间压力下,快速排序的 partition 边界容易写错,冒泡排序又太慢,插入排序在数据量小于 100 时是性价比最高的选择。
代码里有个细节值得单独圈出来:while 循环里k--之后立刻判断k == -1再 break。如果不做这个判断,下一轮 while 条件里就会访问a[-1],这是未定义行为,在部分编译器上可能“碰巧”不崩,但换一个环境就翻车。
同余枚举类的“韩信点兵”也是模板题。暴力从 0 开始递增,逐个检查i % 5 == 2、i % 6 == 3、i % 7 == 4、i % 11 == 5四个条件,全满足就输出并 break。这类题考察的是“能不能想到枚举”,以及循环条件怎么写才不会死循环。复杂度完全不需要优化,因为符合条件的最小整数远小于 int 上限,暴力枚举在毫秒级出结果。真正要背的是“求余等式”的写法:%两边都是整数,等号用来判断余数,和数学里 mod 的表示习惯不一样。
2.4 几何与逻辑推断:公式先行,枚举兜底
题库里的 N 边形面积题直接给公式:正 N 边形面积等于n * a * a / (4 * tan(pi / n)),其中 a 是边长。这种题考察点非常明确:第一,会不会用#define PI 3.1415926定义常量;第二,会不会调用tan()并包含 math.h;第三,输出格式能不能写成%.2lf。公式本身不用推导,但要注意 n 小于 3 时 tan 的参数无意义,程序可能算出 NaN 或直接异常。考试数据一般会给合法值,但自己练习时最好加一个if (n < 3) return 0;。
逻辑推断题“五人参加竞赛”是另一类典型。五个变量,每人只有 0 和 1 两种状态,用五层 for 循环枚举 2 的 5 次方共 32 种组合,然后把一个一个条件拆成布尔表达式写进 if。这种题看起来笨,但胜在不容易错,而且逻辑条件再多也不会超过循环复杂度。用位运算可以写得更短,比如用一个 int 的低 5 位表示五个人去没去,但上机环境下位运算一旦写错很难排查,我一般建议基础阶段的同学就用多层 for 循环,稳。
3. 三道重点题逐行拆解:从循环边界到进位偏移
这一章我直接从题库里挑了三个有代表性的题目,给出整理后能直接编译的代码,再逐段讲清边界参数和设计思路。你会发现这些代码的写法并不精致,但非常适合考试——变量名短、逻辑直接、不依赖任何第三方库。
3.1 三位数 abc 验证:i 为什么只循环到 3
#include <stdio.h> int main() { int i, j, k; int a; /* 原始三位数 */ int b, c; /* a*2 和 a*3 */ int l, m, n; /* b 的个位、十位、百位 */ int d, p, q; /* c 的个位、十位、百位 */ for (i = 1; i <= 3; i++) { for (j = 1; j <= 9; j++) { if (i != j) { for (k = 1; k <= 9; k++) { if (k != j && i != k) { a = i * 100 + j * 10 + k; b = a * 2; c = a * 3; l = b % 10; m = (b % 100) / 10; n = b / 100; d = c % 10; p = (c % 100) / 10; q = c / 100; /* b、c 的六个数位都不能为 0 */ if (l != 0 && m != 0 && n != 0 && d != 0 && p != 0 && q != 0) /* i、j、k 不出现在 b 的数位中 */ if (i != l && i != m && i != n && j != l && j != m && j != n && k != l && k != m && k != n) /* i、j、k 不出现在 c 的数位中 */ if (i != d && i != p && i != q && j != d && j != p && j != q && k != d && k != p && k != q) /* b、c 的数位互不重复 */ if (l != d && l != p && l != q && m != d && m != p && m != q && n != d && n != p && n != q) printf("%d %d %d\n", a, b, c); } } } } } return 0; }循环部分的逻辑说明:外层 i 枚举百位,但上限定成 3 是关键推导——a*2 和 a*3 都必须是三位数,所以 a 最大 333,百位只可能是 1、2、3。j 和 k 分别枚举十位和个位,范围 1 到 9 是因为题目要求数位不含 0。i != j、k != j、i != k三个剪枝保证 a 自身的三个数位互不相同。
数位拆分的参数说明:b % 10取个位,(b % 100) / 10取十位,b / 100取百位。l、m、n 对应 b 的数位,d、p、q 对应 c 的数位。后面的四层 if 是在逐层检查“b 不含 0 且 c 不含 0”“a 的数位不在 b 中”“a 的数位不在 c 中”“b 和 c 的数位互不重复”。你也可以用数组标记 1~9 每个数字出现次数来替代,但考试手写场景下,这种展开写法不容易因为数组初始化出错。
3.2 大数乘法:s[i+j+1] 的下标偏移与符号处理
#include <stdio.h> #include <string.h> #include <stdlib.h> void cheng(char a[], char b[]) { int i, j, ca, cb, *s; ca = strlen(a); cb = strlen(b); s = (int *)malloc(sizeof(int) * (ca + cb)); for (i = 0; i < ca + cb; i++) s[i] = 0; /* 竖式乘法:结果累加到 s[i+j+1] */ for (i = 0; i < ca; i++) for (j = 0; j < cb; j++) s[i + j + 1] += (a[i] - '0') * (b[j] - '0'); /* 从低位到高位统一进位 */ for (i = ca + cb - 1; i >= 0; i--) if (s[i] >= 10) { s[i - 1] += s[i] / 10; s[i] %= 10; } /* 跳过前导 0 */ i = 0; while (s[i] == 0) i++; for (; i < ca + cb; i++) printf("%d", s[i]); printf("\n"); free(s); } int main() { char a[100], b[100]; gets(a); gets(b); if (a[0] == '-' && b[0] != '-') { printf("-"); cheng(&a[1], b); } else if (a[0] == '-' && b[0] == '-') { cheng(&a[1], &b[1]); } else if (a[0] != '-' && b[0] == '-') { printf("-"); cheng(a, &b[1]); } else { cheng(a, b); } return 0; }下标偏移设计的逻辑说明:为什么累加位置是s[i + j + 1]而不是s[i + j]?因为两个 n 位数相乘,最高位可能产生一个额外的进位位,整体长度上限是 ca+cb。把最低位累加到下标 1 而不是 0,给最高位进位预留了一个位置,这样从右往左处理进位时,s[i-1]始终是合法下标,不会出现s[-1]越界。这是一个很典型的“用空间换安全”的写法,自己实现时如果把偏移改成s[i + j],最后处理 s[0] 的进位就会越界,程序在运行到接近尾声时崩溃,非常难排查。
符号处理的参数说明:main 里先判断a[0]和b[0]是不是负号。同号相除结果是正,异号先输出负号再计算。传入&a[1]表示从字符串第二个字符开始取数字部分,这一步很多人会写错成a++或者把负号留在字符串里,导致a[i] - '0'算出一个负数。我一般会在进入函数前用printf("---%s\n", &a[1]);先验证一下传入的字符串确实没有符号位。
3.3 插入排序与分数分解:两个容易被忽略的边界
#include <stdio.h> void sort(int a[], int n) { int i, k, t; for (i = 1; i < n; i++) { t = a[i]; /* 待插入元素 */ k = i - 1; while (t < a[k]) { a[k + 1] = a[k]; k--; if (k == -1) break; /* 扫到数组头部,必须停 */ } a[k + 1] = t; /* k+1 是 t 的正确位置 */ } } int main() { int n, a[100], i; scanf("%d", &n); for (i = 0; i < n; i++) scanf("%d", &a[i]); sort(a, n); for (i = 0; i < n; i++) if (i == n - 1) printf("%d\n", a[i]); else printf("%d ", a[i]); return 0; }插入排序边界的逻辑说明:while 的条件是t < a[k],如果待插入元素比已排序区所有元素都小,k 会一路减到 -1。此时如果不 break,下一轮循环会访问a[-1]。这个 bug 在本地可能不崩溃,但在某些 Linux 实验环境下会随机报段错误,属于那种“换一台机器就翻车”的玄学问题。正确做法就是在k--之后立刻判断边界,再决定是否继续循环。
分数分解的边界问题更隐蔽,先看整理后的代码:
#include <stdio.h> int main() { int k, x, y; scanf("%d", &k); for (y = k + 1; y <= 2 * k; y++) { for (x = y; x <= 2 * k; x++) { if (y * k == x * (y - k)) printf("1/%d=1/%d+1/%d\n", k, x, y); } } return 0; }推导说明:原题要求找出所有满足1/k = 1/x + 1/y的整数解。两边同乘 kxy 得到xy = k(x+y),移项得y(x-k) = kx。因为1/y < 1/k,所以 y 必然大于 k,直接从y = k + 1开始枚举;又因为 x 大于等于 y,且y <= 2k是成立的必要条件——当 x、y 都超过 2k 时,1/x + 1/y会小于1/k。用这个整数等式替代原代码里的浮点数比较a == b,既避免了精度问题,又把枚举范围从 5000 缩小到 k 附近,双重收益。
4. 避坑指南:抄进编译器之前先过这四关
题库里的代码是从 doc 文档里提取的,格式多多少少有些走样。这一章列出的五个坑,都是我实际复现时踩过的,每一条都有明确的“现象 → 原因 → 解决”路径。新手踩到其中任何一条,都可能浪费半小时以上的排错时间。
4.1 全角符号与中文标点:编译错误的隐形来源
现象:把代码从文档复制到 Dev-C++ 或 VS 里,编译第一行就报错stray '\357' in program,仔细看字符,发现括号、分号、引号长得和平时不太一样。
原因:doc 文档在排版时会把半角符号替换成全角符号,比如(变成(,;变成;,字符串引号变成中文引号。编译器不认全角字符,直接当作非法符号。
解决:不要直接从原文档复制,而是自己手动敲一遍代码。如果非要复制,先用编辑器的查找替换把全角符号替换成半角:全角左括号(替换为(,全角右括号)替换为),全角分号;替换为;。替换完再编译,报错行会大幅减少。我习惯先在记事本里把代码过一遍字符,再粘贴到 IDE。
4.2 gets 是定时炸弹:输入超长直接改写内存
现象:代码用了gets(a)读入字符串,编译时报警告warning: the 'gets' function is dangerous,输入一个超过数组长度的字符串后程序崩溃。
原因:gets 不检查缓冲区边界,输入数据超过 char a[100] 的容量时,会继续往内存里写,破坏栈上的其他数据。这个函数在 C99 标准里已被废弃,在 C11 标准里直接移除,但很多老教材和题库还在用。
解决:换成fgets(a, sizeof(a), stdin)。需要注意 fgets 会把末尾换行符也读进来,如果后续要对字符串做 strlen 或逐字符处理,记得把最后的\n删掉,常见写法是if (a[strlen(a) - 1] == '\n') a[strlen(a) - 1] = '\0';。题库代码里大量使用 gets,自己复现时统一替换成 fgets,能省掉很多输入相关的隐蔽问题。
4.3 浮点比较不可靠:分数分解少输出别意外
现象:用a = 1.0 / k; b = 1.0 / x + 1.0 / y; if (a == b)判断时,k 取小数字如 3、4 输出正常,取 7、11 时偶尔少了几组解。
原因:浮点数在二进制下无法精确表示所有十进制小数,1.0 / 7和1.0 / 3 + 1.0 / 4的计算结果虽然数学上相等,但浮点尾数有微小误差,==比较直接判定不相等。
解决:改用整数交叉相乘。把1/k = 1/x + 1/y变形为y * k == x * (y - k),两边都是整数运算,没有精度损失。这也是我在第 3 章改分数分解代码的核心原因。考试如果遇到这种题,优先用整数等式,不要用 float 或 double 去比较相等。
4.4 循环上限拍脑袋:k>2500 时循环白跑
现象:分数分解题,输入 k = 2600,程序运行了很长时间也没有任何输出,键盘敲什么都没反应。
原因:原代码直接写y <= 5000,但根据不等式推导,x 和 y 的有效范围是[k+1, 2k]。k = 2600 时,2k = 5200,已经超过 5000 的上限,循环永远找不到解。
解决:先把上限推导出来再写进循环。这类题的范围推导公式我已经在第 3 章给出了,总结成一句话:分式分解的枚举上界是 2k,不是随便拍一个 5000。遇到其他题,也要先判断数据范围是否可能超出循环边界,否则程序跑完整个循环也等不到输出,考试时这种“死等”最浪费时间。
4.5 多条件逻辑运算:少一对括号就翻车
现象:逻辑推断题里,五个参加竞赛的条件写进一个 if,编译能过,但运行结果明显不符合预期,有些人明明条件满足却没输出。
原因:&&和||混用时,运算符优先级容易搞混;还有的人把逻辑与&&写成了按位与&,导致多条件判断结果完全错乱。
解决:每个子条件单独用括号包起来,不要依赖默认优先级。比如((a == 1 && b == 1) || (a == 0 && b == 0))这样写,编译器不会误解,人也容易检查。另外强烈建议编译时加-Wall,编译器会提示可疑的优先级问题。考试时如果输出异常,先别怀疑算法,用printf("%d %d\n", a, b);把每个变量的当前值打出来,肉眼检查条件真假,通常一眼就能看出是哪对括号的问题。
5. 考前一天的三件事:把题库变成肌肉记忆
第一件事,把第 2 章的四类母题在草稿纸上各写一遍伪代码,不要求完整语法,但要写出循环边界和关键判断条件。重点是分数分解的枚举上界、三位数题 i 的范围、大数运算的数组长度,这些是考场上最容易临时想不起来的参数。写完之后对照题库,给每道题贴上标签:属于数字拆解、大数运算、排序、递推、几何还是逻辑推断。能贴对标签,做的时候思路就不会跑偏。
第二件事,关闭 IDE 的自动补全,用记事本或 Vim 把第 3 章的三道题从头到尾默写一遍。很多人平时写代码靠 IDE 自动补全头文件和变量名,一旦上机环境是裸编辑器,连#include <stdio.h>都要想半天。默写完了用命令行编译一遍:
gcc -Wall -Wextra exam.c -o exam-Wall和-Wextra会把所有警告列出来,包括变量未使用、可能有符号性问题、比较时类型不匹配等。我的习惯是追求“零警告”,只要有 warning 就当 error 处理,因为考场上 warning 往往意味着逻辑有偏差。
第三件事,用四组输入自测:零值、负值、最大边界值、空输入。比如插入排序题输入 0 个数,大数乘法题输入"0"和"9999",分数分解题输入 k = 1 和 k = 5000。边界输入最容易暴露数组越界和除零问题,也是上机考试隐藏测试点最爱用的数据。我当年在一台没有 IDE 的考试机器上,把插入排序默写完,编译通过后忘了测 n = 0 的情况,结果程序直接不输出,白白扣了一整道题的分。从那以后,我每次上机前都强制自己走一遍“手写框架 → gcc -Wall → 边界自测”的流程,题库里的每道题都按这个标准过了一遍。希望这份拆解能帮你少踩几个坑,考场上多拿几分。
本文还有配套的精品资源,点击获取