GESP二级数字黑洞真题:从拆位到函数封装的C++模拟题全解析

发布时间:2026/10/20 15:35:05

GESP二级数字黑洞真题:从拆位到函数封装的C++模拟题全解析 GESP的C二级认证里数字黑洞这道题我每年都会带着学生反复刷。它看起来就是一道普通的模拟题给一个三位数把数字重排成最大数和最小数相减不断重复最后会卡在495不动。但真上手一写十个孩子里有六七个会在前导零上翻车剩下的可能栽在死循环或者输出格式上。这道题值得单独拿出来写一篇不只是因为它在真题里出现过更因为它把二级考纲里最核心的几块——数位拆分、排序、循环控制、函数封装——全部揉进了一道只有几行的题目里。把这题吃透等于把二级编程题的大半套路过了一遍。文章适合正在备考GESP二级的同学也适合带集训队的老师拿来当课堂例题。1. 数字黑洞这道题到底在考什么先看懂题目再动手1.1 一句话读懂数字黑洞所谓数字黑洞指的是某些整数经过一个固定运算反复作用之后最终会落进一个不再变化的数字。三位数的“重排求差”黑洞就是495四位数则是6174很多人也把它叫卡普雷卡常数。具体到这道题流程就四步输入一个三位数比如963把三个数字重排成最大的数963 已经是最大再重排成最小的数369两者相减963 - 369 594重复这个过程。继续算下去594 → 954 - 459 495而 954 - 459 还是 495。于是495就是这个运算的不动点一旦到达这里之后就永远停在495不再变化。这背后有个简单的原理三位数的排列组合只有有限种一个确定的运算反复作用在有限状态上结果不可能无限发散必然走入某个循环。而“最大数减最小数”这个运算的吸引环恰好就是495。这个数学性质不是考试重点但理解了它你写循环条件的时候会踏实很多——你知道它一定会停剩下的问题只是怎么用代码表达清楚。1.2 从命题角度看这道题在考什么GESP二级的考纲范围里编程题基本绕不开顺序结构、分支、循环、一维数组、字符串基础、简单排序、函数定义。数字黑洞这道题非常“坏”地把这些知识点全部串在了一起而且每一个都躲不掉数位拆分给一个整数取出它的百位、十位、个位排序三个数字按从小到大排序才能得到最小数和最大数组装把排序后的数字重新拼回整数这里藏着前导零的坑循环控制反复执行同一套运算直到结果为495函数封装把“一次变换”写成独立函数主循环才干净。所以它才适合拿来做真题分析。如果一道题只考一个知识点那叫练习把五六个知识点压在一起才叫“二级难度”。备考的时候看到一道题首先不是急着敲键盘而是先在纸上标出它到底用到了哪些考点再逐个环节去实现。2. 写代码前必须想清楚的四个关键环节拆位、排序、组数、循环2.1 数位拆分取模、除法、字符串三条路对比三位数的拆位本身不复杂核心就两个运算符整除/和取余%。假设整数 n 963百位n / 100结果是 9十位n / 10 % 10结果是 6。这里先整除10得到96再对10取余得到6个位n % 10结果是 3。如果题目给的是字符串或者你用字符串来做那就直接按字符下标取值再用s[i] - 0转成数字。哪种方式更好没有绝对答案我建议考场默认用整数拆位理由后面会说。三条路的对比放在这里方式核心代码优点缺点整数取余除法n / 100、n / 10 % 10、n % 10直观、稳定、不依赖额外库逻辑上要仔细容易把十位写成n % 100 / 10也对但不统一字符串下标s[0]、s[1]、s[2]前导零天然存在补零直观涉及字符串和整数的来回转换考场紧张时容易乱循环拆位while (n 0) { a[k] n % 10; n / 10; }能推广到任意位数三级以下学生容易把数组下标搞反边界处理麻烦这里插一句十位也可以用n % 100 / 10效果一样。但我要求学生统一写成n / 10 % 10因为它的顺序是“先砍高位再取低位”和百位、个位的写法形成一套逻辑不容易记混。考场上的代码稳定比聪明重要。2.2 排序自己写冒泡还是直接调sortGESP二级允许使用标准库#include algorithm里的sort完全可以调用一行排序省很多事。但我的建议是这道题最好自己写一遍冒泡排序原因有两个。第一二级考纲里排序是一个明确的考点很多地区模拟考和正式考都会单独出排序题。如果你从来不写冒泡只靠sort混过去遇到“把排序过程完整输出”的变体题会直接懵掉。第二三个数字的冒泡排序总共就两次外层循环代码量极小完全不会拖慢做题速度。既然不费劲顺手把考点练了是划算的。如果是三个数还可以用更简单直接的方式三次比较交换。比如把三个数放进 a、b、c先保证 a b再保证 a c最后保证 b c。但这种写法扩展性差数组一长就要重写。用完整冒泡虽然稍微多几行但是思路统一改成四位数黑洞的时候只是把数组长度和循环条件改一下就行。2.3 最大数和最小数怎么组回来前导零在这里埋伏这是整道题最关键的一步也是大多数学生丢分的地方。排序完成后假设数组升序是digits[0]、digits[1]、digits[2]那么最小数应该是digits[0]作为百位digits[1]作为十位digits[2]作为个位最大数反过来。组装方式我强烈不推荐用字符串拼接再转整数比如把字符拼成015然后转成15。这样转出来的15已经丢掉了前导零后面计算最大数 - 最小数时你会拿510 - 15而不是510 - 015去算在有中间状态为两位数时会出现严重错误。正确做法是用数位权重直接合成int minNum digits[0] * 100 digits[1] * 10 digits[2]; int maxNum digits[2] * 100 digits[1] * 10 digits[0];这句话的意思是即使digits[0] 0最小数也是0 * 100 9 * 10 9 99它在数值上等于99但这个99是从“099”按权重合成的。下一步拆位的时候99会拆成0、9、9三个数位都在运算就能继续。如果你用字符串转整数099直接被stoi转成99下一步拆位就从99拆出0、9、9其实也能得到一样的数组。看起来结果相同但前面计算maxNum - minNum时可能没问题风险出在把中间结果存成整数时你已经丢失了位数的“形态”。一旦题目改成要求输出510 - 015 495这样的过程字符串版本反而容易出问题。所以核心原则是每一步中间结果都必须按三位数来理解哪怕它的数值只有两位。2.4 循环什么时候停判断495还是判断结果不变常见写法是while (n ! 495)反复变换直到等于495。这个写法很直观配合“一定会停”的数学性质基本不会出问题。但也有人会写“当前结果等于上一次结果就停”这在三位数里能行因为495的下一步还是495。可是对于四位数6174也一样适用“结果不再变化”其实才是数字黑洞的本质定义。两种写法都能过我倾向于用while (n ! 495)理由是你已经从题目条件里明确知道终点是495直接用它做条件最省事、最不容易被绕进去。还有一个细节如果题目输入的数字就是495循环一次都不执行输出步数应当是0。有些同学会把初始状态也算成一步导致样例输出对不上。看到题目第一件事先确认样例里这个边界是怎么规定的。3. 考场推荐写法数组拆位加冒泡排序的完整实现3.1 完整参考代码下面这版代码我让备考二级的学生直接背下来不是死记硬背而是背它的结构拆位、排序、组装、循环、计数。考试时根据题目要求稍微改一改就能用。#include iostream using namespace std; // 一次变换返回 最大数 - 最小数 int transform(int n) { int digits[3]; digits[0] n / 100; // 百位 digits[1] n / 10 % 10; // 十位 digits[2] n % 10; // 个位 // 冒泡排序升序 for (int i 0; i 2; i) { for (int j 0; j 2 - i; j) { if (digits[j] digits[j 1]) { int temp digits[j]; digits[j] digits[j 1]; digits[j 1] temp; } } } int minNum digits[0] * 100 digits[1] * 10 digits[2]; int maxNum digits[2] * 100 digits[1] * 10 digits[0]; return maxNum - minNum; } int main() { int n; cin n; int steps 0; while (n ! 495) { n transform(n); steps; } cout steps endl; return 0; }3.2 逐段拆开讲一遍transform函数接收一个整数 n返回变换后的结果。函数做三件事拆位、排序、重新组装。拆位时用n / 100、n / 10 % 10、n % 10分别拿到百十个位这个顺序不要换换了代码容易看晕。排序部分外层循环跑两次是因为三个数两两比较只需要两轮内层for (int j 0; j 2 - i; j)是标准冒泡写法。如果记不住这个边界也可以写成更稳妥的版本for (int i 0; i 3; i) { for (int j 0; j 3; j) { if (digits[i] digits[j]) { swap(digits[i], digits[j]); } } }这种双重循环虽然多比较了几次但对三个数来说无所谓而且不容易写错边界。二级考试除非要求性能否则这种“笨但稳”的写法完全可用。我甚至建议平时练习先写这种没有下标的i、j全遍历版本等理解了再精简成标准冒泡。组装时一定注意下标。升序排序后digits[0]最小digits[2]最大所以最小数是“0号位 × 100 1号位 × 10 2号位”最大数是“2号位 × 100 1号位 × 10 0号位”。我把这个顺序在代码里固定下来每次写都按这个思路不会临时去推。主程序的while循环就是不断调用transform每次步数加一。输入本身就是495时步数为0这符合大多数题目的输出要求。3.3 如果题目要求输出完整过程真题的变体里有时候不要求输出步数而是要求输出从初始值到495的每一个中间结果比如963 - 594 - 495这种情况改主程序就行int n; cin n; while (n ! 495) { cout n - ; n transform(n); } cout 495 endl;注意这里最后一步是手动输出495因为n走到495后循环退出而495这个值还没被打印过。不少同学会在这个地方多写一行cout n - 结果末尾多出一个箭头格式判错。这种细节在字符串处理类题型里特别常见宁可多花三十秒读一遍输出要求。4. 另一种思路用字符串处理前导零以及如何改成四位数4.1 字符串写法完整代码除了整数拆位字符串解法在思路上更贴近“数字重排”的字面含义。把整数看成字符串每一位就是字符排序直接用sort拼最大数和最小数也用字符串拼接。完整代码#include iostream #include algorithm #include string using namespace std; int transform(string s) { while (s.length() 3) { s 0 s; } sort(s.begin(), s.end()); string minS s; string maxS ; for (int i 2; i 0; i--) { maxS s[i]; } int minNum stoi(minS); int maxNum stoi(maxS); return maxNum - minNum; } int main() { string s; cin s; int steps 0; while (stoi(s) ! 495) { s to_string(transform(s)); steps; } cout steps endl; return 0; }这个版本里有个动作值得写清楚while (s.length() 3) s 0 s;。如果transform返回的中间结果是整数99转回字符串后只剩两位下一次进函数必须补成099否则排序后最大数是990没错但最小数会变成99而不是099结果相差巨大。字符串写法补零很直观这是它的最大优势。4.2 两种写法怎么选我的结论如果你问考场用哪个我建议用整数写法原因有三个第一stoi和to_string在部分旧版本编译环境或部分评测系统里可能有兼容性问题二级考场一般没问题但一旦遇到报错排查起来非常打击心态。整数写法只用基本运算符任何环境都能跑。第二字符串解法看似直观但涉及“字符串-整数”来回转换每一步都要想清楚当前变量是字符串还是整数。调试的时候打印变量的类型和内容都要额外花心思。对二级考生来说多一层转换就多一分出错风险。第三整数写法的核心逻辑——拆位、排序、合成——在四位数黑洞、多位数重排等其他题目里可以无缝迁移字符串解法改四位数时下标和补零方式都得跟着改麻烦一点。字符串写法也有它不可替代的场景如果题目要求输出963 最大数 - 最小数这种带有补零格式的过程字符串就非常方便。所以正确的策略是两个版本都会写考场根据题目要求选。4.3 扩展改成四位数6174的思路把三位数改成四位数其实只有几处改动数组长度从3变成4拆位多一位digits[3] n % 10然后n / 10逐级取组装时权重变成1000、100、10、1循环条件从n ! 495变成n ! 6174。如果你用整数写法int digits[4]; digits[0] n / 1000; digits[1] n / 100 % 10; digits[2] n / 10 % 10; digits[3] n % 10;排序和合成规律完全一样。这个扩展练习我强烈推荐备考二级的同学做一遍它逼着你把“拆位排序组装”这套流程从“会做”变成“熟练”也能验证你是真的理解了而不是背下了三位数这组固定的数。5. 实测最容易翻车的四个细节含排查表5.1 前导零丢了结果差十万八千里这是这道题的头号坑我每届学生里都有人踩。典型错误写法是这样把三位数字排序后直接把最小数拼成整数int minNum digits[0] * 100 digits[1] * 10 digits[2];看起来没问题问题出在题目要求“输出过程”的变体里。比如输入100一次变换得到99但你要把99理解成“099”最大数是990最小数是099差是891。如果用三个数字直接凑其实也能得到这个结果因为digits数组存在0、9、9合成权重时0已经占了一个位。真正危险的是你把99当成整数99传下去下一步拆位。你拆出9和9缺一个0后面的计算就全乱了。所以记牢一句话在这道题里任何时候讨论这个数都要默认它是三位数哪怕数值上只有两位。代码层面每次变换函数内部都按三位权重合成就能自然规避这个问题。5.2 边界输入100、999这种数字输入100手算过程应该是100 - 099 - 990 - 099 891 891 - 981 - 189 792 792 - 972 - 279 693 693 - 963 - 369 594 594 - 954 - 459 495一共5步到495。你可以拿这个例子验证自己代码输出是不是5。输入999这类三个数字完全相同的数题目一般会明确排除因为最大数和最小数相同差为0永远不会到495。如果你的程序没有对输入做约束可能陷入死循环。考场题目会保证输入合法但你自己练习时可以加一行判断if (digits[0] digits[2]) { return 0; // 或者按题目要求处理 }排序后如果最小位等于最大位说明三个数字全一样。这种情况提前处理避免程序卡死。5.3 死循环风险来自“没接住非法状态”即使输入合法理论上到495一定会停。但如果你拆位写错比如十位写成了n % 10个位也写成了n % 10那中间状态可能会在某个环里打转永远到不了495。程序看起来“没什么报错”但就是不停。排查方法很简单加一个计数器超过100次直接跳出int steps 0; while (n ! 495 steps 100) { n transform(n); steps; }如果超过了100次还没到495说明你的变换函数写错了而不是数字本身有问题。还有一种情况是你把循环条件写反比如while (n 495)那是一句不用执行任何一次就退出的代码输出0也是常见错误。5.4 输出格式步数还是过程先看清题目同样一道数字黑洞不同版本的问法完全不同输出步数从输入数到495一共需要多少次变换输出每一步结果要求把中间过程全部打印出来输出最大数和最小数要求打印每次的最大数 - 最小数表达式。这三种问法主循环逻辑完全一样但输出代码差别很大。我的习惯是做题前先把输出要求圈出来再用样例反推。比如样例给的是3而你算出的是2那大概率是把初始状态多算了一步如果样例输出的格式里有箭头而你只输出了数字也会白丢分。常见问题排查表错误现象可能原因解决方法输入100结果不对中间状态99没补成099后续拆位少一位每次变换按三位权重合成不要变成两位再拆程序永远不会停十位拆分错误或循环条件写反打印每一步中间结果检查或加step100兜底输出步数比样例多1初始状态被算作一步确认是否n ! 495才进循环输出过程末尾多一个箭头循环退出前没处理好最后一个值495单独输出不要在循环里输出箭头6. 题目背后的通用套路与备考建议6.1 这类“模拟题”的通用解题套路数字黑洞属于非常典型的“模拟题”题目描述一个运算规则让你严格按规则反复执行直到满足某个终止条件。GESP二级里这种题占的比例不小比如各类“按规则变换数字”“模拟报数”“模拟排队”都属于同一家族。做这类题有个通用四步走我把它贴在集训队墙上确定每一步数据的形态。是整数还是数组还是字符串中间状态要不要补位这步想不清楚后面全错。把一次操作封装成一个函数。输入、输出都是明确的数据形态主程序只负责调用和判断终止。写清楚终止条件。等于某个数、步数上限、还是状态不再变化边界状态初始就满足终止怎么处理用题目样例逐行手算一遍再对照程序输出。数字黑洞的前导零问题本质就是第一步“确定数据形态”没做好。只要你牢记中间状态按三位数看待后面三个步骤几乎不会出问题。6.2 备考GESP二级围绕这题还能怎么练如果你正在准备GESP二级我给三个具体的练习方向。第一个方向把今天这道题改写成四个不同的版本。一是输出步数二是输出过程三是输出每次的最大数和最小数四是改成四位数6174。同一道题四种问法写四遍比做四道新题更有效。第二个方向把这套“拆位-排序-组装”流程用在其他题上。比如统计一个三位数各个数位上的最大最小值之和或者把输入的各位数字逆序输出。本质上都用到拆位和组装练熟了变换类题目基本拿下。第三个方向限时练习。二级一道编程题的考试时间大约在20到30分钟数字黑洞合理用时是15分钟以内。我建议你拿起表测试一次如果超过30分钟说明对数组和循环还不够熟需要回到数组基础题多练。提示考试中#include bits/stdc.h这种万能头文件在部分评测环境下可能不被允许。平时练习就用标准头文件考场不慌。带这题这么多年我个人最深的体会是数字黑洞本身不难难的是把每个中间状态都按同样的规则“格式化”。写模拟题最重要的习惯就是先定义清楚数据每一步的形态再写循环。你只要把这一步想明白二级的编程题基本就稳了。最后再分享一个小技巧把近几年的GESP真题都找出来每道编程题都按今天这种“拆位-处理-组装-循环”的方式过一遍。你会发现大多数题都逃不出这个套路考试时看到题目心里就有底了。
延伸阅读

更多相关文章

2026/10/20 15:30:04

iOS CoreLocation实战:从权限到后台定位的全链路指南

做iOS开发这些年,凡是涉及到位置功能的项目,基本都绕不开CoreLocation。无论是“获取当前经纬度”“判断用户所在城市”,还是后台持续上报位置,原生框架CoreLocation始终是最底层的那个环节。Swift版本写定位模块,比Ob…

2026/10/20 15:30:04

大一新生必看:大学四年规划、绩点提升与避坑指南

大学四年怎么过才不会后悔,这是几乎所有大一新生都想过的问题。但说实话,我刚进大学那会儿,根本没人给我讲这些,全靠自己踩坑踩出来的经验。今天我把这些心得整理出来,既是写给当年那个懵懵懂懂的自己,也希…

2026/10/20 15:30:04

IS-IS缺省路由全解析:从ATT位到路由泄露的实战指南

很多人一看到“IS-IS 缺省路由”这几个字,第一反应是:这不就是配一条 default 路由嘛,静态路由里 /0 指一下就完事了。但真在 IS-IS 网络里动过手的人都知道,IS-IS 的缺省路由是个“有脾气”的东西——它牵扯到 Level-1/Level-2 层…

2026/10/20 19:42:25

C++基础入门教程(一):基础知识大杂烩

前言 这是一篇面向初学者的 C 入门梳理。标题叫"大杂烩",确实是因为入门阶段需要同时建立好几条线索:程序怎么从源码变成可执行文件、变量和类型是怎么回事、控制流和函数怎么写、类为什么存在、标准库能帮我们省掉哪些重复劳动。这些内容单独…

2026/10/20 19:42:25

通达信DLL公式实战:用C语言实现MA均线指标

简介:通达信插件开发资料包,面向具有C语言与Windows编程基础、希望扩展通达信分析功能的开发者。资源围绕通达信C接口、MA移动平均线算法与DLL公式展开,展示了如何把均线计算逻辑封装为动态库函数,并通过通达信外部接口集成到行情…

2026/10/20 19:42:25

Docker微服务架构实战:拆分、容器网络与治理演进

简介:这是一份面向开发工程师、运维工程师及架构师的Docker与微服务架构落地实战PDF书籍,适合正在规划或实施微服务改造的中小型技术团队阅读。全书聚焦微服务技术选型、服务拆分原则、传统应用向微服务过渡的方法论,并系统讲解Docker技术原理…

2026/10/20 19:37:24

企业微信自动回复API落地实践:关键词匹配+AI智能对话架构指南

做企业微信消息服务这几年,我最大的体感是:客服人群里超过一半的提问是重复劳动。于是我做了个“企微自动回复 API”,从关键词匹配起步,后来接入了 AI 智能对话,这才把客服从复制粘贴里解放出来。这篇是我的完整落地记…

2026/10/19 18:06:35

金融AI落地指南:从调研报告到技术选型与工程实践

简介:这份PDF报告聚焦2025年金融服务业中国AI现状与趋势,面向金融科技从业者、AI战略规划人员及关注行业数字化转型的研究者,帮助读者把握生成式AI在银行、支付、交易等场景中的落地路径与投资方向。资源包内含1个PDF文件,大小约1…

2026/10/20 1:05:37

单片机C运行时从启动到多任务:中断安全与共享资源冲突

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

GCC编译四步详解:预处理、编译、汇编、链接实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/20 1:05:37

FIW线是什么?FEP注塑线与普通氟塑料线的区别及应用指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑