资讯动态

Java黑皮书5.9题:不用数组如何找前两名学生?

发布时间:2026/10/5 11:33:58 来源:尧图企业网站定制
其实这道题很有意思它出现在《Java语言程序设计》也就是大家常说的Java黑皮书第5章的课后题里第5.9题。在这一章读者刚刚学完循环、方法这些基础结构这道题的要求也很朴素写一个程序让用户输入学生人数然后逐个输入学生的姓名和分数最后把分数最高的前两个学生名字和成绩打印出来。很多初学者第一次看到这道题第一反应是“这不就是个排序吗把所有人的成绩排一下序取前两个不就行了”。这个思路本身没错但如果只停留在“用排序去解”这个层面在这道题里其实走偏了。第5章还没讲到数组更没讲到排序算法这道题真正的考察点不是“你会不会Arrays.sort”而是“你在没有数组的情况下能不能用循环、变量和条件判断解决一个需要“记忆”的问题”。这个差异恰恰是初学者和有点经验的人之间的一道分水岭。这篇文章我就从这道题出发把我自己带学生、包括当初刷黑皮书时的完整思考过程拆开讲一遍。不仅给你能直接跑的代码更要把这题背后的设计意图、几种不同的解法思路、以及在实际考试或面试里常见的变体和坑一次性讲透。好了别急着打开IDE写代码先跟我把这道题的“骨相”摸清楚。1. 内容整体设计与思路拆解1.1 为什么这道题不先排序再取前两名先把需求翻译成最直白的话数据是“一个名字对应一个分数”要拿到“分数最高的两个人”。很多人第一反应是排序因为现实生活里查“前几名”本来就是排个序的事。但放到这道题的语境里有一个关键前提——数据是“边输入边处理”的你是先知道张三考了90分然后才知道李四考了95分再然后王五考了88分。你不可能等所有数据都齐了再统一排序因为题目压根没让你先把数据存起来。当然你会说我可以先把所有名字和分数存到数组里最后再排序。这个思路对不对对但那是后面章节的做法。在第5章这个位置教师和教材都没有把你引向“数组 排序”这个方案。这道题出现的时机决定了它的核心训练目标是用两个变量去实时维护“当前最高”和“当前第二高”。这本质上是一种在线算法online algorithm的思想——数据一条条到达你不需要存全部数据只需要保留对你最关键的几个状态量。1.2 双变量实时更新的核心逻辑这道题之所以能不用数组就解决是因为“前两名”这个需求只依赖两个位置的数据第一名的信息第二名的信息。每进来一个新学生只需要做三件事第一如果这个新学生的分数比当前第一名还高那么原来的第一名就要“退位”成第二名新学生成为第一名第二如果这个新学生分数没有超过第一名但比当前第二名高那他就只是把第二名顶掉第三如果他连第二名都比不过那就什么都不用做。这个逻辑你仔细想一下其实就是“冠军挑战赛”的流程。冠军先站在台上挑战者一个一个上台打赢了冠军就取代他冠军被打败之后也不是直接淘汰而是变成第二名接着站在台上。这个比喻比“排序”更贴近这道题的本质。这里面最容易被忽略的细节是当第一名被替换时原第一名必须“降级”到第二名。我见过非常多初学的同学只写了“新学生打败第一名就让他当第一名”结果第二名永远空着或者第二名莫名其妙就丢了。这就是因为脑子里没有把一个完整的状态转移图画出来。1.3 考虑边界情况并列分数怎么算黑皮书原题里没有特别说明并列分数怎么处理但在真实考题和面试里这经常是一个追问点。比如有两个学生的分数都是95都高于第三名那“前两名”该是谁这取决于题意。如果是“找两个最高分的学生”那通常会认为并列第一的两个人就是前两名哪怕只有两个并列的95分第三个人是94分也只输出这两个95分的人。如果你的代码用的是而不是那么后一个95分不会被当作新第一名但会通过“大于第二名”的判断进入第二名最终你还是能输出两个95分。这其实是比较合理的默认行为。但如果题目要说“分数不能并列”或者“并列时按输入顺序取先来的”那处理方式又要微调。这些细节恰恰是实际写代码时最该先跟需求方确认的地方。放在做题的语境下我会建议代码同时处理好“大于第一名”和“大于第二名”两种情况因为这样无论并列怎么定义你的输出都不会太离谱。2. 核心细节解析与实操要点2.1 用Scanner逐条读入数据时的注意事项这道题一定会用到Scanner。最基础的使用套路是先import java.util.Scanner然后在main方法里创建Scanner对象。接下来有一个特别关键的细节题目要求先输入学生个数然后循环输入姓名和分数。这里我遇到过一个很典型的初学者问题用next()还是nextLine()读姓名如果学生姓名是英文单词比如 “John”那next()和nextLine()看着没啥区别。但黑皮书很多读者是国内学生测试数据经常是中文名字。中文名用next()也能读因为中文名中间没有空格。可如果哪个学生的名字是“Zhang San”这种带空格的next()读到空格就停了nextLine()才能读完整。这看起来是一个很小的实现细节但一旦踩了坑排查起来很痛苦。因为程序不会报错它只是安静地给了你一个“看起来不对但说不清哪里不对”的结果。比如你输入了 “Zhang San” 作为名字next()只读到 “Zhang”然后成绩读的是 “San”程序立刻抛InputMismatchException因为 “San” 不是数字。你第一反应可能是“成绩那里出错了”其实根子在名字读取方式上。我的建议是如果名字固定是单个单词用next()最省事因为它不吞换行如果用nextLine()读名字那在读完学生个数之后必须先单独调一次nextLine()把上一行残留的换行符吃掉否则第一个学生的名字会被读成一个空字符串。这是我让每个学生动手写这道题时必然强调一遍的经典坑。2.2 初始化第一名和第二名的变量这道题里怎么给“当前第一名分数”和“当前第二名分数”设初值也是一个很讲究的点。最常见的做法是把第一名分数初始化为 -1第二名分数初始化为 -1。因为分数最低是0分如果初始化成0那么一个真实考了0分的学生进来时系统就没法区分“这个0分是真实成绩”还是“还没输入任何学生”的状态。把第一名初始化成 -1 之后第一个学生的分数无论多少只要非负都一定大于 -1所以他必然成为第一名。这个逻辑跑起来非常顺。如果你把初值设成0那么当第一个学生考了0分你判断“新分数 当前第一名”就不成立第一名永远是那个虚假的0分最后输出姓名时还会出现空指针或者空白输出。这种问题不是因为代码运行逻辑复杂就是因为初值选得不严谨。有些同学喜欢把第一名分数的初值设成Integer.MIN_VALUE这样更通用任何整数成绩进来都能正确更新。这也是一种好习惯。不过对这道题而言-1 已经足够了而且更好理解。我这里多说一句真实工程里“哨兵初值”的选择非常影响代码健壮性大家可以从这道题开始养成习惯凡是“当前最值”类变量初值一定要想清楚。2.3 姓名的同步更新不能忘分数有第一名和第二名对应的姓名也必须同时维护两个变量firstName和secondName。很多初学者写着写着只更新了分数忘记更新姓名最后输出的时候发现分数很合理但名字对不上。这是一个典型的“状态未同步”问题。要避免这个问题我推荐一个习惯把“更新第一名”和“更新第二名”写成两个独立的 if 块而不是用if-else if链。因为当一个新学生分数超过原来的第一名时这个学生既触发了“更新第一名”的逻辑也要求“原来的第一名降为第二名”。如果你用if - else if第二个逻辑根本不会执行原第一名的名字和分数就没有被妥善安置到第二名的位置。所以这个场景里两个 if 块是必要的。第一个 if 用来处理新学生成为第一名第二个 if 用来处理新学生成为第二名只有新学生没有超过第一名的前提下才判断。严格来说第二个 if 可以写成一个else if块挂在第一个 if 下面这样当新学生刷新纪录时就不会再进入第二名判断因为原第一名已经被降级为第二名了逻辑上不重复。但要非常小心挂在if下面的else if分支条件和执行顺序。在我的教学经验里让初学者先把这两个 if 分开写更容易想清楚状态变化。2.4 循环边界到底循环几次题目要求输入学生个数假设记为 n那么循环体要执行 n 次读取 n 个学生的姓名和分数。新手最容易犯的一个错误是循环条件写成i n导致多循环了一次不得不额外输入一个不存在的学生。还有一种错误是循环从1开始计数读入第1个到第n个这本身没错但要警惕后面代码里用下标索引时搞混。用for (int i 0; i n; i)是最省心的写法因为从0开始是程序员默认的习惯和数组下标一致。如果你今后接触数组版本的解法这种写法迁移起来零成本。当然你用for (int i 1; i n; i)也能跑出正确结果只要你自己心里清楚循环体执行了多少次就行。3. 实操过程与核心环节实现3.1 用最朴素的变量版实现先把第一版代码写出来。这是最贴合教材第5章知识范围的版本没有数组没有集合就是普通变量、循环和条件判断。import java.util.Scanner; public class FindTopTwoStudents { public static void main(String[] args) { Scanner input new Scanner(System.in); // 1. 读取学生人数 System.out.print(请输入学生的人数: ); int numberOfStudents input.nextInt(); input.nextLine(); // 吃掉数字后面的换行符 // 2. 初始化最高分和第二高分-1表示还没有任何有效成绩 String firstName ; String secondName ; double firstScore -1; double secondScore -1; // 3. 循环读入每个学生的姓名和分数 for (int i 0; i numberOfStudents; i) { System.out.print(请输入第 (i 1) 个学生的姓名: ); String name input.nextLine(); System.out.print(请输入第 (i 1) 个学生的分数: ); double score input.nextDouble(); input.nextLine(); // 吃掉分数后面的换行符 // 4. 判断是否刷新第一名 if (score firstScore) { // 原来的第一名降级为第二名 secondName firstName; secondScore firstScore; // 新学生成为第一名 firstName name; firstScore score; } else if (score secondScore) { // 新学生没有超过第一名但超过了第二名 secondName name; secondScore score; } } // 5. 输出结果 System.out.println(最高分的学生是 firstName 分数是 firstScore); System.out.println(第二高分的学生是 secondName 分数是 secondScore); input.close(); } }这段代码的运行逻辑非常直白。我用一个表格把这几个核心变量在不同时刻的取值变化列出来帮助大家脑内模拟一遍| 输入顺序 | 姓名 | 分数 | 操作 | 第一名姓名 | 第一名分数 | 第二名姓名 | 第二名分数 | | --- | --- | --- | --- | --- | --- | | 1 | Alice | 85 | 成为第一名 | Alice | 85 | (空) | -1 | | 2 | Bob | 92 | 超过AliceAlice降为第二 | Bob | 92 | Alice | 85 | | 3 | Cathy | 90 | 超过Alice但没超过Bob | Bob | 92 | Cathy | 90 |看完这个表你是不是觉得整个过程特别像一个擂台赛每次新选手上台先把擂主和挑战者比较一轮再决定怎么调整名次。这种“维护冠军和亚军”的思路跟“最后统一排序”的思路相比省去了存储所有数据的麻烦而且内存占用是常量级的 O(1)非常轻量。3.2 用 if-else 链和独立 if 两种写法的对比我在前面提到过最后判断部分可以用两个独立 if也可以用一个 if 加一个 else if。两种写法在这个具体场景里最终结果是一样的但对初学者来说理解成本差很多。独立 if 的写法是这样的if (score firstScore) { secondName firstName; secondScore firstScore; firstName name; firstScore score; } if (score firstScore score secondScore) { secondName name; secondScore score; }注意第二个 if 里我写了score firstScore这个条件是为了避免一种特殊情况新学生的分数和第一名相等。如果分数相等且用了他可能先进第一个 if把原第一名降级自己当第一名然后第二个 if 又判断score secondScore此时 secondScore 已经被更新成原第一名的分数所以新学生的分数等于 secondScore不会再触发。这倒是正好不会出问题。但如果你用if (score secondScore)而不加score firstScore那么在触发第一个 if 的情况下第二个 if 可能把刚降级的“原第一名”再次覆盖成新学生自己虽然结果不会错但逻辑上很绕。再看 if-else 链if (score firstScore) { secondName firstName; secondScore firstScore; firstName name; firstScore score; } else if (score secondScore) { secondName name; secondScore score; }这种写法更紧凑第二个条件的判断天然建立在“新学生没有超过第一名”这个前提下不需要手动写score firstScore。我希望大家真正理解这段代码而不是抄下来就完事。理解的关键是新学生分数超过第一名时原第一名降级如果没超过第一名再看是否超过第二名。两条路径互斥所以用else if非常合适。3.3 数组版解法给你一个更通用的视角虽然第5章还没教数组但从后面的章节往回看这道题完全可以用数组 遍历实现。我用它来对比说明“变量版”到底省了什么。import java.util.Scanner; public class FindTopTwoWithArray { public static void main(String[] args) { Scanner input new Scanner(System.in); System.out.print(请输入学生的人数: ); int n input.nextInt(); input.nextLine(); String[] names new String[n]; double[] scores new double[n]; for (int i 0; i n; i) { System.out.print(请输入第 (i 1) 个学生的姓名: ); names[i] input.nextLine(); System.out.print(请输入第 (i 1) 个学生的分数: ); scores[i] input.nextDouble(); input.nextLine(); } int firstIndex 0; int secondIndex -1; for (int i 1; i n; i) { if (scores[i] scores[firstIndex]) { secondIndex firstIndex; firstIndex i; } else if (secondIndex -1 || scores[i] scores[secondIndex]) { secondIndex i; } } System.out.println(最高分的学生是 names[firstIndex] 分数是 scores[firstIndex]); System.out.println(第二高分的学生是 names[secondIndex] 分数是 scores[secondIndex]); input.close(); } }数组版最大的好处是数据先全部存下来了你之后可以做更多事比如计算平均分、按名次输出全部学生、甚至重新排序。坏处是你要额外开一片内存空间。如果数据量是几百万条变量版的优势就体现出来了——它不用把几百万条姓名都装在内存里只保留两个候选人的信息就行。这两种思路没有绝对的高下之分关键是理解它们的适用场景。变量版适合“流式数据 只求前几名”的场景数组版适合“数据已全量存在 后续还有多方操作”的场景。题目本意是让你掌握前者但如果你能同时理解后者说明你对这个问题的理解已经超过“应付作业”的层面了。3.4 完整测试用例与输出演示我推荐你在运行程序时至少准备三组测试数据。第一组是常规数据用于快速验证逻辑没毛病。第二组是边界数据比如只有一个学生时第二名输出什么。第三组是并列分数数据观察程序怎么处理。测试用例1常规数据请输入学生的人数: 3 请输入第 1 个学生的姓名: Alice 请输入第 1 个学生的分数: 85 请输入第 2 个学生的姓名: Bob 请输入第 2 个学生的分数: 92 请输入第 3 个学生的姓名: Cathy 请输入第 3 个学生的分数: 90输出最高分的学生是 Bob分数是 92.0 第二高分的学生是 Cathy分数是 90.0测试用例2只有一名学生请输入学生的人数: 1 请输入第 1 个学生的姓名: Solo 请输入第 1 个学生的分数: 99输出最高分的学生是 Solo分数是 99.0 第二高分的学生是 分数是 -1.0这个输出其实不太优雅。只有一个学生时“第二高分”压根不存在。如果你希望程序在这种场景下给出更友好的提示可以在最后输出前加一个判断。这是一个很好的程序健壮性训练点。测试用例3并列第一请输入学生的人数: 3 请输入第 1 个学生的姓名: Amy 请输入第 1 个学生的分数: 88 请输入第 2 个学生的姓名: Ben 请输入第 2 个学生的分数: 88 请输入第 3 个学生的姓名: Cindy 请输入第 3 个学生的分数: 87这里有两种可能输出。如果用score firstScore那么第二个88分不会顶掉Amy但会进入else if (score secondScore)因为 secondScore 初始是 -1所以Ben成为第二名。输出是最高分的学生是 Amy分数是 88.0 第二高分的学生是 Ben分数是 88.0如果用score firstScore条件那么第二个88分进来时会先顶掉AmyAmy降为第二然后输出可能是最高分的学生是 Ben分数是 88.0 第二高分的学生是 Amy分数是 88.0并列时到底谁是第一谁是第二取决于你的比较条件里有没有等号。这一点在考试里非常容易被拿来设坑我建议你把两种都亲手跑一遍体会一下差别。4. 常见问题与排查技巧实录4.1 浮点数比较的坑题目里分数是什么类型自然语言里说“分数”时可能是整数也可能是小数。黑皮书原题没有明确限定我为了通用性用了double。但浮点数比较时有一个隐患如果某两个学生的分数在二进制小数里无限循环比如 0.1 0.2 这类直接用比较一般没问题因为我们要的是大小关系不是相等判断。可如果要求“分数相等”时的精确处理浮点数就不太保险了。如果你明确知道分数一定是整数比如满分100的整数分建议直接用int。这能省掉一堆浮点数精度带来的小麻烦也让代码更清爽。如果是0到100带小数的成绩比如 89.5那用double也完全没问题因为比较大小不涉及精确相等。4.2 nextInt 和 nextLine 混用导致读取顺序错乱这是Scanner相关的最经典问题。当你调用nextInt()读入学生人数后输入缓冲区里其实还有一个换行符。如果你接下来直接调用nextLine()去读第一个学生的姓名那么nextLine()会立刻把那个残留的换行符消费掉返回一个空字符串。结果就是第一个学生的姓名变成了空白程序随后读到的分数其实是原本的姓名引发类型不匹配异常。解决方案有三种我按推荐程度排序。第一种每次nextInt()或nextDouble()后紧跟一次input.nextLine()把换行符吞掉然后再读下一行的字符串。第二种全部数据都用nextLine()读取然后自己用Double.parseDouble()转成数字。第三种混用时保持“先读字符串再读数字”或“先读数字再吞换行”的固定节奏不要一会儿这样一会儿那样。在我的教学经验里第三种“保持节奏”是最重要的。很多同学在循环里读姓名和分数读着读着顺序就乱了就是因为每一轮里字符串和数字的读取顺序不一致。4.3 只有一个学生时第二名输出异常前面已经演示过当numberOfStudents 1时第二名的姓名是空字符串分数是 -1。这在严格意义上不算bug但用户体验很差。一个负责任的小程序应该考虑到这种情况做点特殊处理。我建议在循环结束后加一个判断如果secondScore还是 -1说明没有第二名就只输出第一名并提示“没有足够多的学生来产生第二名”。这样程序面对真实世界的各种输入时表现得更稳。4.4 进入循环立即输入导致的换行符问题还有一种情况很隐蔽用户在输入学生人数时在数字后面顺手敲了一个空格或者敲完数字又按了Tab。nextInt()会跳过这些空白字符去读整数所以一般没事。但如果用户在数字后面直接输入了名字比如输入3 Alice那么nextInt()读走3nextLine()读走 Alice这个学生名字就变成了“ Alice”开头带空格。带空格的名字看起来还能用但如果你把名字拼接到字符串里做比较或输出偶尔会出一些很诡异的错。要规避这个一个笨办法就是在提示语里明确告诉用户“每个信息请单独一行输入”。另一个办法是读入数字后吞掉当前这一行的所有残留内容而不是只吞一个换行符。用input.nextLine()就能做到因为它的语义是“读完这一整行剩下的部分”。4.5 遇到中文乱码的问题如果你用的是 macOS 或 Linux 终端并且代码里直接写了中文提示语运行时光标和输出可能出现乱码。这跟 Java 本身没关系是控制台编码和文件编码不一致导致的。解决方法是确保源文件用 UTF-8 保存终端也用 UTF-8 编码Windows 上老版本的 CMD 默认 GBK需要执行chcp 65001切到 UTF-8。更省事的做法是在 IDE比如 IntelliJ IDEA 或 Eclipse里直接运行IDE 通常会处理好编码问题。向用户提问的提示语用中文还是英文纯属个人风格。黑皮书原版题目用英文很多参考答案也保留了英文提示。你用中文提示代码里全是中文字符串也只影响你自己看不影响判题系统。我建议不妨保留英文提示语比如Enter student name: 这样代码在跨平台运行时更不容易遇到编码烦恼也顺便练一下专业英文。4.6 循环变量是否必须从0开始这个问题我在带学生时被问过很多次。有人习惯for (int i 1; i numberOfStudents; i)有人习惯for (int i 0; i numberOfStudents; i)。两者在这道题里没本质差异但输出提示语时要注意别把“第 0 个学生”这个说法暴露给用户。我的写法是循环变量从0开始但显示给用户时用(i 1)这样用户看到的是“第1个、第2个”。如果你用i从1开始其实也可以但后续如果你把这段代码改成数组版下标访问names[i]就会越界——因为数组下标从0开始。所以从长远来看循环变量从0开始更符合 Java 社区的通用习惯。5. 变体拓展与应用场景分析5.1 改为找出最低的前两个学生这道题稍微一改马上就变成另一道常见面试题找出分数最低的前两个学生。核心思路完全一样只是把比较符号反过来新学生的分数比当前最低分更小就让他当最低分原最低分降为第二低如果没超过最低分但比第二低还低就顶掉第二低。这个变体训练的是“抽象能力”。当你理解了“维护前两名”的本质是“维护前K个极值”那么最大/最小只是符号方向的问题。建议你自己动手把代码改一遍改完会发现思路完全通用。5.2 找出前N名学生引入数组或集合如果题目从“前两个”变成“前N个”那两个变量就不够用了得用数组或者ArrayList。思路是在线维护一个“前K名”的榜单每来一个分数就跟榜单最后一名比如果超过就插入榜单长度超过K就淘汰末尾。这是从“前2名”到“前K名”的自然延伸也是很多算法题比如找Top K大元素的原型。虽然第5章不需要掌握这种扩展但我建议你在学完数组之后回头用这道题当素材练一练会很有收获。5.3 同时记录多个并列最高分有些更复杂的变体要求如果并列第一的人有好几个都要列出来。那代码逻辑就更复杂了你需要维护一个“最高分数列表”。这种情况通常先找最高分再遍历所有人收集等于最高分的名字。这要求你先把所有数据存下来纯变量版就不够用了。这些变体不是黑皮书这道题的要求但理解它们能让你更清楚题目本身只是训练基础真正的应用场景千变万化核心能力在于“把现实需求转换为状态更新规则”的抽象能力。5.4 应用到真实项目实时榜单、游戏计分、评选投票这种“实时维护Top K”的思路在真实项目中非常常见。比如一场在线编程竞赛的实时排行榜每有一个选手提交成绩系统就要更新一遍榜单但不需要把几千人的成绩都重排一遍只需要跟榜单末尾比一比就行。再比如直播间的礼物榜单、游戏里的最高分纪录、评选活动的票数统计本质都是同一套逻辑。尤其适合这种变量版代码的场景是数据来源是流式的比如从网络端口一句一句接收学生成绩每接收一条就更新名次。如果每次都攒着、最后统一处理内存和延迟都可能成为问题。当然真实系统一般用 Redis 的 Sorted Set 这类现成数据结构来做但原理依然是“维护有序榜单”。6. 调试技巧与逐行模拟6.1 用一个很简单的输入集手推一遍先把学生数量设为2输入2 Alice 80 Bob 90第一轮第1个学生 Alice分数80。firstScore 从 -1 变成80firstName 变成 Alice。secondScore 还是 -1。第二轮第2个学生 Bob分数90。score firstScore成立所以 secondName firstName AlicesecondScore firstScore 80然后 firstName BobfirstScore 90。最后输出 Bob 90 和 Alice 80。这里你能直观看到循环结束时两个名额里存储的是“最终的前两名”而且顺序是第一名在前。这一步手推如果没问题说明你对变量更新的时机已经掌握了。6.2 用IDE的Debug功能观察变量值光靠脑子模拟数据量一旦变大就容易出错。我强烈建议你用 IDE 的调试功能在每次更新第一名和第二名的代码行处打上断点。运行到断点时查看firstName、firstScore、secondName、secondScore这四个变量的值确认“状态转移”是否符合预期。这个方法对初学者特别有效。很多同学“觉得自己逻辑对了但程序输出不对”其实都是某个状态在某个分支里没有按预期更新。Debug 模式下走一遍问题往往瞬间就暴露了。6.3 在代码里临时加打印输出除了断点调试你还可以在关键位置临时加System.out.println打印每一轮循环结束后的第一、第二名状态。这个方法稍微老土但胜在直观、不依赖IDE在考试或者一些限制严格的环境里也能用。确认程序正确后再把调试用的打印语句删掉。有人觉得打印语句太“新手”其实在很多大型项目里必要的日志输出反而是问题排查的重要手段。我建议你把“临时打印辅助调试”当成一个常规操作不需要有心理负担。7. 关于命名规范和代码可读性的建议最后聊一个容易被忽略但很重要的话题命名。黑皮书前面的章节不会特别强调命名规范但从一开始养成好习惯后面会省很多事。用firstName表示“最高分学生姓名”用firstScore表示“最高分数”清晰直观。有些同学图省事把变量命名为a、b、max行数少了但代码可读性直线下降。等你回头再看自己的代码或者交给别人 review完全看不懂a和b分别代表什么。我推荐这套命名方案firstName第一名学生姓名secondName第二名学生姓名firstScore第一名的分数secondScore第二名的分数如果将来用数组可以沿用names和scores配合firstIndex、secondIndex表示下标。这样读代码的人一眼就能知道每个变量是什么意思。这个过程里还有一个隐藏的学习点变量名同时也是你思维模型的外显。如果你能清晰地给变量命名说明你对“谁是第一名、谁是第二名、怎么存储”已经有清晰认识如果你连变量名都起不出来那多半是对逻辑还处于模糊状态。所以我常说写代码的第一步不是动手敲键盘而是先在脑子里把角色定义出来。再补充一点关于代码格式化的小事。黑皮书配套的代码风格比较传统花括号换行。而 IDEA 默认的风格是花括号跟在语句后面。这两种风格不影响功能只影响观感。你选一种顺眼的从头坚持到尾即可。团队协作时以团队的规范为准个人练习时怎么舒服怎么来但一定要统一。我在实际带项目时遇到很多学生交上来的作业功能都实现了但代码缩进混乱、命名随意导致后期调试效率极低。代码首先是给人看的其次才是给机器执行。这个观念越早建立对你写大型项目的帮助越大。等你把这道题吃透可以试试把它改成方法版——把“找前两名”的核心逻辑抽成一个独立方法接收一个学生数组返回前两名信息。这是从“面向过程”到“面向对象”思维转变的起点也是黑皮书后续章节的主线。我当年就是从这道题开始不知不觉从“写代码完成作业”变成了“用代码解决问题”。希望这篇文章也能让你少走一些我当时走过的弯路。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑