news 2026/9/26 22:27:22

C语言数组统计数字出现次数:下标映射法详解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
C语言数组统计数字出现次数:下标映射法详解

1. 这道题到底在考什么:先看懂"数字"和"整数"的区别

先说个我辅导学生时遇到的典型场面。很多人拿到"求一批整数中出现最多的数字"这个题目,第一反应是:把这一批整数挨个比较,看看哪个数出现的次数最多。这个思路错得离谱——题目说的是数字(digit),不是数(number)。数字指的是0到9这十个字符,整数指的是完整的数值本身。"124"和"421"是两个不同的整数,但它们都包含数字"1"、"2"、"4"。题目要的,是在这一批整数的十进制表示里,0到9这十个数字哪个出现得最频繁。

这是何钦铭、颜晖版《C语言程序设计(第四版)》第七章数组部分非常典型的一道习题。放在第七章,出题意图很明显:这一章的主线就是"批量数据的组织与处理",而数组就是C语言里组织批量数据最基础的手段。这道题表面上是在练循环和取余取模,实际上是在训练一个更重要的思维——用下标直接对应数据本身,用数组元素的值记录统计结果。

1.1 拆分题目:输入、处理、输出分别要做什么

把题目拆开,它其实有三个明确的任务段:

  • 输入段:读入整数个数n,再逐个读入n个整数。这里有个隐藏细节——题目给的是一批整数,不是一批数字,所以输入的是整型数据,不能用字符方式处理。
  • 处理段:对每个整数拆解它的每一位数字,统计各数字(0~9)出现的次数。拆位用循环和取余%,取整用整数除法/。
  • 输出段:找出出现次数最多的那个数字,把数字本身和它的出现次数打印出来。注意如果有多个数字并列最多,教材原题一般要求输出最小的那个,或者按题目具体要求处理。

这个三段式结构是所有编程题的通用框架。学生最容易栽的地方在第二段:不会把"统计十个数字出现次数"和"数组"联系起来。其实思路非常直接——定义一个int digit[10]数组,下标0到9恰好对应十个数字,每次拆出一个数字d,就执行digit[d]++。下标即数据,值即次数,这就是数组最漂亮的用法。

1.2 教材第七章为什么把这道题放在这里

第七章讲数组,前半部分讲一维数组的定义、初始化和引用,后半部分讲排序、查找、插入删除这些经典操作。这道题并不涉及排序和查找,但它把数组用在了另一个高频场景——计数统计。计数统计看起来简单,却是很多复杂算法的基石,比如桶排序的思想、哈希表最原始的雏形、甚至图像直方图统计,本质上都是"数据值映射到下标,下标位置累加"。

所以这道题的教学价值不在"难",而在"巧"。它让孩子第一次体会到:数组不只是拿来存一堆数据然后遍历的容器,它还可以是一个天然的映射表。这种思维转换,比会写一百行循环都重要。

2. 核心算法拆解:为什么"下标即数字"是最优解

直接上核心逻辑。假定已经读入了n个整数,现在要统计每个数字出现多少次。最朴素的错误做法是:把每个整数转成字符串,然后逐个字符去判断是'0'还是'1'……一直到'9',写十个if或者一个switch。这种写法不是不行,但代码冗长不说,思维上也绕了远路。更聪明的做法,就是定义一个长度为10的数组,用数字本身做下标:

int digit[10] = {0}; // 下标0~9分别对应数字0~9,初值全为0 int x; while (x != 0) { digit[x % 10]++; // 取出个位数字,对应下标加1 x = x / 10; // 去掉个位 }

这段代码的精髓只有两行。x % 10拿到当前最低位的数字,比如123 % 10得到3,这个3直接作为下标去访问digit[3];x / 10把最低位砍掉,123变成12,下次循环再取出的就是原来的十位2。循环往复,直到x变成0为止。整个过程既不涉及字符串转换,也不涉及浮点运算,全部是整数运算,效率和可读性双高。

2.1 用"宿舍楼分房间"来理解计数数组

为了让学生彻底理解这个思路,我上课时经常打一个比方。假设你要统计一栋宿舍楼里从0号到9号十个房间各住了多少人,最笨的办法是拿个本子挨个记:"0号房住了3人,1号房住了2人……"但更聪明的做法是:在楼下放十个计数器,每个房间门口挂一个。每来一个人住进3号房,你就把3号房门口那个计数器的数字加1。等所有人入住完毕,扫一眼十个计数器就完事了。

这里的"房间号"就是数字本身,"计数器"就是数组元素digit[3],而"有人入住"这个动作就是拆位拆出了一个3。C语言里数组下标从0开始恰好和数字0对齐,这是语言给你的天然便利,不用白不用。

2.2 完整的可运行代码

光讲思路不够,直接把完整代码贴出来,照着敲一遍就能跑:

#include <stdio.h> int main(void) { int n, i, x, d; int digit[10] = {0}; // 统计0~9各数字出现的次数 int max = 0; // 记录最大出现次数 printf("Input n:"); scanf("%d", &n); printf("Input %d integers:", n); for (i = 0; i < n; i++) { scanf("%d", &x); if (x < 0) { // 处理负数:先取绝对值再拆位 x = -x; } if (x == 0) { // 特殊处理:整数0本身包含一个数字0 digit[0]++; } while (x != 0) { d = x % 10; digit[d]++; x = x / 10; } } // 找出出现次数最多的数字(若并列,取数字最小的) for (i = 1; i < 10; i++) { if (digit[i] > digit[max]) { max = i; } } printf("出现最多的数字是%d,出现次数为%d次\n", max, digit[max]); return 0; }

这个版本是我根据教材习题要求做了完整处理的版本,比教材上某些简化的示例多了两个关键细节:负数处理和0的处理。这两个细节恰恰是初学者最容易踩的坑,下面专门展开说。

2.3 为什么初始化必须写{0}而不是省略

数组定义int digit[10] = {0};,这个初始化不是可写可不写的装饰。C语言规定,局部数组如果不初始化,它的元素值是随机的(取决于栈上残留的数据)。如果不写= {0},你后面执行digit[d]++操作的其实是一个不确定的初值,统计结果必然出错。

很多教材在讲数组初始化时会说"可对全部元素赋初值",但实践中最稳妥的做法就是:要么写{0}让全部元素归零,要么用循环显式赋值for (i = 0; i < 10; i++) digit[i] = 0;。我见过太多学生把初始化漏掉,然后统计结果一会儿大一会儿小,找半天bug找不到原因,最后发现是数组初值没归零。这个坑几乎每个初学者都会踩一次。

3. 边界条件与常见坑:负数、0和输入缓冲区的那些事

3.1 负数拆位前必须先取绝对值

教材原文并没有明确说明输入是否包含负数。如果输入是负数,比如-123,直接while (x != 0)循环会怎么走?-123 % 10在C语言里的结果是-3(C89标准允许结果为负,C99以后规定向零取整,所以-123 % 10 == -3),这样digit[-3]就会数组越界,程序可能直接崩溃,或者悄无声息地写坏其他内存数据。更极端的情况,如果x = -2147483648(int类型的最小值),对int取绝对值也是未定义行为(这是另一个坑,不过教材题目一般不会考这么深)。

安全的做法非常朴素:读入x之后,先判断if (x < 0) x = -x;,把负数转成正数再拆位。这个操作虽然简单,但如果不加,程序行为就是未定义的。数组越界是C语言里最隐蔽的杀手,有时候当场不报错,等程序运行到某个临界点才崩,排查起来让人崩溃。

3.2 整数0:循环一次都不会执行,但它包含一个数字0

这是最容易被忽视的边界情况。如果输入中出现整数0,那么while (x != 0)这个循环条件从一开始就是假,循环体一次都不会执行。可是0这个数在十进制表示里确实包含一个数字0,它应该被统计进去。所以必须在循环前单独判断:if (x == 0) digit[0]++;。

我之所以专门强调这一点,是因为很多参考书的示例代码根本不处理0,导致如果某次测试数据里恰好有0,统计结果就少了一个0的计数。这种错误在平时练习时很难发现,因为大多数人的测试数据都是正整数。一旦上了OJ或者考试系统,边界测试用例必然包含0,一测就挂。做题务必养成"边界条件优先"的习惯——输入的最小值、最大值、等于0、负数,这些极端情况都必须过一遍脑子。

3.3 scanf连续读入的换行符陷阱

这段代码里用了两次scanf,一次读n,一次在循环里逐个读整数。scanf在读%d时有一个经典行为:它会跳过前面的空白字符(空格、换行、制表符),所以即使你输入的时候每敲一个整数按一次回车,程序也能正常逐个读到值。这个行为对本题而言是安全的,不需要额外处理。

但要注意一个反例:如果之后有人图省事用了scanf("%c", &ch)读字符,那换行符就会变成'\n'被读进去,这是另一个经典bug。本题用不到字符输入,不过这个知识要记着,第七章后面的习题(比如读字符串统计字符频次)就会碰到。

3.4 我建议的测试数据组合

调试这类统计程序,不要只用一组数据。我每次给学生讲完这道题,都会让他们至少跑三组用例:

测试场景输入示例期望结果
常规正整数5: 123 456 789 123 456数字1、2、3、4、5、6各出现2次,并列最多,取最小数字1
包含03: 0 10 100数字0出现4次,最多
包含负数4: -111 222 -111 999数字1出现6次,最多

这三组覆盖了最核心的三个逻辑分支:普通拆位循环、x==0的特判、负数取绝对值。我见过很多学生第一组跑对了就急着交作业,结果第二组、第三组全崩。测试不是走过场,编程的严谨性就是从设计测试用例开始的。

4. 从这道题延伸出去:数组计数的三种变体与真实场景

4.1 变体一:统计的不是数字,而是数字段(区间统计)

统计单个数字太简单了,教材后续习题通常会升级:给一批成绩,统计各分数段的人数(90~100、80~89、70~79等)。这时候数组的下标就不直接等于分数了,而是下标i对应第i个分数段。比如用score[0]存不及格人数、score[1]存60~69分人数……核心代码就一句话:

int level = x / 10; // 84 / 10 = 8,对应下标8 if (level > 9) level = 9; // 满分100要归到最高档 if (level < 0) level = 0; // 异常数据粗筛 count[level]++;

你看,这跟本题的"下标即数字"是同一个思想,区别只在于从"一位数字"变成了"一个区间映射"。能搞懂这道题,分数段统计就是分分钟的事。

4.2 变体二:从批量整数变成单个大整数

还有一个常见变形:输入一个很长的整数(比如身份证号、银行卡号),然后统计里面每个数字出现的次数。这时候就不能用int去读了,因为位数超长。正确的做法是用字符串读入:

char str[100]; scanf("%s", str); int digit[10] = {0}; for (int i = 0; str[i] != '\\0'; i++) { digit[str[i] - '0']++; }

注意str[i] - '0'这个操作——字符'0'到'9'的ASCII码是连续的,减去字符'0'的ASCII码,就能把字符型数字转换成整型数字。这个技巧在本题目中不需要(我们直接用x % 10得到的就是整型数字),但很多后续习题会用到,早点掌握不吃亏。

4.3 真实场景:这思想其实无处不在

别觉得"统计数字出现次数"是典型的作业题,现实中它的影子到处都是。比如数据压缩算法里的频次统计——霍夫曼编码第一步就是统计每个字符的出现频率,核心代码和本题一模一样,只不过把数组长度从10换成了256(ASCII码范围)。再比如电商平台分析用户购买记录,要找出被加入购物车次数最多的商品编号,思路也是把商品ID映射到数组下标,ID对应位置自增。工业界的实时日志分析、关键词热度排行,底层逻辑都能看到这个"桶计数"的影子。

每次看到学生说"这题有什么实际用途",我都很想告诉他们:你以后接触哈希表、字典、Map这些数据结构时,会发现它们都在解决同一个问题——怎么把查询数据的复杂度降到接近O(1)。而数组直接通过下标访问,本身就是O(1)查询的极致形态。这道题就是你第一次亲手搭起的最简版哈希表。

4.4 多数字并列最多时的输出策略

本题还有一个容易被忽略的输出细节:如果0到9中有多个数字出现次数并列最多,应该输出什么?根据多数教材习题的约定,是输出最小的那个数字。我的代码里用了一个小技巧:

int max = 0; // 先假定数字0出现次数最多 for (i = 1; i < 10; i++) { if (digit[i] > digit[max]) { max = i; } }

注意这里用的是>而不是>=。当两个数字出现次数相同时,>会保留下标较小的那一个(因为下标循环从小到大遍历,后来者只有在严格大于当前最大次数时才会替换),天然满足"输出最小数字"的要求。如果题目改成"输出所有并列最多的数字",那就要在找完最大值之后再遍历一遍数组,把所有digit[i] == digit[max]的i都打印出来。细节虽小,但不同题目要求不同,读题时务必看仔细。

5. 从教材到实战:代码风格的三个进阶建议

5.1 用一个符号常量定义数组长度,别写魔法数字

如果你留意过我给的代码,会发现数组长度直接写了10,循环也直接用了9。这种写法对这道题来说没问题,因为0~9就是十个数字,数字的个数是天然的常量。但从专业代码风格角度,更推荐写成:

#define DIGIT_COUNT 10 int digit[DIGIT_COUNT] = {0};

为什么?因为数组长度和循环边界在程序中多处出现,一旦将来你要统计十六进制数字(0~15共16个)、或者统计ASCII可打印字符(95个),只需要改一行常量定义。直接写10写9,改起来就要全局搜索替换,漏改一处就是隐蔽bug。教材上的代码为了教学简洁通常直接写数字,但自己练习时养成用宏定义的习惯,对后续做项目有百利无一害。

5.2 把拆位统计的逻辑封装成函数

这道题的main函数里放了两层循环加一个查找最大值的过程,代码不长,但逻辑其实可以分成更清晰的三块:读入数据、统计、找最大值。实战中我更习惯把统计这一块抽成函数:

void count_digits(int x, int digit[]) { if (x < 0) x = -x; if (x == 0) { digit[0]++; return; } while (x != 0) { digit[x % 10]++; x /= 10; } }

函数化之后,main函数变得简洁易读,而且这个count_digits函数具有通用性——以后任何地方需要统计某个整数的数位,直接调用即可。这是初学阶段就要开始建立的模块化意识:函数不是把代码拆碎,而是把逻辑分层。

5.3 用调试器看数组值的变化:理解代码的最好方式

如果条件允许,强烈建议在IDE里对这段代码下断点,一行一行单步执行,同时打开"监视"窗口观察digit数组的变化。比如输入整数123,你会清晰地看到digit[3]先从0变1,然后digit[2]从0变1,最后digit[1]从0变1。这个过程比看任何讲解都直观,它能把"拆位"和"数组计数"这两个抽象概念变得无比具体。

这里我基于实际教学经验补充一句:大多数学生能看懂代码,但只有亲手单步跟踪过一遍,才算真正"会"了这道题。看书和写字是两回事,写字和真正理解又是两回事。

6. 关于这道题,我个人的备课心得

最后聊几句我自己的看法。这道题作为第七章的习题,难度定级不高,但它的信息密度很大:涉及循环嵌套、数组初始化、取余取整运算、边界条件判断、极值查找,还有一点点的输出策略设计。我在实际教学里会把这道题当作"数组真正入门的门槛"——前面那些一维数组求均值、求最大值的题目,本质上还是把数组当"存放数据的容器";但到了这道题,数组第一次变成了"参与运算的核心数据结构",下标第一次有了业务含义。

所以我也建议正在学C语言的读者:做完这道题,别急着往后翻,自己试着做三个变形——统计一段英文里各字母出现次数、统计一批人的年龄区间分布、模拟掷骰子统计各点数频率。这三个变体如果都能独立完成,第七章数组这部分的基础就算真正打牢了。编程不是刷完题目就结束的事,把一道经典题吃透,比潦草地做十道题更有收获。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 22:27:18

2026最新:零代码基础,3步搞定怎么样做网站管理员

2026最新:零代码基础,3步搞定怎么样做网站管理员 自己不会代码,却想独立掌控一个网站,这确实是很多转行新手最头疼的坎。别慌,2026最新的建站逻辑早就变了,技术门槛大幅降低,管理思维才是核心。你不需要成为程序员,只需要懂得像管家一样打理你的数字资产。 概念速懂:管理员到底管什么…

作者头像 李华
网站建设 2026/9/26 22:26:51

wordpress主题升级失败实战案例:3步避坑指南

wordpress主题升级失败实战案例:3步避坑指南 找建站公司怕被坑高价,这是很多中小企业主心里的刺。我见过太多老板花大几千甚至上万做站,结果主题一升级就崩盘,售后还推诿扯皮。今天不聊虚的,直接拆解一个真实的 wordpress主题升级失败 现场。这不是理论推演,而是我经手的 实战案例…

作者头像 李华
网站建设 2026/9/26 22:26:47

网站开发成本估计避坑指南:3步算清预算不花冤枉钱

网站开发成本估计避坑指南:3步算清预算不花冤枉钱 自己不会代码想做网站,最怕的就是报价单上的数字像迷魂汤,今天说五万明天说五十五万,心里没底只能干瞪眼。这份避坑指南就是帮你拆解那些藏在报价单背后的隐形成本,让你拿着计算器就能跟开发公司掰扯清楚。别再被“高端大气上档次”的话术忽悠,咱们只谈真金白银和实…

作者头像 李华
网站建设 2026/9/26 22:25:26

绍兴网站建设价格全解析:搞懂域名服务器后,到底要多少钱

绍兴网站建设价格全解析:搞懂域名服务器后,到底要多少钱 很多绍兴的老板或者刚入行的朋友,一开口就问:“做个网站到底 多少钱 ?” 别急,先别急着报价。 如果你连 域名服务器搞不懂 ,那报价单上的数字对你来说就是一串乱码。 在绍兴,从几千块的模板站到大几万的定制开发,价格差异巨大。…

作者头像 李华
网站建设 2026/9/26 22:25:02

关于高校网站建设论文的总结对比评测

高校建站论文避坑指南与速查手册 改个需求建站公司拖一周,这种痛谁懂?做高校信息化项目十年,见过太多甲方拿着“论文级”的标准去卡商业交付,最后双方都头大。今天不扯虚的,直接上这份 速查手册 ,把那些晦涩的学术概念翻译成你能落地的技术选型逻辑。…

作者头像 李华
网站建设 2026/9/26 22:24:57

做dna胎儿亲子鉴定网站避坑指南

做dna胎儿亲子鉴定网站避坑指南 手里攥着预算,心里没底,这是很多想做垂直行业站点的老板的真实写照。尤其像 做dna胎儿亲子鉴定网站 这种对隐私、信任度要求极高的特殊领域,自己不会代码又想快速上线,稍不留神就掉进坑里。今天不扯虚的,咱们直接聊技术选型。…

作者头像 李华