资讯详情

资讯详情

C语言编码考试题拆解:火车站售票程序的规则优先级与数据结构设计

简介这是东软集团IA事业部07年编码考试C编程赛题面向需要强化C语言基本功、数据结构与文件操作能力的开发者。题目以火车站售票程序为实战场景要求模拟一节80座车厢的售票流程定义座位状态数据结构实现一次最多售4张票的规则其中2张票优先相邻座位、3至4张票优先同一间隔无法满足时再随机出票并能够反复从文件读取购票需求、将售票结果写入文件。资源包含完整试题文档共1个doc文件压缩包大小54KB其中涵盖题目背景、五道编程题分值分布、售票规则总则及程序要求并附有GetRequest与OutputResult两个关键函数的参考实现可直接用于搭建程序框架。文档同时注明了输入输出文件默认路径及调试用request.txt的用法便于按真实考试条件自行验证。已有232人学习浏览适合备考企业级C语言编码测试也可作为数据结构与文件操作的实战练手素材。通过研读该试题能够系统梳理座位状态管理、优先规则判断及文件解析的完整编码思路。1. 这道C语言编码考试题考的是四件能直接照做的事第一次拿到这套早年某企业IA事业部的C语言编码考试题时很多备考的人会误判火车站售票程序把80个座位卖了不就行了等真正动手才发现题目用一段很短的描述同时压了四个考点——数据结构怎么设计、相邻与间隔怎么判定、三种售票规则的优先级怎么落地、文件读写接口怎么接。更反直觉的是这套题总分40分编码规范分含在每一题里最多能扣除题目分数的三分之一。也就是说程序能跑只算及格规则分支写对、代码风格干净才是拿分关键。这篇文章就把这套题的考点分布、高频代码写法、答题顺序全部拆开适合正在备考C语言笔试机试或技术面试的读者直接背下来练。2. 考点与分值40分里规范能扣13分数据结构和规则占大头2.1 40分的构成为什么说“能跑”只是及格题目原文写得很清楚编程题总计40分规范方面分数包含在每题分数中。很多人把注意力全放在“实现功能”上忽略了最后那句“程序要求逻辑清楚、结构良好符合编码规范要求否则扣分最多扣除题目分数的三分之一”。三分之一是什么概念按照40分估算规范扣分上限在13分左右。如果数据结构杂乱、函数职责混乱、变量命名随意哪怕运行结果全对也可能只拿到27分上下。反过来功能全对加上规范良好这题就是拉开差距的题目。参考往年类似编码题的评分倾向可以做一个相对合理的分值拆解虽然不是原卷官方细则但足够用来分配作答时间考察模块对应题目要求参考分值座位数据结构定义与初始化第1条“定义存储车厢座位信息的数据结构和相关状态信息”8分三种售票规则的优先级第2条“模拟售票过程”核心逻辑18分文件读取与结果输出GetRequest / OutputResult 的正确使用6分多次售票主循环与终止条件第2条“程序运行允许多次售票”4分编码规范与结构第3条最多扣除题目分数的三分之一上限约13分运行结果正确第4条“程序能够运行运行结果正确”包含在功能分中从这个拆解能看出一件事规则优先级那18分是命脉而文件输入输出虽然只占6分但它连接着主循环和题目给定的两个函数一旦接错规则写得再好也输出不出去。备考时应该把“数据结构—规则—文件—主循环”当成一条流水线来练而不是分开背几个孤立的语法点。2.2 从题目描述反推判分人会看什么题目给了三个很具体的信号。第一座位号从1到80分为40个间隔每个间隔包含两排座位。这不是普通的编号题判分人一定会验证你的间隔划分是否符合“1、2、5、6一个间隔3、4、7、8一个间隔”的规律。第二GetRequest和OutputResult的实现在下页可以直接使用。这说明这两个函数是送分工具正确调用即可不要去改动它们的数据读取方式更不要自己重新写一套文件解析逻辑。第三输入文件默认为d:\request.txt输出文件默认为d:\result.txt但“根据实际情况可以修改”。这意味着判分环境可能换路径所以代码里不应该把路径写死到字符串拼接的角落而应该让文件路径集中在程序开头方便替换。再往深一层想判分人最常做的验证动作有三个用题目附带的request.txt跑一遍看输出文件是否生成且格式正确连续多次请求看是否出现重复售票故意构造“请求3张但全车厢只剩2张”的输入看程序是否提示不足且不售票。其中一个高频陷阱是很多人会在“售3张或4张原则”里漏掉兜底逻辑。原文说的是“优先售一个间隔内的票如果剩余票数不足就提示不足不售票。如果不能满足优先原则则售任何位置的票”。注意这里的层次总余票不够才提示不足总余票够但同间隔凑不齐要走“售任何位置”的兜底分支。这个分支在判分时经常被单独构造用例来测比如请求4张车厢里没有任何一个间隔剩4个空座但全局仍有5张余票正确输出应当是卖出4个任意位置而不是拒绝售票。2.3 一次完整的输入输出推演结合题目给定的GetRequest实现输入文件的格式是1到4之间的数字中间可以用空格、制表符、换行符、换页符和回车符分割读到0或文件结束时停止。假设request.txt内容为1 2 3 4 2 2 3 4 0程序第一次读取到1售1张第二次读取到2售2张以此类推读到0时停止售票。OutputResult每次会向result.txt追加一行分隔线加一行座位号分隔线是20个等号座位号用逗号结尾。需要注意的是result.txt以追加方式打开所以同一程序多次运行结果会不断累积。如果要重新测试应该先手动清空result.txt否则会看到上次运行的历史记录容易误判。判分场景里还有一类边界是座位卖光后再来请求此时GetRequest继续返回1到4但车厢内余票为0程序必须先检查“剩余票数是否足够请求数”不足则提示且不调用OutputResult。很多人会在sell函数内部边卖边发现没有空座结果把已经卖出的座号也写进了输出数组最后输出了一组不完整的座位号。这类错误在笔试机器判分里是必扣分项提前用“先统计全局余票不够直接拒绝”的方式即可绕过。3. 高频考点80个座位的建模与“间隔/相邻”判定3.1 三种数据结构方案为什么推荐结构体数组座位信息的存储方式直接决定后续规则实现的复杂度。第一种是int seat[80]用0和1表示空和已售简单但语义弱打印调试时不直观。第二种是位标记用80个bit表示状态省空间但对初学者来说读写过于绕。第三种是结构体数组每个座位有编号和状态两个字段代码可读性最好扩展时还能再挂“是否靠窗”“是否过道”等信息。本题只有80个座位内存不是瓶颈可读性和正确性才是重点所以推荐结构体数组。typedef struct Seat { int seatId; /* 座位号从1到80 */ int sold; /* 0表示未售1表示已售 */ } Seat; Seat seats[80]; void InitSeats(void) { int i; for (i 0; i 80; i) { seats[i].seatId i 1; seats[i].sold 0; } }这段代码有三个细节。第一seatId从1开始和题目座位号对齐避免“数组下标0对应座位1”的换算错误。第二将所有座位初始化为未售这个动作必须发生在任何售票逻辑之前。第三用一个独立的初始化函数而不是在main里散落循环是为了满足“结构良好、符合编码规范”的扣分条款。面试官最想看到的是初始化、状态查询、状态更新被拆成不同的小函数而不是所有逻辑堆在一个while循环里。3.2 相邻判定的正确公式2和3不算相邻题目对“相邻”有明确定义1和2、3和4、5和6、7和8等且特意强调“2和3不算相邻”。这句话很容易被忽略。如果直接写成“座位号相差1就算相邻”2和3会被误判为相邻而它们实际上分属两个不同的相邻对。相邻对的规律是座位号除以2之后落在同一个“对”里。1和2的(座号-1)/2都等于03和4的(座号-1)/2都等于15和6的(座号-1)/2都等于2。所以判断函数可以写成int IsAdjacent(int a, int b) { return (a - 1) / 2 (b - 1) / 2; }这个函数的参数单位是真实座位号不是数组下标。如果你把数组下标直接传进去比如seats[0]对应座位1、seats[1]对应座位2那么(0-1)/2和(1-1)/2不同会错误地认为1和2不相邻。写函数时一定在调用入口换算成座位号。另一个常见误用是直接判断a和b的差值等于1这会把2和3、4和5也判成相邻和题目规则冲突。3.3 间隔判定的公式先把布局图读明白原题给出的布局可以简化为四行座位第一行1、5、9直到77第二行2、6、10直到78过道第三行3、7、11直到79第四行4、8、12直到80。所谓“间隔”指的是过道同一侧、相邻两排的四个座位。验证例子1、2、5、6属于同一间隔3、4、7、8属于同一间隔9、10、13、14属于同一间隔。规律是每隔两个“相邻对”换一次侧边再每隔四个座位换一排。int IntervalOf(int seat) { int idx seat - 1; int row idx / 4; /* 第几排0到19 */ int col idx % 4; /* 0、1在过道一侧2、3在另一侧 */ return (row / 2) * 2 (col 2); }调用示例座位1idx为0row为0col为0返回0座位5idx为4row为1col为0(1/2)*20等于0和座位1同间隔座位3idx为2row为0col为2返回1座位8idx为7row为1col为3返回1。这四个例子覆盖了首排、第二排、过道两侧能验证公式是否写对。实际考试时可以把这个推导过程写在注释里再用一个断言式的测试代码验证几个已知间隔确保不把间隔和“相邻对”混淆。间隔判定的价值在于售3张或4张票的优先逻辑遍历0到39号间隔统计某个间隔内4个座位的空座数够数就取。如果代码里没有一个可靠的IntervalOf函数这一步就只能硬编码映射表40个间隔写出来既冗长又容易漏不推荐。4. 高频考点三种售票规则的实现顺序与兜底分支4.1 售1张与售2张相邻优先和“随便”的差别售1张票的原则是“随便售出一张没有售出的票”。这里的“随便”在实际判分里等价于“任意合法空座”不强制要求随机。写代码时用遍历找第一个空座即可刻意引入rand()随机函数反而可能导致输出不稳定增加自查难度。售2张票的原则是“优先售2张相邻的票如果没有就随便售出两张票”。所谓相邻只认相邻对即座位号(2k1, 2k2)这一对其中k从0到39。int SellOne(int *out, int *count) { int i; for (i 0; i 80; i) { if (seats[i].sold 0) { seats[i].sold 1; out[(*count)] seats[i].seatId; return 1; } } return 0; } int SellTwoAdjacent(int *out, int *count) { int k; for (k 0; k 40; k) { int a 2 * k 1; int b 2 * k 2; if (seats[a - 1].sold 0 seats[b - 1].sold 0) { seats[a - 1].sold 1; seats[b - 1].sold 1; out[(*count)] a; out[(*count)] b; return 1; } } return 0; }SellOne和SellTwoAdjacent都返回int用1表示成功、0表示失败主函数可以根据返回值决定是否调用OutputResult。SellTwoAdjacent里有个细节a和b是座位号访问seats数组时必须减1同时要保证“两个都空”再一起售出不能先卖出a再发现b已售那样会造成只卖出一张的脏状态。如果SellTwoAdjacent返回0才进入“随便售两张”的兜底此时同样不能重复选择同一个座位要维护一个局部计数器找到两个不同空座。4.2 售3张或4张优先同间隔失败后不拒绝而是兜底这是整道题逻辑最重的部分。原文规则分两层第一层“优先售一个间隔内的票”第二层“如果剩余票数不足就提示不足不售票如果不能满足优先原则则售任何位置的票”。正确的实现顺序应当是先统计全局余票全局余票不足请求数直接提示不售全局够时遍历所有间隔找空座数大于等于请求数的间隔找不到这样的间隔就兜底从任意位置凑够请求数。int SellByInterval(int need, int *out, int *count) { int g; for (g 0; g 40; g) { int base (g / 2) * 8 (g % 2) * 2 1; int cand[4]; int freeCount 0; int i; cand[0] base; cand[1] base 1; cand[2] base 4; cand[3] base 5; for (i 0; i 4; i) { if (seats[cand[i] - 1].sold 0) { freeCount; } } if (freeCount need) { for (i 0; i 4 *count - (out - (int*)0) need; i) { if (seats[cand[i] - 1].sold 0) { seats[cand[i] - 1].sold 1; out[(*count)] cand[i]; } } return 1; } } return 0; }注意cand数组的生成逻辑base的公式由第3章的IntervalOf反推而来也可以不用公式直接维护一个二维数组intervalSeats[40][4]把所有间隔的四个座位号预先填好。笔试时间紧张时预填表比公式更稳缺点是占篇幅。SellByInterval只负责“优先”分支找不到符合要求的间隔时就返回0由调用方决定是否进入兜底。兜底函数就是普通的“从前往后收集need个空座”结构类似SellOne但需要收集多个int SellAny(int need, int *out, int *count) { int i; for (i 0; i 80; i) { if (seats[i].sold 0) { seats[i].sold 1; out[(*count)] seats[i].seatId; need--; if (need 0) { return 1; } } } return 0; }SellAny有一段容易出错的边界循环里先标记已售再递减need如果need减到0就返回。如果剩余空座总数恰好等于need循环自然结束并返回1如果总数小于need说明调用方在“全局余票检查”那里漏了判断sell函数会边走边卖最后卖出部分座位却返回0主函数又不会调用OutputResult导致部分座位被标记已售但结果没有输出。这是典型的脏状态自测时要把“余票不足”的输入也跑一遍确认输出文件没有任何新增内容。4.3 主循环与给定文件函数的正确接法题目提供GetRequest和OutputResult这两个函数主循环的写法直接决定调试效率。GetRequest内部用静态变量缓存了输入文件的所有数据每次调用取一个数取到0或读满80个数字后返回false。OutputResult每次以追加方式打开result.txt。主流程建议先做全局余票检查再按需求数分发到对应的卖票函数。int main(void) { int req 0; InitSeats(); while (GetRequest(req)) { int out[4]; int count 0; int totalFree 0; int i; for (i 0; i 80; i) { if (seats[i].sold 0) totalFree; } if (totalFree req) { printf(Insuffcient tickets\n); continue; } if (req 1) { SellOne(out, count); } else if (req 2) { if (!SellTwoAdjacent(out, count)) { SellAny(2, out, count); } } else { if (!SellByInterval(req, out, count)) { SellAny(req, out, count); } } if (count 0) { OutputResult(out, count); } } return 0; }这段代码里有几个值得向面试官解释的设计。第一totalFree每次循环重新统计而不是维护一个全局计数器虽然效率低但能避免售出座位后忘记递减计数器导致的逻辑错误80个座位的规模完全够用。第二count在进入每个分支前都被重置为0out数组长度固定为4因为题目限定每次最多售4张。第三只有count大于0才调用OutputResult保证“余票不足”的场景不会往输出文件里写空行。GetRequest和OutputResult的使用有个隐藏考点不要试图“修复”它们的实现。比如GetRequest用fscanf读入时把逗号当作格式分隔符而输入文件实际可能以空格和换行分隔。fscanf的格式串“%d,”会在读完数字后尝试匹配逗号匹配失败并不会导致本次读取失败所以这个实现能够兼容空格制表符换行符等常见分隔。如果你自己重写一个读取函数反而可能破坏“0表示停止”的约定或者引入文件指针重复打开的问题。5. 易错点五个失分点与对应的正确写法5.1 把2和3当成相邻邻座规则直接错一半错在哪有考生把“相邻”理解为“座位号连续”于是SellTwoAdjacent里判断条件写成abs(a - b) 1导致2和3被配成一对。为什么错题目明确说“2和3不算相邻”相邻只发生在(1,2)、(3,4)、(5,6)这种奇数在前、偶数在后的组合里。如果按绝对值差1判断卖出2、3这一对时位置上是背对背的两个座不符合“相邻”的判定标准这种输出在判分时属于规则错误。怎么答对用(座号-1)/2是否相等来判断。1和2的(1-1)/2与(2-1)/2都是03和4的(3-1)/2与(4-1)/2都是17和8的(7-1)/2与(8-1)/2都是3而2和3的(2-1)/2等于0、(3-1)/2等于1自然判为不相邻。5.2 间隔公式在“3、4、7、8”上验证失败还硬写错在哪写出一个间隔函数后只用1、2、5、6验证发现返回0就收工没有继续验证3、4、7、8。为什么错车厢的间隔划分不是简单的“每四个座一组”。如果公式写成(seat-1)/4座位1、2、5、6会被拆成两组因为它们对应的数组下标0、1属于同一组而下标4、5属于下一组。所以只验证第一个间隔会掩盖公式错误。怎么答对把三个已知样本全部跑一遍1、2、5、6要返回同一个间隔值3、4、7、8要返回另一个间隔值9、10、13、14要返回第三个间隔值。最好的办法是写一个InitIntervalTable提前把40个间隔的座位号映射填入二维数组运行时直接查表彻底绕开公式推导风险。5.3 售3张或4张找不到同间隔就直接拒绝错在哪SellByInterval返回0后主函数直接continue不调用任何兜底卖票逻辑。为什么错题目说的是“如果剩余票数不足就提示不足不售票如果不能满足优先原则则售任何位置的票”。“剩余票数不足”指全局余票小于请求数而“不能满足优先原则”指全局余票够但没有任何一个间隔能凑齐请求数。这两种情况性质不同后者仍然要卖出任意位置的票。怎么答对在主函数里先算totalFreetotalFree小于req才拒绝SellByInterval失败后紧接着调用SellAny(req, out, count)。为了验证这条逻辑可以构造这样的输入先把某些座位手动售出使得某个请求为4的时刻所有间隔最多只剩3个空座但全局还有6张票此时程序必须卖出4张任意座位。5.4 输出文件用“w”覆盖或忽略了GetRequest的越界隐患错在哪自己写输出函数时用fopen(d:\result.txt, w)第二次售票的结果把第一次覆盖或者在使用GetRequest读取时没有注意到它内部用静态数组保存80个数当nIndex等于80时仍然先访问inputData[80]再判断条件。为什么错题目给出的OutputResult用的是“a”追加模式覆盖写会让多轮售票结果只剩最后一轮判分时只能看到一次输出。越界问题属于原题给定实现的一个隐患虽然实际输入文件不太可能正好80个非零数字但严格来说nIndex等于80时访问inputData[80]已经是数组越界属于未定义行为。怎么答对输出模式保持附录里的“a”不要动如果自己要审查GetRequest可以把条件顺序改成if (nIndex 80 || inputData[nIndex] 0)先判断下标再取值。这道题考的是编码规范里的边界意识能指出这个隐患并在注释里说明比闷头调用更能体现功底。5.5 兜底卖任意座位时把同一个座位卖两次错在哪SellAny函数边遍历边标记已售但没有跳过那些在SellByInterval里已标记的座位导致out数组里出现重复座位号。为什么错SellByInterval在发现间隔内空座够数时会直接把前几个空座标记为已售并把座号写入out。如果这个函数没有正确返回或者调用方在失败后又在没清空out的情况下追加SellAny就会重复卖出。还有一种情况是SellAny的内部循环没有区分“当前座位是否已售”用未售判断时写成了if (seats[i].seatId)等于没有判断状态。怎么答对统一封装一个标记函数MarkSold(seatId)所有售卖分支都通过它更新状态SellAny循环里严格判断sold字段并且在每次售卖前把count重置为0。自测方法连续请求50次以上最后检查输出数组里80个座位号是否正好覆盖1到80且没有重复这一步可以用一个简单的计数数组来验证。提示把上面五条整理成一份自测清单每次写完代码先过一遍清单再跑数据能省下大量调试时间。6. 40分钟应试答题顺序从读题到自测的固定流程6.1 前5分钟把规则翻译成伪代码拿到题先不要急着开写。第一件事画出四行座位布局在纸上标出1、2、5、6和3、4、7、8两个间隔推导间隔公式。第二件事把题目里的三个优先级写成三行伪代码1张随便2张先相邻对再随便3或4张先同间隔再随便只有总余票不足才拒绝。这两步做完整个程序的结构已经在脑中了剩下的30多分钟只是把伪代码翻译成C语言。6.2 第6到30分钟按下层到上层的顺序写代码先写InitSeats和IsAdjacent、IntervalOf这两个判定函数再写SellOne、SellTwoAdjacent、SellByInterval、SellAny四个售座函数最后写main主循环。这个顺序的好处是每个函数都能独立验证。例如写完IsAdjacent立刻测试(1,2)返回1、(2,3)返回0写完SellTwoAdjacent立刻构造一个只有这两座空着的场景跑一遍。每完成一个函数就做一次最小验证而不是全部写完再统一调试否则出错了要同时排查五个函数难度成倍增加。函数里所有输出参数统一采用(int *out, int *count)的形式out数组由调用方提供长度固定为4count记录实际卖出数量这个接口设计也能直接在面试时讲给面试官听展示你的模块化意识。6.3 最后5分钟用三组输入做回归自测第一组输入是“1 2 3 4 2 2 3 4 0”用来验证基本流程和输出文件追加效果。第二组输入是“4 4 4 4 4 0”用来验证每次请求4张时是否优先落在同一间隔当连续请求多轮后某些间隔空座不足时是否自动改为任意位置兜底。第三组输入是“1 1 1 1”后面不跟0让程序把座位卖到接近售罄再补一个“2”请求验证余票不足时是否提示并不输出。三组跑完如果输出文件里的座位号没有重复、格式每行以逗号结尾、分隔线数量等于成功售票次数这道题就稳了。提示这三组输入建议直接保存成三个request文件调试时只改程序顶部的路径宏不要每次手动重建输入。最后说一个身边的真实教训。A同学当年考这套题前30分钟就把主体代码写完了剩下10分钟一直在调输出格式结果忽略了“售4张且无同间隔可满足”的兜底分支判分用例正好打在这个分支上白白丢了一档分。从那以后我每次做这类“规则优先”的题目都会强制自己按“全局余票检查—优先分支—兜底分支”的顺序在纸上写一遍再对照题目原文逐句核对避免把“优先”当成“唯一”。这套题不难难的是把规则当成代码里的分支而不是作文里的描述。希望这篇拆解能帮你在笔试机试和面试手写代码时少踩几个坑拿到该拿的每1分。本文还有配套的精品资源点击获取
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →