洛谷 P1427 小鱼的数字游戏
洛谷 P1427 小鱼的数字游戏
恩师:hnjzsyjyj
一、题目介绍:初识小鱼的数字游戏
大家好呀!今天咱们要一起学习的是洛谷上的一道入门级编程题 ——P1427 小鱼的数字游戏。这道题虽然简单,但对于刚接触编程的小伙伴来说,是理解 “输入处理”“数据存储” 和 “循环输出” 的绝佳案例。咱们先从题目本身开始,一步步揭开它的面纱~
1.1 题目来源与定位
洛谷 P1427 是洛谷题库中的一道入门级题目,难度评级为 “入门”,非常适合刚开始学习 C++ 编程的同学练习。它主要考察的是对基本输入输出、循环结构和数组使用的掌握程度,是很多小伙伴在编程路上遇到的 “小甜点” 题目~
1.2 题目描述
咱们先来看题目到底要我们做什么(虽然题目名叫 “小鱼的数字游戏”,但其实和游戏关系不大,更像是一个数字处理任务哦):
题目要求我们实现一个程序,这个程序需要完成以下操作:
- 首先,持续接收用户输入的整数,这些整数可以是正数、负数(但题目里没说限制,不过实际输入都是整数啦);
- 当输入的整数是 “0” 时,就停止接收输入;
- 最后,把之前输入的所有整数(注意哦,不包括最后那个 “0”)按照倒序的方式输出出来,每个数之间用空格隔开。
举个例子方便大家理解:如果输入的是 “3 1 2 0”,那么输出就应该是 “2 1 3”;如果输入的是 “5 0”,输出就是 “5”;如果输入的第一个数就是 “0”,那输出就什么都没有(因为没有有效数字呀)。
是不是很清晰?这道题的核心就是 “接收输入→存储数据→倒序输出”,听起来不难,但里面藏着不少编程的基础知识点,咱们慢慢聊~
1.3 题目难度与适合人群
这道题的难度属于 “入门级”,适合刚学完 C++ 基础语法(变量、循环、数组)的小伙伴练习。如果你刚学会怎么用cin
输入、cout
输出,知道数组怎么定义和访问,那这道题对你来说刚刚好~ 就算你还不太熟练也没关系,跟着我一步步分析,保证你能学会!
二、解题思路:拆解问题的三步法
面对一道编程题,咱们可不能上来就写代码,得先想清楚 “怎么做”。就像盖房子要先画图纸,解题目也得先有思路~ 对于 “小鱼的数字游戏”,咱们可以把问题拆成三个关键步骤:
2.1 第一步:处理输入 —— 怎么接收用户的数字?
题目要求 “持续接收整数,直到输入 0 为止”,这就意味着我们需要一个循环结构来反复读入数字。那用什么循环呢?C++ 里有while
循环、for
循环,这里用while
循环最方便,因为我们不知道要输入多少个数,直到遇到 “0” 才停止,属于 “条件终止” 的循环。
那循环里要做什么呢?每次循环都要读入一个整数,然后判断这个整数是不是 “0”:
- 如果是 “0”,就跳出循环,结束输入;
- 如果不是 “0”,就把这个数存起来,留着后面用。
这一步的核心是 “循环读入 + 终止条件判断”,大家可以记一下这个思路,以后遇到 “持续输入直到某个条件停止” 的问题,都可以用这个方法~
2.2 第二步:存储数据 —— 用什么装这些数字?
输入的数字不能用完就丢呀,咱们后面还要倒序输出呢,所以得找个 “容器” 把它们存起来。在 C++ 里,最常用的 “容器” 就是数组啦!数组就像一个有很多格子的盒子,每个格子可以装一个数字,而且每个格子都有自己的编号(也就是数组下标),方便我们后续查找和使用。
那数组要定义多大呢?题目里没明确说输入的数字最多有多少个,但洛谷的入门题一般不会卡数据范围,咱们可以定义一个稍微大一点的数组,比如a[105]
(表示能装 105 个数字),足够应对这道题的所有测试数据了。
除了数组,我们还需要一个 “计数器” 来记录到底存了多少个有效数字(因为最后要倒序输出,得知道有多少个数要输出呀)。这里可以用一个变量id
,每次存一个数字,id
就加 1,这样最后id
的值就是有效数字的个数啦~
2.3 第三步:倒序输出 —— 怎么把数字反过来?
存好数字后,最后一步就是把它们倒序输出。假设我们存了id
个数字,分别存在数组的a[1]
到a[id]
(这里注意数组下标从 1 开始哦,方便计数),那倒序输出就是从最后一个数字a[id]
开始,依次输出a[id-1]
、a[id-2]
…… 直到a[1]
。
怎么实现这个过程呢?用一个for
循环就可以啦!循环的变量i
从id
开始,每次减 1,直到i
等于 1 为止,在循环里依次输出a[i]
,每个数后面加个空格,这样就完成倒序输出啦~
总结一下解题的三步法:循环输入并判断终止条件→用数组存储有效数字→反向遍历数组输出。是不是很清晰?接下来咱们就根据这个思路,看看具体的代码怎么写~
三、代码解析:逐行读懂小鱼的游戏代码
接下来就是大家最关心的代码部分啦!咱们直接用题目里给的代码,逐行分析它是怎么实现上面的解题思路的。先把代码贴出来,大家可以先眼熟一下:
cpp
#include<bits/stdc++.h>
using namespace std;
int a[105],id,x;
int main() {while(cin>>x){if(x==0)break;a[++id]=x;}for(int i=id;i>=1;i--)cout<<a[i]<<" ";return 0;
}
这短短的几行代码,就完成了小鱼的数字游戏的所有功能,咱们一步步来看~
3.1 头文件与命名空间:代码的 “开胃菜”
cpp
#include<bits/stdc++.h>
using namespace std;
这两行是 C++ 代码的 “标配开头”,咱们来解释一下:
#include<bits/stdc++.h>
:这是一个 “万能头文件”,它包含了 C++ 中几乎所有常用的标准库(比如输入输出库、数组库、字符串库等)。有了它,咱们就不用一个个写#include<iostream>
#include<vector>
这些头文件了,非常方便,尤其适合写短代码的时候用~不过要注意哦,在一些严格的比赛中可能不推荐用万能头文件,但入门练习用它完全没问题!using namespace std;
:这句话的意思是 “使用标准命名空间”。C++ 里很多常用的函数(比如cin
cout
)都在std
这个命名空间里,如果不写这句话,每次用cin
就得写成std::cin
,会很麻烦。加上这句话后,咱们就可以直接用cin
cout
啦,简化代码~
3.2 变量定义:给数字找 “家”
cpp
int a[105],id,x;
这行代码定义了三个变量,咱们分别来看:
int a[105]
:定义了一个名叫a
的数组,它是int
类型(用来存整数),大小是 105(也就是说最多能存 105 个整数)。前面咱们说过,这个数组就是用来存输入的有效数字的 “盒子”。int id
:这个变量是 “计数器”,用来记录输入的有效数字的个数。一开始id
的值是 0,每存一个有效数字,id
就加 1,最后id
的值就是数字的总个数。int x
:这个变量是 “临时容器”,用来暂时存储每次输入的整数,方便我们判断这个数是不是 0,以及要不要存到数组里。
这里有个小细节:数组a
的大小为什么是 105 呢?因为题目中输入的数字个数不会太多(洛谷的入门题一般数据范围很小),105 足够用了。如果担心不够,也可以定义成a[1005]
,多留点空间总没错~
3.3 主函数:程序的 “心脏”
C++ 程序的执行都是从main
函数开始的,咱们重点来看main
函数里的代码:
3.3.1 输入处理:while
循环读入数字
cpp
while(cin>>x){if(x==0)break;a[++id]=x;
}
这几行代码实现了 “持续输入直到遇到 0” 的功能,咱们逐句分析:
while(cin>>x)
:cin>>x
的意思是 “从输入设备(比如键盘)读一个整数到变量 x 里”,这个操作本身会返回一个 “是否读取成功” 的状态。while
循环的条件就是 “只要能成功读入 x,就继续循环”,这样就能持续接收输入啦~if(x==0)break;
:这是循环里的终止条件。当读入的 x 是 0 时,就执行break
,跳出整个while
循环,结束输入过程。这里要注意哦,0 是不存到数组里的,它只是一个 “停止信号”。a[++id]=x;
:这行是存储有效数字的关键!++id
是 “前置自增” 操作,意思是先把id
的值加 1,再用加 1 后的id
作为数组的下标。比如第一次输入有效数字时,id
原本是 0,++id
后变成 1,所以a[1] = x
,把第一个数字存到数组的第 1 个位置;第二次输入时,id
变成 2,a[2] = x
,以此类推。这样数组的a[1]
到a[id]
就依次存了所有有效数字,非常整齐~
这里有个小疑问:为什么不用id++
呢?如果写成a[id++]=x
,第一次输入时id
是 0,会先把 x 存到a[0]
,然后id
变成 1,这样数组的下标就从 0 开始了。倒序输出时也可以,但用++id
让下标从 1 开始,更符合我们 “第 1 个、第 2 个” 的计数习惯,后续输出时逻辑更清晰~
3.3.2 倒序输出:for
循环反向遍历
cpp
for(int i=id;i>=1;i--)cout<<a[i]<<" ";
这行代码实现了倒序输出的功能,咱们来拆解一下:
for
循环的格式是for(初始条件;循环条件;更新操作)
。这里初始条件是i=id
(从最后一个有效数字的下标开始);循环条件是i>=1
(只要 i 大于等于 1,就继续循环);更新操作是i--
(每次循环后 i 减 1,往前移动一个位置)。cout<<a[i]<<" "
:在循环里,每次输出数组a
中第i
个位置的数字,然后输出一个空格,这样每个数字之间就有空格隔开啦~
举个例子:如果输入的数字是 3、1、2,那么id
最后是 3,数组a[1]=3
、a[2]=1
、a[3]=2
。for
循环中 i 从 3 开始,先输出a[3]=2
,然后 i=2,输出a[2]=1
,再 i=1,输出a[1]=3
,最后结果就是 “2 1 3”,完美实现倒序输出~
3.3.3 程序结束:return 0
cpp
return 0;
这行代码表示main
函数执行结束,程序正常退出。在 C++ 里,main
函数的返回值 0 通常表示 “程序成功执行完毕”。
四、易错点分析:这些坑千万别踩!
虽然这道题看起来简单,但刚开始编程的小伙伴很容易在细节上出错。咱们来盘点一下常见的易错点,帮大家避避坑~
4.1 易错点 1:数组下标越界或存储错误
问题描述:
有的小伙伴可能会把数组的下标搞混,比如用a[id++] = x
,结果第一个数字存在a[0]
,最后倒序输出时漏掉第一个数;或者数组定义太小,比如a[10]
,但输入了 11 个数字,导致数组越界,程序崩溃。
例子:
错误代码:
cpp
int a[10],id=0,x;
while(cin>>x){if(x==0)break;a[id++]=x; // 这里用了id++,下标从0开始
}
for(int i=id;i>=1;i--)cout<<a[i]<<" "; // 循环从id开始,会漏掉a[0]
这个代码中,数组下标从 0 开始存,但输出时从id
(此时id
是数字个数)开始,而a[id]
是没有存数据的,导致输出错误。
解决办法:
- 统一数组下标从 1 开始:用
++id
作为下标,确保第一个数字存在a[1]
,最后一个在a[id]
。 - 数组定义大一点:比如
a[105]
或a[1005]
,避免数据太多存不下。
4.2 易错点 2:忘记处理 0 的输入
问题描述:
有的小伙伴可能会把 0 也存到数组里,导致输出时多了一个 0;或者没判断 0 的输入,让循环一直执行,停不下来。
例子:
错误代码:
cpp
while(cin>>x){a[++id]=x; // 没有判断x是否为0,直接存起来if(x==0)break;
}
这个代码会把 0 存到数组里,最后输出时会多一个 0,不符合题目要求(0 是终止信号,不输出)。
解决办法:
一定先判断 x 是否为 0,只有 x 不是 0 时才存到数组里,顺序不能反:if(x==0)break;
要写在a[++id]=x;
前面。
4.3 易错点 3:输出格式错误
问题描述:
题目要求每个数字之间用空格隔开,但有的小伙伴可能最后多输出一个空格,或者忘记加空格,导致格式错误。
例子:
错误代码 1(没加空格):
cpp
for(int i=id;i>=1;i--)cout<<a[i]; // 输出结果连在一起,比如“213”
错误代码 2(多一个空格):
cpp
for(int i=id;i>=1;i--)cout<<a[i]<<" ";
// 最后会多一个空格,比如“2 1 3 ”(洛谷一般不卡这个,但严格来说不规范)
解决办法:
- 必须加空格:每个数字后面加空格,确保数字之间分开(洛谷的评测系统对末尾多的空格通常不严格,不用太担心)。
- 规范写法:如果担心末尾空格,可以先输出第一个数,再循环输出 “空格 + 数字”,比如:
cpp
if(id>=1)cout<<a[id]; // 先输出最后一个数
for(int i=id-1;i>=1;i--)cout<<" "<<a[i]; // 再输出空格+前面的数
4.4 易错点 4:循环条件错误
问题描述:
输出时的for
循环条件写错,比如写成i>0
和i>=1
虽然效果一样,但有的小伙伴可能写成i<id
,导致循环次数不对。
例子:
错误代码:
cpp
for(int i=id;i>id;i--)cout<<a[i]<<" "; // 循环条件错误,循环一次都不执行
这个代码的循环条件i>id
永远不成立,导致什么都不输出。
解决办法:
输出循环的条件必须是 “从 id 开始,到 1 结束”,即i>=1
或i>0
,确保每个有效数字都被输出。
4.5 易错点 5:输入方式导致的问题
问题描述:
有的小伙伴用scanf
输入,但没处理好输入失败的情况;或者用cin
时没注意输入格式,导致循环异常。
例子:
错误代码:
cpp
int x;
while(x!=0){ // 没先输入x就判断,x的初始值不确定cin>>x;a[++id]=x;
}
这个代码中,第一次判断x!=0
时,x 还没被赋值,值是不确定的,可能导致循环一开始就不执行。
解决办法:
用while(cin>>x)
的形式,先输入 x,再判断是否为 0,确保循环逻辑正确。
五、优化方向:让代码更优雅
咱们现在的代码已经能正确解决问题了,但还可以从几个角度优化,让代码更简洁、更灵活~
5.1 优化 1:用vector
替代数组(动态存储)
咱们现在用的是固定大小的数组,虽然够用,但如果输入的数字特别多,数组大小可能不够。C++ 中的vector
(向量)是一种动态数组,可以自动调整大小,更灵活~
优化代码:
cpp
#include<bits/stdc++.h>
using namespace std;
vector<int> a; // 定义一个vector,不用指定大小
int x;
int main() {while(cin>>x){if(x==0)break;a.push_back(x); // 把x添加到vector末尾}for(int i=a.size()-1;i>=0;i--)cout<<a[i]<<" "; // vector下标从0开始return 0;
}
优点:
- 不用提前定义大小,输入多少数字都能存下,不用担心越界。
a.size()
直接获取数字个数,不用id
计数器,更简洁。
5.2 优化 2:用栈实现倒序(先进后出)
倒序输出的本质是 “先进后出”,而栈(stack
)这种数据结构正好满足这个特性:先存进去的数字后出来。
优化代码:
cpp
#include<bits/stdc++.h>
using namespace std;
stack<int> s; // 定义一个栈
int x;
int main() {while(cin>>x){if(x==0)break;s.push(x); // 把数字压入栈}while(!s.empty()){ // 栈不为空时cout<<s.top()<<" "; // 输出栈顶元素(最后进去的数字)s.pop(); // 弹出栈顶元素}return 0;
}
优点:
- 不用手动记录数字个数,栈的
empty()
和top()
方法直接帮我们处理倒序。 - 更符合 “倒序” 的逻辑思维,代码可读性强。
5.3 优化 3:输入输出效率提升
对于大量输入输出的情况,cin
和cout
的效率可能不够高,这时候可以用scanf
和printf
,或者给cin
/cout
提速。
提速代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int a[105],id,x;
int main() {ios::sync_with_stdio(false); // 关闭cin与stdio的同步,提速cin.tie(0); // 解除cin和cout的绑定,进一步提速while(cin>>x){if(x==0)break;a[++id]=x;}for(int i=id;i>=1;i--)cout<<a[i]<<" ";return 0;
}
优点:
ios::sync_with_stdio(false);
和cin.tie(0);
能显著提高cin
/cout
的输入输出速度,在数据量大时很有用。
六、拓展练习:从小鱼游戏到更多倒序问题
学会了小鱼的数字游戏,咱们可以趁热打铁,看看类似的 “倒序” 问题怎么解决,巩固一下知识点~
6.1 拓展练习 1:倒序输出字符串
题目:
输入一个字符串,倒序输出这个字符串(比如输入 “abc”,输出 “cba”)。
思路:
- 用
string
存储字符串; - 从字符串的最后一个字符开始,依次输出到第一个字符。
参考代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int main() {string s;cin>>s;for(int i=s.size()-1;i>=0;i--)cout<<s[i];return 0;
}
6.2 拓展练习 2:倒序后计算平均值
题目:
输入若干整数,以 0 结束,倒序输出这些整数后,再输出它们的平均值(保留一位小数)。
思路:
- 用数组存数字,记录个数
id
; - 倒序输出数组;
- 计算数组中所有数字的和,除以
id
得到平均值。
参考代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int a[105],id,x;
double sum=0;
int main() {while(cin>>x){if(x==0)break;a[++id]=x;sum+=x; // 累加求和}// 倒序输出for(int i=id;i>=1;i--)cout<<a[i]<<" ";// 输出平均值cout<<endl<<fixed<<setprecision(1)<<sum/id;return 0;
}
6.3 拓展练习 3:多组测试数据
题目:
有多组测试数据,每组数据都按 “小鱼的数字游戏” 规则输入(以 0 结束),每组数据输出一行倒序结果,直到输入 “-1” 表示所有测试结束。
思路:
- 用外层循环包裹每组数据的处理;
- 每组数据开始前重置
id
和数组,避免和上一组数据混淆。
参考代码:
cpp
#include<bits/stdc++.h>
using namespace std;
int a[105],id,x;
int main() {while(true){ // 外层循环处理多组数据id=0; // 重置计数器cin>>x;if(x==-1)break; // 所有测试结束if(x==0){ // 本组没有有效数字cout<<endl;continue;}a[++id]=x;while(cin>>x){ // 处理本组其他数字if(x==0)break;a[++id]=x;}// 倒序输出本组结果for(int i=id;i>=1;i--)cout<<a[i]<<" ";cout<<endl;}return 0;
}
七、总结:小鱼游戏带给我们的编程启示
通过学习洛谷 P1427 小鱼的数字游戏,咱们不仅学会了一道题的解法,更掌握了编程中的几个核心知识点:
7.1 循环结构的灵活使用
while
循环适合 “条件不确定” 的持续操作,比如 “输入直到某个条件停止”;for
循环适合 “已知范围” 的遍历,比如 “从 id 到 1 的倒序输出”。掌握不同循环的适用场景,能让代码更简洁高效。
7.2 数组的基础应用
数组是存储多个同类型数据的基础工具,通过下标访问和遍历数组,是处理批量数据的核心技能。记住数组下标从 0 还是 1 开始,对后续操作至关重要。
7.3 问题拆解的思维
面对任何编程问题,都可以像拆 “小鱼的游戏” 一样,把复杂问题拆成 “输入→存储→输出” 等小步骤,一步步解决。这种 “拆解思维” 能帮我们理清思路,避免无从下手。
7.4 细节决定成败
处理输入时的终止条件、数组下标的正确使用、输出格式的规范,这些细节虽然小,但直接影响程序是否正确。编程中一定要注重细节,多调试、多测试。
八、PPT 展示建议
如果用这篇内容做 PPT,建议按以下结构分页,重点突出,方便讲解:
- 封面页:标题 “洛谷 P1427 小鱼的数字游戏”+ 简单背景图
- 题目介绍页:题目描述 + 示例输入输出(用大字体展示)
- 解题思路页:三步法流程图(输入→存储→输出)
- 代码框架页:代码整体结构,标出核心部分
- 逐行解析页:分 3-4 页,每页解析 1-2 行代码,重点讲
while
循环和for
循环 - 易错点页:用表格列出易错点和解决办法
- 优化方向页:对比不同方法的代码(数组 vs vector vs 栈)
- 拓展练习页:列出拓展题目,简要说明思路
- 总结页:核心知识点提炼,鼓励练习
这样的 PPT 结构清晰,重点突出,能让听众(尤其是初学者)更容易跟上思路,理解这道题的解法和背后的知识点~
九、最后:编程路上的小鼓励
编程学习就像小鱼的数字游戏,看似简单的问题里藏着扎实的基础。每一道题都是一次积累,每一次调试都是一次成长。不要怕犯错,多动手写代码,多思考为什么,你会发现编程越来越有趣~ 下次遇到类似的问题,相信你一定能轻松解决!加油呀!