资讯动态

C语言实战:火车站售票题的数组建模与规则实现

发布时间:2026/10/24 19:15:17 来源:尧图企业网站定制
简介这是东软集团IA事业部2007年C语言编码考试试题适合正在准备企业编程笔试、想巩固C语言基本功与文件操作能力的开发者。题目要求实现一节80座车厢的火车站售票模拟程序重点考查数据结构设计、相邻与间隔座位分配逻辑、输入输出文件处理及编码规范。资源内包含完整试卷与可直接参考的GetRequest、OutputResult函数框架对理解静态变量保持文件句柄、按规则拆分布局、处理边界情况很有帮助。资料为1个doc文档压缩包约54KB已吸引232人学习。借助这套真题读者既能检验自身C语言水平也可将其作为练习项目动手搭建座位状态表、实现1至4张票的优先购票策略并在调试中提升文件读写与异常处理的实战能力。整体内容精炼特别适合以“火车票购票模拟”为切入点的C语言进阶训练。1. 这道“火车站售票”C语言题考的不是你会不会写循环先说一个高频丢分点题目里 1、2、5、6 属于同一个间隔3、4、7、8 属于旁边那个间隔。很多人看到“座位号从 1 到 80”第一反应就是“连续 4 个座位一组”结果间隔模型从一开始就错了后面售票规则的优先级逻辑全部跑偏。这道某企业事业部早期的 C 语言编码考试题表面是“模拟火车站售票”实际考察三件事一、能不能用合适的数据结构表达 80 个座位的售出状态二、能不能把“相邻”和“间隔”这两个约束翻译成可查找的逻辑三、文件读写和函数接口用得到不到位。分值大致是数据结构与编码规范约 25 分售票规则约 40 分主流程和文件操作约 35 分。适合准备企业编码机试的求职者也适合想练 C 语言状态建模的在读学生。下面直接按考点拆开讲。2. 底层考点座位数组与间隔映射先画清楚再写代码2.1 数据结构80 个座位就是“4 列 × 20 行”间隔用公式算这一题不需要链表也不需要位图一张数组就够。定义int seatState[80]0 表示未售1 表示已售。为什么不用结构体数组因为座位本身没有附加属性要存状态变化也只有“未售 → 已售”一种用 int 数组最简单阅卷人一眼能看懂。真正要动脑的是“间隔”怎么算。车厢布局可以抽象成 4 列 × 20 行。座位编号规律是每个座位号 n从 1 开始先减 1除以 4 得到行号 row对 4 取余得到列号 col。于是row (n-1)/4col (n-1)%4。验证一下座位 1 在第 1 行第 1 列座位 2 在第 1 行第 2 列座位 5 在第 2 行第 1 列座位 3 在第 1 行第 3 列。过道位于第 2 列和第 3 列之间所以 col 小于 2 的是过道左侧两列col 大于等于 2 的是过道右侧两列。间隔定义为“两行 × 两列”的块一个间隔装 4 个座位全车共 20 个间隔。计算间隔编号时先算行块再算左右行块rowBlock row / 2如果 col 小于 2间隔号 rowBlock * 2 1否则间隔号 rowBlock * 2 2。按这个公式座位 1、2、5、6 的间隔号都是 1座位 3、4、7、8 的间隔号都是 2和题干完全对得上。这段映射是整道题的地基考试时可以先把下面的函数写出来int GetInterval(int seatNo) { int row (seatNo - 1) / 4; // 0 开始的行号 int col (seatNo - 1) % 4; // 0 开始的列号 int rowBlock row / 2; // 每两行合成一个行块 if (col 2) { // 过道左侧两列 return rowBlock * 2 1; } else { // 过道右侧两列 return rowBlock * 2 2; } }注意这个函数的输入是座位号 1 到 80输出是间隔号 1 到 20。不要把它和相邻对的编号混在一起相邻对是 (1,2)、(3,4)、(5,6) 这种两两一组间隔是四个座位一组两者维度不同。后续“售 2 张”和“售 3/4 张”分别查这两套结构写清楚就能避免逻辑纠缠。2.2 文件接口GetRequest 读什么、OutputResult 写什么题目把输入输出函数直接给了很多考生把这两个函数当成不可改动的黑匣子实际要用好也有讲究。GetRequest(int *p)的作用是从d:\request.txt里读一次购票请求每次调用读一个 1 到 4 的数字写入*p返回 true 表示有请求返回 false 表示请求读完主循环就此结束。注意它每次只消费一个请求所以主循环的执行次数等于文件里的请求个数而不是只执行一次。有个细节很容易翻车原题给出的GetRequest里fscanf格式是%d,要求数字之间用逗号分隔。如果request.txt里的数据是“1 2 3 4”这种纯空白分隔格式函数会在等待逗号时读失败。题目描述说“空格、制表符、换行符、换页符和回车符都可以分割”又说“供调试使用请使用题目附带的文件 request.txt”稳妥做法是统一把格式改成%d并在提交前用真实输入文件跑一遍。另一个注意点是GetRequest用 static 修饰文件指针和数组第一次调用时打开文件并一次性读完数字之后多次调用从静态数组里取数。如果自己重写这个函数别在每次调用里重复fopen否则请求会漏读。OutputResult(int *SeatNum, int Count)的职责是追加写一行本次售票结果。它的实现每次都把文件按追加方式打开所以多次售票的结果会叠在一起。为了防止上一次调试的旧数据干扰main函数开头最好先用w方式打开一次d:\result.txt立即关闭把文件清空。文件操作另一个常见问题是fopen失败返回 NULL考试环境里路径写错很常见至少判断一下返回值并在窗口打印提示这一两行就能救回规范分。2.3 主循环骨架初始化、读请求、调售票、写结果考试时不要一上来就写规则细节先把主流程写死。主流程就是四步循环置所有座位为未售调用GetRequest读请求根据请求调对应的售票函数成功后调OutputResult输出GetRequest返回 false 就结束循环。这样结构清晰即使某个规则函数没写完主流程也能单独跑起来调试。int main(void) { int request, sellCount; int soldSeats[4]; int i; // 1. 座位全部置为未售 for (i 0; i 80; i) { seatState[i] 0; } // 2. 清空上次遗留的输出文件 FILE *fp fopen(d:\\result.txt, w); if (fp ! NULL) { fclose(fp); } // 3. 循环读请求 while (GetRequest(request)) { if (request 1) { sellCount TrySellOne(soldSeats); } else if (request 2) { sellCount TrySellTwo(soldSeats); } else { sellCount TrySellMore(request, soldSeats); } // 4. 有票才写输出 if (sellCount 0) { OutputResult(soldSeats, sellCount); } else { printf(本次请求不售票余票不足\n); } } return 0; }这个骨架里TrySellOne、TrySellTwo、TrySellMore分别对应三类售票规则每个函数只做一件事找出座位号、更新状态、返回卖出张数。输出统一交给OutputResult这样职责清晰也符合“逻辑清楚、结构良好”的评分要求。编码规范分最多能占到题目分数的三分之一函数划分离不开主流程管调度规则函数管选座辅助函数管映射阅卷人一眼就能找到采分点。3. 高频考点售 1 张、2 张、3~4 张票的三层规则怎么落地3.1 售 1 张票线性扫描第一个空座就可以题干说“随便售出一张没有售出的票”。这里的“随便”并不等价于“必须用 rand 随机”。绝大多数阅卷标准只要求满足“卖出一张未售票”按顺序扫描第一个空座也是合法的。用了随机数反而多出三个麻烦可能重复选到已售座位需要在循环里重试测试结果不可复现还要处理srand种子忘记写种子会导致每次运行结果都不同。正确写法是把选座和更新状态放在同一个函数里完成int TrySellOne(int out[]) { for (int n 1; n 80; n) { if (seatState[n - 1] 0) { seatState[n - 1] 1; out[0] n; return 1; } } return 0; }这个函数返回 1 表示成功返回 0 表示车厢已满。out数组由调用方提供函数内只写数据不负责输出文件这就是“职责单一”。面试追问“如果 80 个座位全部卖完GetRequest 还有请求怎么办”时标准答法是TrySellOne返回 0主流程打印“余票不足不售票”不调用OutputResult这与后面 3 张、4 张规则里的“不售票”提示逻辑一致。3.2 售 2 张票相邻对怎么找才不出错两张票的规则是“优先 2 张相邻没有相邻就随便两张”。题干明确说了相邻包括 1 和 2、3 和 4、5 和 6、7 和 8但不包括 1 和 5、2 和 3。也就是说相邻对就是按座位编号两两成对(1,2)、(3,4)、(5,6)……一直到 (79,80)。用数组下标表示就是seatState[0]和seatState[1]成对seatState[2]和seatState[3]成对。最容易写错的循环是全范围扫描“相邻 i 和 i1”。如果从 1 循环到 79会把 (2,3) 也判成相邻而题干明确说 2 和 3 不算因为 2 在过道左侧、3 在过道右侧两人中间隔着过道不是相邻。正确实现是步长 2 扫描int TrySellTwo(int out[]) { // 优先卖相邻对1-2, 3-4, 5-6, ... for (int i 1; i 80; i 2) { if (seatState[i - 1] 0 seatState[i] 0) { seatState[i - 1] 1; seatState[i] 1; out[0] i; out[1] i 1; return 2; } } // 回退任意两张未售出的票 int count 0; for (int n 1; n 80 count 2; n) { if (seatState[n - 1] 0) { seatState[n - 1] 1; out[count] n; } } return count; }这里有一个容易被忽略的细节回退分支同样会把状态置为已售所以无论走哪个分支函数返回时座位状态一定是更新的。如果只写“找到相邻对”的逻辑、回退分支忘记置状态同一批座位会被重复卖出。调试时输出结果每行多出的逗号分隔座位号会暴露这个问题但最好写的时候就避免。3.3 售 3 张和 4 张票间隔优先与余票不足的判定顺序3 张 4 张是整道题规则最厚的部分题干三句话对应三个分支优先售一个间隔内的票如果剩余票数不足就提示不足不售票如果不能满足优先原则则售任何位置的票。翻译成代码判定顺序是先统计全车剩余票数不足直接返回不售票再遍历 20 个间隔找有没有“该间隔剩余座位数 请求数”的有就选这个间隔里的座位没有就随便选全车空座。为什么“剩余票数”要先判因为题干明确要求“如果剩余票数不足就提示不足不售票”这个分支优先级最高。比如只剩 3 个空座请求是 4此时无论如何都不能出票。但如果剩余票数是 4且没有任何一个间隔能一次性提供 4 个座位就可以从不同间隔凑出 4 张这对应“不能满足优先原则则售任何位置”。int TrySellMore(int need, int out[]) { int totalFree 0; int n, k; // 1. 统计全车剩余票数 for (n 1; n 80; n) { if (seatState[n - 1] 0) { totalFree; } } if (totalFree need) { return 0; // 提示不足不售票 } // 2. 遍历 20 个间隔找空座数足够的一个 for (int interval 1; interval 20; interval) { int freeInInterval[4]; int cnt GetFreeInInterval(interval, freeInInterval); if (cnt need) { for (k 0; k need; k) { seatState[freeInInterval[k] - 1] 1; out[k] freeInInterval[k]; } return need; } } // 3. 没有满足条件的间隔任意位置出票 int count 0; for (n 1; n 80 count need; n) { if (seatState[n - 1] 0) { seatState[n - 1] 1; out[count] n; } } return count; }这里用GetFreeInInterval把某个间隔的所有未售座位号放进数组并返回数量它和GetInterval配合避免重复写行列换算。这个辅助函数是本题的一个高频采分点单独写出来能让阅卷人明确看到“我知道间隔怎么算”。面试追问通常有两种一种是“优先间隔存在多个时选哪个”答案是题干没有规定优先级取第一个满足条件的间隔即可代码用 for 循环从头扫稳定可复现另一种是“间隔里空 3 个但请求是 4能不能从同间隔硬凑”答案是不能因为“优先售一个间隔内的票”要求所有座位在同一间隔该间隔不满足需求就找下一个间隔。答完再结合代码说明基本就稳了。4. 真题逐题演练完整可运行的售票程序与得分注释4.1 完整代码从间隔映射到主循环的一键可跑版本把前面各部分拼成完整程序。这份代码按 C99 标准编译文件路径保持题目默认的d:\request.txt和d:\result.txt。如果考试环境不允许写盘根目录改宏定义或直接改字符串即可。先看完整代码再逐段解释。#include stdio.h #include stdbool.h #define SEAT_NUM 80 #define INTERVAL_NUM 20 /* 座位状态0 未售1 已售 */ int seatState[SEAT_NUM]; /* 题目提供的读请求接口修正分隔符为 %d 以兼容空白分割 */ bool GetRequest(int *p) { static FILE *fp fopen(d:\\request.txt, r); static int nIndex 0; static int inputData[SEAT_NUM] {0}; static bool isfirst true; if (isfirst) { for (int i 0; i SEAT_NUM; i) { if (fscanf(fp, %d, inputData[i]) EOF) { break; } } isfirst false; if (fp ! NULL) { fclose(fp); } } if (nIndex SEAT_NUM inputData[nIndex] ! 0) { *p inputData[nIndex]; return true; } return false; } /* 题目提供的写结果接口追加写一行 */ void OutputResult(int *seatNum, int count) { FILE *fp fopen(d:\\result.txt, a); if (fp NULL) { return; } fprintf(fp, \n); for (int i 0; i count; i) { fprintf(fp, %d,, seatNum[i]); } fprintf(fp, \n); fclose(fp); } /* 座位号到间隔号1~80 映射到 1~20 */ int GetInterval(int seatNo) { int row (seatNo - 1) / 4; int col (seatNo - 1) % 4; int rowBlock row / 2; if (col 2) { return rowBlock * 2 1; } else { return rowBlock * 2 2; } } /* 统计某个间隔内的未售座位并写入 out 数组 */ int GetFreeInInterval(int interval, int out[]) { int count 0; for (int n 1; n SEAT_NUM; n) { if (GetInterval(n) interval seatState[n - 1] 0) { out[count] n; } } return count; } /* 售 1 张任意一张未售 */ int TrySellOne(int out[]) { for (int n 1; n SEAT_NUM; n) { if (seatState[n - 1] 0) { seatState[n - 1] 1; out[0] n; return 1; } } return 0; } /* 售 2 张优先相邻对否则任意两张 */ int TrySellTwo(int out[]) { for (int i 1; i SEAT_NUM; i 2) { if (seatState[i - 1] 0 seatState[i] 0) { seatState[i - 1] 1; seatState[i] 1; out[0] i; out[1] i 1; return 2; } } int count 0; for (int n 1; n SEAT_NUM count 2; n) { if (seatState[n - 1] 0) { seatState[n - 1] 1; out[count] n; } } return count; } /* 售 3 或 4 张先判余票再找间隔最后任意位置 */ int TrySellMore(int need, int out[]) { int totalFree 0; for (int n 1; n SEAT_NUM; n) { if (seatState[n - 1] 0) { totalFree; } } if (totalFree need) { return 0; } for (int interval 1; interval INTERVAL_NUM; interval) { int freeInInterval[4]; int cnt GetFreeInInterval(interval, freeInInterval); if (cnt need) { for (int k 0; k need; k) { seatState[freeInInterval[k] - 1] 1; out[k] freeInInterval[k]; } return need; } } int count 0; for (int n 1; n SEAT_NUM count need; n) { if (seatState[n - 1] 0) { seatState[n - 1] 1; out[count] n; } } return count; } int main(void) { int request, sellCount; int soldSeats[4]; int i; for (i 0; i SEAT_NUM; i) { seatState[i] 0; } // 清空旧输出文件 FILE *fp fopen(d:\\result.txt, w); if (fp ! NULL) { fclose(fp); } while (GetRequest(request)) { if (request 1) { sellCount TrySellOne(soldSeats); } else if (request 2) { sellCount TrySellTwo(soldSeats); } else { sellCount TrySellMore(request, soldSeats); } if (sellCount 0) { OutputResult(soldSeats, sellCount); } else { printf(本次请求不售票余票不足\n); } } return 0; }这段代码覆盖了题干要求的全部功能点。GetRequest的 static 变量只在第一次调用时打开文件之后从静态数组取数这是题目给定的设计不要改成每次调用都读文件。OutputResult的追加写配合 main 开头的清空操作保证多次调试不会把旧结果混进来。三个TrySell函数各自独立返回值统一表示“实际卖出的票数”失败时返回 0主流程根据返回值决定是否输出逻辑链路清晰。调试时可以用一个简单的d:\request.txt比如依次写入“2 2 4 1”四行数字用空格或换行分开。第一次请求 2 会卖出 1、2第二次请求 2 会卖出 3、4第三次请求 4 会优先找同一间隔这时 5、6、7、8 所在的间隔 2 还空着会一次卖出 5、6、7、8第四次请求 1 会卖 9。把预期结果和result.txt对照能快速验证相邻对和间隔逻辑。4.2 逐段得分点这 5 处位置是阅卷人重点看的地方编程题给分不是只看最后能不能运行而是按数据结构和规则拆点。下面这张对照表可以帮你自查提交前逐项确认比盲目改代码有效。位置评分点易错替代写法正确做法main 开头的清空输出文件操作规范直接用a不清旧文件先w打开一次再关闭GetInterval间隔模型正确按n/4连续分组行列换算两行两列成块GetFreeInInterval间隔统计在主函数里重复写循环独立函数返回数量和座位号TrySellTwo 的步长相邻判定正确i 0; i 79; i把 2、3 误判为相邻for (i 1; i 80; i 2)TrySellMore 的判定顺序规则优先级正确先找间隔再判余票先统计余票不足直接返回 0编码规范分占比不低命名要见名知义。比如totalFree比sum好freeInInterval比a好函数之间用空行隔开核心逻辑加一行注释解释“为什么先判余票”。注释不必写满但间隔映射、规则优先、不足处理这三处必须写清意图这就是扣分规则里“逻辑清楚、结构良好”的直接体现。5. 易错点速查五个“一看就会、一写就错”的失分点5.1 错把间隔当成连续 4 个座位一组错在哪以为座位 1~4 是间隔 15~8 是间隔 2直接在数组上按每 4 个元素切片。为什么错车厢座位是 4 列 × 20 行间隔由两行两列构成。1、2 在第 1 行左侧5、6 在第 2 行左侧所以 1、2、5、6 才同属一个间隔3、4、7、8 属第二个间隔。按连续 4 个座位分组后“间隔内优先”的规则就全乱套3 张 4 张票可能卖出过道两侧的座位。怎么答对用行列换算公式GetInterval计算间隔号并把间隔号和座位号的映射关系用测试用例验证。先手算 1、2、5、6 的间隔号都是 1再验证 9、10、13、14 的间隔号是 3确保公式覆盖到边界座位。5.2 直接套用fscanf(%d,, ...)导致读不到请求错在哪题目给的GetRequest里写的是带逗号的格式有些人原样照抄输入文件里却是空格或换行分隔。为什么错%d,要求读取数字后必须紧跟逗号。如果请求之间是空格函数会在等逗号时卡住inputData数组大部分位置保持 0主循环提前结束或漏读请求。怎么答对提交前把格式统一成%d兼容空白和逗号两种分隔。如果题目明确要求使用附带文件先打开 request.txt 看一眼实际内容再决定。改一行代码的事别让整个程序因为格式问题跑不出结果。5.3 先输出再更新座位状态导致同一座位被卖两次错在哪在TrySell函数里先调OutputResult(soldSeats, cnt)再置seatState为 1。为什么错下一次请求进入时座位状态还是 0同一个座位会被再次卖出。尤其是卖 2 张和 3、4 张时间隔内空座数统计也会跟着错整个结果文件看起来像“重复出票”这在铁路售票场景里属于致命逻辑错误。怎么答对把状态更新放在函数内部、输出之前完成。每个TrySell函数在确定座位号后立刻置 1返回值只表示本次卖出的票数主流程拿到soldSeats再交给OutputResult。状态更新和输出解耦既符合职责单一也能避免重复卖票。5.4 3 张、4 张票余票不足时没有提示直接随便凑票错在哪TrySellMore里先遍历间隔找不到足够空座就跳到任意位置没判断全车总余票。为什么错题干明确写了“如果剩余票数不足就提示不足不售票”。如果只剩 3 个空座、请求是 4 张正确行为是拒绝售票而不是从 3 个空座里挑 3 张卖出去因为这样等于少卖一张、规则解释不通。怎么答对在TrySellMore开头先统计totalFree小于need直接返回 0。主流程看到返回 0 就打印“余票不足不售票”不调用OutputResult。这样把“不足不售”作为最高优先级分支后面“间隔优先”和“任意位置”都在余票充足的前提下执行。5.5 输出文件用追加模式却不清空旧结果混进新运行错在哪每次调试直接运行程序result.txt用a追加上一次甚至上上次的售票结果全留在文件里。为什么错阅卷人或面试官看到的结果文件是多次运行叠加的无法判断本次运行是否真正符合请求序列。如果第一次运行卖出了 1、2第二次运行又卖出了 1、2文件里看起来就像重复出票规则写得再对也会被误判。怎么答对main开头先以w方式打开result.txt立刻关闭清空文件。之后OutputResult再用a追加本次运行的多行结果。这样每次运行都是干净起点结果文件只包含本次请求对应的售票记录。6. 答题技巧把这类规则模拟题压缩成“三步走”模板这类“规则型模拟题”不只出现在火车站售票这一题上排号系统、资源分配系统、库存扣减题都是同一个套路。我后来每次遇到这类题都强制按三步走很少再在规则翻译上丢分。第一步把所有约束写成“约束 → 数据结构”的映射。相邻对就是 (1,2)、(3,4) 这种步长 2 的配对间隔就是“两行两列”的块需要行列换算座位状态就是 0/1 数组。先把这些映射写出来代码只是翻译。题目里“相邻”和“间隔”是两种不同的结构不要偷懒合并成一种。第二步把规则翻译成独立函数每个函数只回答一个问题。“售 1 张”回答“找哪个空座”“售 2 张”回答“有没有相邻对”“售 3/4 张”回答“先判余票还是先找间隔”。函数之间不共享局部变量靠参数和返回值通信。试想如果所有规则都写在 main 里出错了要在一大段代码里找逻辑而拆成函数后每个函数都能单独验证。第三步用固定输入做 5 次手算测试。我习惯准备一个request.txt依次写入“2 2 4 1 3”然后在纸上模拟第一次卖 1、2第二次卖 3、4第三次卖 5、6、7、8第四次卖 9第五次只剩 70 多个空位、找一个间隔凑 3 张。跑完程序对照 result.txt如果前两行和纸面结果不一致一定是相邻对步长或间隔公式写错了。这一步能拦住 80% 的隐性错误。规律就一句话先画模型再拆函数最后跑数据。从那以后我每次做这类模拟题都强制先写一张“约束 → 函数”的映射表再动 main几乎没在评审规则上再翻过车。这套流程应付企业级编码机试的规则型编程题足够用了希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑