C语言经典练手题:水仙花数拆解循环与取余的巧妙应用
这两年我一直在坚持整理一套C语言入门练手题专栏名字就叫“菜鸟编程C语言100例”。前面三道题分别过了一遍变量与数据类型、格式化输出、if-else分支判断到了第四题我想着该让循环结构登场了。但纯写一个“for循环讲解”又太枯燥于是把题目设计成了“输出100到999之间所有的水仙花数”。别急着觉得这道题烂大街恰恰因为它经典才适合放在第四题这个位置——它能把循环、取余取整、数学思维和调试习惯串联起来是让人真正“会写代码”而不是“会抄答案”的关键一题。这道题适合刚学完if和for语法、想找个练手项目的初学者也适合带新人的老手直接拿来做两周的小练习。它的核心价值不在“会做”而在“能不能讲清楚为什么这么做”。下面我把这道题完整的思考过程、初版代码、踩坑记录和优化路径拉出来聊一聊。1. 题目分析与整体设计思路这一题的核心描述很简短编写一个程序输出所有三位水仙花数。所谓水仙花数指一个n位数其各位数字的n次方之和恰好等于该数本身。以三位数为例若某个数abc满足 a³ b³ c³ abc那它就是水仙花数。经典的三位水仙花数有四个153、370、371、407。拿到题目后正常的第一反应是“怎么取到百位、十位、个位”。很多新手会卡在这里不是因为循环不会写而是不知道如何用整数除法和取模运算把一个数拆开。我个人的习惯是先在稿纸上干跑一遍比如拿153举例153除以100取整得到1153对100取模得到5353再除以10取整得到553对10取模得到3。这个过程其实就是“从高位到低位逐层剥离”。把拆位的逻辑搞定之后剩下的结构就清晰了一个for循环从100走到999循环体内部做拆位、计算立方和、条件判断满足条件就输出。宏观上这题的教学重点有两个层面。第一层是语法层面让学习者熟悉for循环的基本骨架第二层是思维层面让学习者明白“枚举 筛选”是计算机解题的基本范式。我见过不少人第一次做这道题时上来就直接查百度“水仙花数C语言代码”然后复制粘贴运行出了结果觉得自己会了。但换个题目比如“输出所有四叶玫瑰数”或者“输出100以内所有满足各位平方和等于自身的数”立刻就不会了。这就是因为枚举思维没建立起来。所以整个题目的设计都围绕“让初学者真正理解拆位和循环的关系”来展开。2. 核心知识点拆解与原理补充这一题虽然短小但牵扯出来的知识点很密集尤其是拆位的数学原理。如果这部分地基打不牢后面学数组、学字符串处理时可能还会踩坑。2.1 拆位的两种思路先明确一个前提在我们讨论的三位水仙花数场景里拆位本身是纯数学操作不需要把整数转成字符串。最常用的方法是整除与取模组合。以一个三位数n为例百位 n / 100这个结果是一个0到9的整数。C语言中整数除以整数结果直接截断小数部分所以153/100得到1。十位 n / 10 % 10也可以写成 n % 100 / 10。区别在于运算顺序。n/10先把153变成15然后15%10得5n%100先把153变成53再除以10取整得5结果一样中间经过的中间量不同。个位 n % 10153对10取模直接得到3。第二种写法 n % 100 / 10 是我个人更推荐新手练习的。因为它的计算过程和人们口算拆位的过程一模一样“先扔掉百位以上的部分再把剩下的十位个位里的十位分离出来。”无论先用除法还是先用取模最后拿到的结果都是稳定的不存在谁对谁错。但你需要保证自己写完后能用具体数字走一遍避免把运算符优先级搞混。我见过有初学者把十位写成 n / 10 % 100得到的是15而不是5这就是典型的“顺序没理清”。遇到这种问题不要空想直接在编译器里加上printf打印中间值逐行看结果比对着屏幕发呆有效一百倍。2.2 运算符的坑与优先级提醒C语言里/和%的优先级相同结合方向是自左向右。这句话看起来简单但实际运用时很容易出问题。比如表达式 n / 10 % 10实际执行顺序是 (n / 10) % 10因为同一优先级从左往右算。如果你期望的是 n / (10 % 10)那就大错特错了因为10%10等于0除以0直接运行时错误。还有一种经典错误使用浮点数pow函数做立方计算。标准库math.h里提供了pow函数比如 pow(a, 3) 表示a的三次方。按理说可以用但这里有个隐藏的精度问题。pow的返回值是double浮点数在计算机里本质上是有精度损失的。当你拿一个整型变量接收pow的返回值时编译器会做隐式转换一旦计算结果出现类似27.000000000000004或26.999999999999996这样的值时赋给int后就会被截断成27或26极可能导致判断失误。所以在这个阶段我更建议直接使用 a * a * a。这不仅绕开了浮点精度隐患还让代码更加直白一看就懂。后面学到函数封装时再考虑把立方运算包成一个自定义函数而不是依赖pow。2.3 循环结构的边界思考for循环的三段式结构——初始化、条件判断、循环后操作很多人只会照着写for(i100; i999; i)但未必想过为什么写成999而不是1000。两者运行次数完全一样对结果也没有影响。但从可读性角度我推荐写成 i999因为题目明确定义的是“三位数”最大就是999让代码的表达范围和题目描述保持一致别人读起来不需要额外换算。同样值得思考的是能不能不从100开始而是从100开始逐个枚举这个问题的答案显然是肯定的三位数就是从100开始的包括100本身。也就是说循环起点可以写成i100不需要从0或1开始去白跑几百次。很多人写循环喜欢“从0开始走遍所有数”在这道题里那是在浪费CPU。3. 实操过程与代码实现细节到了这一节我们把整个代码分步骤搭起来。先说调试环境我平时练习用的是Visual Studio Code加MinGW的gcc编译器调试时打断点比IDE要麻烦一点所以会用printf打印中间状态来辅助。如果你用的是Dev-C或某高校OJ系统自带的在线编译器操作方式完全一致代码可以直接跑。3.1 确定整体框架第一步写标准头文件和主函数框架。包含stdio.h即可不需要额外引入math.h因为我们要刻意避开pow。#include stdio.h int main() { // 循环遍历100到999 // 拆位计算立方和判断输出 return 0; }很多初学者会把int main写成void main。我的建议是坚持用int main并返回0。这不是什么考试强迫症而是在C标准里int main是唯一被标准定义的形式。某些古老编译器兼容void main但换到新环境或者参加在线评测时可能出现警告甚至编译失败。细节决定习惯。3.2 拆位与判断的初版实现接下来写循环体。这里我给出的第一个版本也是大多数教程里会写的版本#include stdio.h int main() { int i; int a, b, c; int sum; for (i 100; i 999; i) { a i / 100; // 百位 b i / 10 % 10; // 十位 c i % 10; // 个位 sum a * a * a b * b * b c * c * c; if (sum i) { printf(%d\n, i); } } return 0; }逐行说明一下每一步的意图。循环变量i用来承载当前被检查的三位数。a、b、c三个变量分别存放百位、十位、个位的数字。拆位之后直接对这三个数字做立方求和。这里如果用a i/100那么对于153来说a1对于370来说a3完全符合预期。再用b i / 10 % 10拿153举例i/10得到1515%10得到5。最后个位直接i%10。sum变量存储三个数字立方后的总和。紧接着用if做一个等价判断。如果判断通过就把这个数通过printf打印出来。输出格式我用的是%d\n每个结果占一行简单清晰方便观察输出列表。运行这个程序正确的输出应该是153 370 371 407如果你拿到的结果只有前三个或者只有第一个说明判断条件或者拆位逻辑有问题。此时不要直接盯着代码硬看把sum值打印出来比对一部。3.3 加入打印调试信息的过程我在实际带人过程中发现初学者很难一次写对。与其给一个完美答案不如让学习者自己把调试信息加上去观察拆位的结果对不对。比如在sum赋值之前插入printf(当前数字: %d, 百位: %d, 十位: %d, 个位: %d\n, i, a, b, c);对153而言会输出“当前数字: 153, 百位: 1, 十位: 5, 个位: 3”。如果输出变成“百位: 1, 十位: 15, 个位: 3”说明你的b取错了问题就出在“取模和除法的顺序”上。这种加调试打印的方法比看代码要直观得多也很建议保留这个习惯。使用完调试后把它注释掉保持最终输出的清爽。这个过程也体现了真实的编程日常不是一次写对而是通过观察中间变量不断修正。这也是为什么我一直提倡练编程题时一定不能只看别人的完整代码必须自己动手敲一遍甚至故意写错几版再通过调试改正记忆才会牢固。4. 常见问题与排查技巧实录写这道题的过程中我整理了三个出现的频率最高的问题每一个我都亲自在课堂上帮人排查过。4.1 输出的结果是空的一个数都没有这个现象的原因通常是判断条件写反或者写错。比如有人把 if(sum i) 写成了 if(sum i)注意这里是赋值而不是比较。sum i 这个表达式会把i的值赋给sum然后判断赋值后的sum是否为真整体恒为真导致程序把每个数都打印出来——不对这个现象的表现应该是打印全部三位数而不是一个都没有。如果真的是一个都没有多半是sum计算成了别的值。常见原因有两个一是对i做了浮点数相关的错误操作二是把a、b、c的取值搞错了比如十位算成了15以上。这时候用上面的调试打印最有效。4.2 结果只输出了153没有370、371、407这个情况比较隐蔽。我排查过的一个案例是有人把个位写成了 i % 10这没问题但他把十位写成了 i % 10 % 10看起来好像也是“先取余再取余”实际执行时确实能算出一个数但对像370这样的数i%10先得到0再%10还是0十位就变成0了这导致判断失败最终结果只剩153之类的数。另一个隐蔽的坑是使用pow函数。有同学写pow(a,3)打印pow的结果时看到是27.000000感觉没问题但赋值给int的瞬间发生截断。特殊情况如pow(5,3)在某些编译器环境下计算结果可能是124.999999转成int后变成124丢掉一个水仙花数。所以再次强调不要用pow做整数小次方运算。4.3 编译时出现“undefined reference to pow”用了pow函数的人会遇到链接错误。因为pow函数在数学库里gcc编译时需要在末尾加-lm参数。加上之后能编译通过但前面说的精度问题仍然存在。我的建议是从初学阶段就养成用乘法直接计算的习惯等以后理解了浮点存储原理再考虑pow也不迟。下面把常见排查要点整理成一张速查表方便你对照自查。现象可能原因排查方法一个结果都没有拆位错误 或 赋值符号误写调试打印a、b、c的值结果只有部分水仙花数十位拆位错误 或 pow精度问题打印中间sum值换用连续乘法编译报pow相关错误未链接数学库 或 用错了pow优先避免pow用aaa输出的数字范围不对循环边界写错检查for是否从100到9995. 从这题延伸出来的几个改进方向这道题本身写对只是及格更值得玩味的是后面不断改造它的过程。几个方向我都在自己的练习题集里试过拿来分享。第一个方向是把代码封装成可复用的函数。比如写一个int isNarcissistic(int num)专门判断一个数是不是水仙花数然后main里只做循环调用。这个小改动对后续学习模块化编程很有帮助把判断逻辑和遍历逻辑分开代码结构会更干净。第二个方向是让程序支持任意位数。比如输入一个n位数判断它是否满足“各位数字的n次方之和等于自身”。这就涉及循环动态计算指数不再是固定三次方。此时你可以实现一个自定义整数次幂函数比如用for循环累乘返回整型结果既绕开pow精度问题又不限制次数。第三个方向是扩展成水仙花数的“强化版”练习找1000以内的所有“阿姆斯特朗数”或者找100以内的所有“各位数字平方和为原数”的怪数。本质上还是一样的枚举加筛选但数字位数不同、指数不同学生必须真正理解拆位思想才能举一反三。我在实际练习中还发现把这道题改为“输出水仙花数的个数”也很有价值。你只需要用count变量累计符合条件的数字数量最后输出count。这个改动虽然简单但对for循环中计数器的使用是很好的训练后面处理统计类题目时会频繁用到。写在最后的做题建议这道题我做过很多遍每次带新人重做一遍都能看到新的坑。有一点我特别想强调写编程题不是背答案也不是比谁第一次就写对。真正重要的是你拿到题之后有没有在纸上演算有没有故意制造几个错误再把它调通。我见过不少学习者看别人的代码觉得很简单自己合上编辑器却写不出来原因就是“观看时代入了别人的逻辑书写时没有建立自己的逻辑”。如果你刚做完这道题可以试着挑战一下不看我上面给的代码自己从零写一遍完整可运行的程序。写完再对照一下拆位的写法看看是不是用了自己能够解释清楚的方式。如果讲解给别人听也能讲明白那这道题才算过关。后面第五题我打算把这个水仙花数问题改成用函数封装的形式再写一遍顺便引出数组的概念。到那个时候回头再看今天的代码你会发现当初觉得抽象的东西其实只是成长路上的一个台阶。

相关新闻

SpringBoot+Vue轻量级博客系统设计与实现

SpringBoot+Vue轻量级博客系统设计与实现

1. 项目概述:SpringBootVue轻量级博客系统设计去年帮学弟评审毕业设计时,发现市面上很多博客系统要么功能冗余,要么扩展性差。于是我们决定用SpringBootVue搭建一个轻量级但五脏俱全的在线创作社区。这个系统最核心的特点是采用前后端分离架构…

2026/10/15 6:55:51 阅读更多 →
正念与心流:10分钟呼吸练习,让专注成为生命底色

正念与心流:10分钟呼吸练习,让专注成为生命底色

1. 先说清楚:我们到底在谈什么你有没有过这样的时刻:明明手头有一件重要的事要做,却忍不住五分钟刷一次手机;开会时脑子飘到晚上吃什么;想读几页书,眼睛盯着一行字,心里却已经把明天的待办事项过…

2026/10/15 6:55:51 阅读更多 →
Oracle 数据实时入仓 Doris 和 StarRocks 怎么做?CDC、全量迁移与 5 种方案对比

Oracle 数据实时入仓 Doris 和 StarRocks 怎么做?CDC、全量迁移与 5 种方案对比

在很多企业系统中,Oracle 负责在线事务处理,Doris 或 StarRocks 负责报表、数据分析和实时数据服务。当订单、用户、支付和日志数据持续增长时,直接在 Oracle 上运行复杂统计 SQL,可能影响线上业务。因此,很多团队会建…

2026/10/15 6:55:51 阅读更多 →

最新新闻

CentOS 7.6搭建DNS正向解析服务:BIND配置实战

CentOS 7.6搭建DNS正向解析服务:BIND配置实战

平时做运维的朋友肯定都有过这种经历:公司内部新起了一套系统,IP地址越来越多,今天要连这个、明天要连那个,总不能天天翻Excel表格。更尴尬的是,内网服务想用一个好记的域名访问,结果发现公共DNS根本不认识…

2026/10/15 7:37:59 阅读更多 →
ConcurrentHashMap 1.7与1.8源码对比:从Segment到CAS+synchronized的并发演进

ConcurrentHashMap 1.7与1.8源码对比:从Segment到CAS+synchronized的并发演进

做Java开发的朋友,多少都被ConcurrentHashMap“教育”过。面试要问,源码分析要讲,线上排查高并发问题也绕不开它。而只要一聊到源码,1.7和1.8这两个版本的区别就是永远躲不开的坎:一个是Segment分段锁,一个…

2026/10/15 7:37:59 阅读更多 →
WinForm文本编辑器源码解析与高级功能扩展实战指南

WinForm文本编辑器源码解析与高级功能扩展实战指南

简介:这份C#文本/代码编辑器专题文档,面向具备一定C#基础、想从零构建或二次开发编辑器的研发人员,解决从界面搭建、文件操作到功能扩展各环节的关键问题。文档先讲解基于WinForm与RichTextBox的基础实现,包括字体加粗/斜体/下划线…

2026/10/15 7:37:59 阅读更多 →
微信小程序投票系统怎么从零搭建?以活动评选为例的完整流程

微信小程序投票系统怎么从零搭建?以活动评选为例的完整流程

从技术视角看,在微信生态里"创建一个投票活动",本质是配置一套活动数据结构 报名审核状态机 防刷策略 数据导出能力。本文以「活动评选」场景为例,梳理从创建到发布的完整流程,并以投票帮小程序作为落地实践案例。 …

2026/10/15 7:37:59 阅读更多 →
T型三电平逆变器VSG自适应控制与并离网切换优化

T型三电平逆变器VSG自适应控制与并离网切换优化

1. 项目背景与核心价值电力电子变换器在新能源发电系统中扮演着关键角色,而T型三电平逆变器因其高效率、低损耗的特性,在中大功率场合得到广泛应用。传统逆变器控制策略在并离网切换过程中常面临暂态冲击大、同步性能差等问题,这正是虚拟同步…

2026/10/15 7:37:59 阅读更多 →
软件的本质是行为:REA式行为克隆全流程拆解与实操复盘

软件的本质是行为:REA式行为克隆全流程拆解与实操复盘

说真的,看到“REA三天狂揽12万星”那条标题的时候,我第一反应是:这又是标题党?一个Agent,不用源码就能克隆一切软件,听起来太像营销号编出来的故事。但等我顺着链接把演示看完,又拿自己手头一个…

2026/10/15 7:36:59 阅读更多 →

日新闻

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

Docker Compose 容器化部署 ChirpStack LoRaWAN 网络服务器实战

1. 为什么选择容器化方式部署 LoRa 网络服务LoRa 物联网项目落地时,网络服务器是整个链路的核心枢纽。它负责接收网关上报的数据包、完成去重与解密、再把有效载荷分发给上层应用。早期大家习惯把网络服务器直接装在物理机上,依赖一堆系统库和特定版本的…

2026/10/15 0:00:46 阅读更多 →
共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

共享LLM服务下的跨模型自动扩缩容:Kubernetes实战与避坑指南

1. 共享推理服务里,扩缩容到底难在哪做过大模型推理服务部署的人,大概都经历过这样的场景:一个团队共用一套推理集群,A 业务跑的是 7B 的小模型,B 业务跑的是 72B 的大模型,C 业务白天流量高、晚上几乎归零…

2026/10/15 0:00:46 阅读更多 →
Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

Systemd 启动阶段根文件系统只读之谜:fstab 与 remount-fs 服务深度解析

1. 引子:一次内核恐慌引发的追查先从一个真实场景说起。某次我给一台老旧的嵌入式设备折腾系统裁减,为了把启动时间压进两秒以内,我手贱地把一串自认为“没用”的 systemd 服务给 disable 了,其中就包括systemd-remount-fs.servic…

2026/10/15 0:00:46 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/15 0:14:36 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/14 22:45:47 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/14 7:53:34 阅读更多 →