Companion to the AP-Style MC & Free-Response Practice SetAP 风格选择题与自由回答题练习的解析配套
Unit 2: Selection and Iteration第 2 单元:选择与循环CSA
Multiple Choice)—— 详细解析Each item restates the prompt and choices, marks the correct letter, and gives a brief justification. Trap distractors are called out where useful.每道题重述题干与选项、标出正确答案字母,并给出简明解析;对容易混淆的干扰项(trap distractor)单独提示。
x = 0; if (x != 0 && 10/x > 0) … → result?x = 0;if (x != 0 && 10/x > 0) … → 结果?
yes is printed.被打印。no is printed.被打印。ArithmeticException is thrown.抛出 ArithmeticException。&& short-circuits: if the left operand is false, the right operand is not evaluated.
x != 0 is false (since x = 0).&& is therefore false; 10 / x is never computed.else branch runs: no is printed.&& works. This "guard-then-use" idiom is the standard way to protect against division-by-zero and null dereference.
&& 是短路求值(short-circuit evaluation):如果左操作数为 false,右操作数根本不会被计算。
x != 0 为 false(因为 x = 0)。&& 直接为 false;10 / x 永远不会被求值。else 分支执行:打印 no。&& 并不是那样工作。这种"先保护、后使用"的写法是防止除以零和空引用解引用的标准技巧。
for (int i = 5; i <= 20; i += 3) — count iterations?for (int i = 5; i <= 20; i += 3) —— 循环了多少次?
56715i that satisfy i <= 20:
i = 5, 8, 11, 14, 17, 20 — six values23, which fails the test.⌊(end − start) / step⌋ + 1 when end is achievable. Here ⌊(20 − 5) / 3⌋ + 1 = 5 + 1 = 6. Trap (A) drops the inclusive endpoint; (C) over-counts by one (classic fencepost error).
i <= 20 的 i:
i = 5, 8, 11, 14, 17, 20 —— 六个值23,不再满足条件。end 恰好可达时,迭代次数 = ⌊(end − start) / step⌋ + 1。这里 ⌊(20 − 5) / 3⌋ + 1 = 5 + 1 = 6。陷阱 (A) 漏掉了能取到的右端点;(C) 多算了一次(典型的"栅栏柱错误" fencepost error)。
Equivalent to !(a < b || c >= d)?与 !(a < b || c >= d) 等价的是?
a > b && c < da >= b && c < da >= b || c < da > b || c < d!(P || Q) ≡ !P && !Q. Negate each clause carefully:
!(a < b) ≡ a >= b (the negation of "strictly less" is "greater or equal," not "strictly greater")!(c >= d) ≡ c < d&&: a >= b && c < d|| connective (forgot the second rule of De Morgan).
De Morgan's Law):!(P || Q) ≡ !P && !Q。逐个否定每个子条件:
!(a < b) ≡ a >= b("严格小于"的否定是"大于等于",不是"严格大于")!(c >= d) ≡ c < d&& 连接:a >= b && c < d|| 换成 &&(忘了德摩根的第二条规则)。
x = 15; nested if printing A/B/C, then D always.x = 15;嵌套 if 可能打印 A/B/C,之后总会打印 D。
ADBDBCDDx = 15:
x > 10 → true, enter outer if. The else if branch is now skipped entirely.x % 2 == 0 → 15 % 2 = 1, so false. The inner else runs: print B.ifs, println("D") runs: print D + newline.BD. Trap (C) assumes else if (x > 5) also fires — but only one branch in an if / else if chain ever runs.
x = 15 推演:
x > 10 → true,进入外层 if。else if 分支现在整段被跳过。x % 2 == 0 → 15 % 2 = 1,false。内层 else 执行:打印 B。if 后,println("D") 执行:打印 D 并换行。BD。陷阱 (C) 认为 else if (x > 5) 也会执行——但在 if / else if 链里,最多只会执行其中一个分支。
n = 234; while (n > 0) { s += n % 10; n /= 10; }n = 234;while (n > 0) { s += n % 10; n /= 10; }
69234432% 10 grabs the last digit, /= 10 chops it off.
234 % 10 = 4 → s = 4; n = 23.23 % 10 = 3 → s = 7; n = 2.2 % 10 = 2 → s = 9; n = 0.n > 0 fails. Exit. Print 9.% 10 取最低位,/= 10 把它去掉。
234 % 10 = 4 → s = 4;n = 23。23 % 10 = 3 → s = 7;n = 2。2 % 10 = 2 → s = 9;n = 0。n > 0 不再满足,退出。打印 9。s = "ProgrAmming"; count chars equal to 'a'/'e'/'i'/'o'/'u'.s = "ProgrAmming";统计等于 'a'/'e'/'i'/'o'/'u' 的字符个数。
1234chars with == is case-sensitive: 'A' == 'a' is false. Walk through the string:
P r o g r A m m i n g
0 1 2 3 4 5 6 7 8 9 10
Lower-case vowels only: o at 2, i at 8. Count = 2. The uppercase A at index 5 is not matched.
Trap (C) counts the uppercase A by treating the comparison as case-insensitive. To make this counter case-insensitive you'd normalize: Character.toLowerCase(c) == 'a' || …, or call s.toLowerCase() first.
== 比较 char 是区分大小写的(case-sensitive):'A' == 'a' 为 false。逐字符走一遍:
P r o g r A m m i n g
0 1 2 3 4 5 6 7 8 9 10
只匹配小写元音:o(下标 2)、i(下标 8)。计数 = 2。下标 5 处的大写 A 不会被匹配。
陷阱 (C) 把大写 A 也算上,相当于把比较当成了不区分大小写。要做成不区分大小写,可以先归一化:Character.toLowerCase(c) == 'a' || …,或先调 s.toLowerCase()。
n=100; count=0; stop=false; while (n>0 && !stop) { count++; if (count>5) stop=true; n-=10; } println(count + " " + n);n=100; count=0; stop=false; while (n>0 && !stop) { count++; if (count>5) stop=true; n-=10; } println(count + " " + n);
5 505 606 4010 0count increments first; n decrements last. The flag stop is set inside the body, but the loop condition only re-checks after the body completes. So the iteration where count first exceeds 5 still finishes — including the n -= 10.
iter | count after stop after n after
1 | 1 false 90
2 | 2 false 80
3 | 3 false 70
4 | 4 false 60
5 | 5 false 50
6 | 6 true 40 ← count>5 fires, stop set, n still drops
loop test now: 40>0 && !true = false → exit
Print: "6 40".
Trap (A) assumes stop=true short-circuits the rest of the body (it doesn't — if/then isn't break). (B) stops after the 5th decrement and before the count increment. (D) ignores the flag entirely.
count 先自增,n 最后再减。标志变量 stop 是在循环体内部被赋值的,但循环条件要等整个循环体执行完才重新检查。所以当 count 第一次超过 5 那一轮,整个循环体仍会跑完——包括 n -= 10。
iter | count after stop after n after
1 | 1 false 90
2 | 2 false 80
3 | 3 false 70
4 | 4 false 60
5 | 5 false 50
6 | 6 true 40 ← count>5 触发,stop 置真,但 n 仍然下降
loop test now: 40>0 && !true = false → 退出
打印:"6 40"。
陷阱 (A) 错以为 stop=true 会"短路"掉循环体后续语句(不会——if/then 不是 break)。(B) 在第 5 次减法之后、count 自增之前停止。(D) 完全忽略了标志变量。
for i = 1..4 { for j = 1..i { count++; } }for i = 1..4 { for j = 1..i { count++; } }
4101624i times for each outer i. Total iterations:
i = 1: inner runs 1 timei = 2: inner runs 2 timesi = 3: inner runs 3 timesi = 4: inner runs 4 times1 + 2 + 3 + 4 = 10. (This is the triangular number n(n+1)/2.)
Trap (C) assumes a full 4 × 4 rectangle (which would happen if the inner loop were j <= 4 independent of i). (A) counts only outer iterations.
i,内层循环跑 i 次。合计:
i = 1:内层跑 1 次i = 2:内层跑 2 次i = 3:内层跑 3 次i = 4:内层跑 4 次1 + 2 + 3 + 4 = 10。(这是三角数,n(n+1)/2。)
陷阱 (C) 把它当成完整的 4 × 4 矩形(如果内层是 j <= 4 与 i 无关,才会是 16)。(A) 只数了外层的次数。
n = 1234; while (n > 0) { rev = rev*10 + n%10; n /= 10; }n = 1234;while (n > 0) { rev = rev*10 + n%10; n /= 10; }
123443214324320rev = rev * 10 + (last digit of n) "shifts" the previously-accumulated digits left and appends the next digit. Trace:
n = 1234, rev = 0rev = 0·10 + 4 = 4; n = 123rev = 4·10 + 3 = 43; n = 12rev = 43·10 + 2 = 432; n = 1rev = 432·10 + 1 = 4321; n = 0 — exit.rev = 4321. Trap (C) stops one iteration early; (D) forgets the + n%10 on the last step.
rev = rev * 10 + (n 的最低位) 把已积累的数字"整体左移一位",然后把新的最低位接到末尾。逐轮推演:
n = 1234, rev = 0rev = 0·10 + 4 = 4;n = 123rev = 4·10 + 3 = 43;n = 12rev = 43·10 + 2 = 432;n = 1rev = 432·10 + 1 = 4321;n = 0 —— 退出。rev = 4321。陷阱 (C) 提前一轮停止;(D) 漏掉了最后一步的 + n%10。
a=5; b=3; while (a>0 || b>0) { if (a>0) a--; if (b>0) b--; } println(a + " " + b);a=5; b=3; while (a>0 || b>0) { if (a>0) a--; if (b>0) b--; } println(a + " " + b);
0 00 -22 0||, so the loop runs while either variable is still positive; (2) the inner if guards prevent b from going negative once it hits 0.
iter | a after b after loop test after
1 | 4 2 4>0 || 2>0 → true
2 | 3 1 true
3 | 2 0 true
4 | 1 0 (b guard skipped) true
5 | 0 0 0>0 || 0>0 → false → exit
Once b = 0, the if (b > 0) guard skips its decrement — so b stays at 0 while a finishes counting down.
Trap (B) drops the if (b>0) guard, so b goes negative (decrements once per iteration past 0). (C) swaps the roles. (D) assumes the loop can't terminate because of the OR — but eventually both reach 0 since each iteration strictly decreases the positive variable(s).
||,所以只要任何一个变量还为正,循环就继续;(2) 内部的 if 保护(guard)让 b 在到达 0 后不会变成负数。
iter | a after b after loop test after
1 | 4 2 4>0 || 2>0 → true
2 | 3 1 true
3 | 2 0 true
4 | 1 0 (b 的保护跳过自减) true
5 | 0 0 0>0 || 0>0 → false → 退出
一旦 b = 0,if (b > 0) 保护就会跳过它的自减——所以 b 停在 0,而 a 继续往下数。
陷阱 (B) 没有 if (b>0) 保护,b 会变负(每轮在 0 之后还减一次)。(C) 把两者的角色搞反了。(D) 以为 OR 让循环停不下来——其实只要正值变量每轮严格减少,最终都会到 0。
n = 0; p = 1; while (p < 1000) { p *= 2; n++; }n = 0; p = 1; while (p < 1000) { p *= 2; n++; }
91010001024iter | p after body n
1 | 2 1
2 | 4 2
3 | 8 3
4 | 16 4
5 | 32 5
6 | 64 6
7 | 128 7
8 | 256 8
9 | 512 9
10 | 1024 10
loop test: 1024 < 1000 false → exit
Note: the test p < 1000 uses the value of p before the next iteration. p = 512 is still < 1000, so the body runs once more — that final iteration is what pushes n from 9 to 10. Trap (A) stops at p = 512 without doing the final iteration; (D) reports p instead of n.
iter | p 执行后 n
1 | 2 1
2 | 4 2
3 | 8 3
4 | 16 4
5 | 32 5
6 | 64 6
7 | 128 7
8 | 256 8
9 | 512 9
10 | 1024 10
loop test: 1024 < 1000 false → 退出
注意:循环条件 p < 1000 用的是下一次循环开始之前的 p。p = 512 仍然 < 1000,所以循环体还要再跑一次——正是这最后一次把 n 从 9 推到 10。陷阱 (A) 在 p = 512 处停下,没做最后那一轮;(D) 报的是 p 的值而不是 n。
n=100; count=0; for (int i=n; i>1; i=i/2) count++; println(count);n=100; count=0; for (int i=n; i>1; i=i/2) count++; println(count);
56750i = i / 2 is integer division, so values shrink fast and round down. The condition is i > 1 (not > 0), so the loop exits once i reaches 1.
iter | i before i after = i/2 count after
1 | 100 50 1
2 | 50 25 2
3 | 25 12 3
4 | 12 6 4
5 | 6 3 5
6 | 3 1 6
loop test: 1 > 1 false → exit
So count = 6 — equivalently, ⌊log₂(100)⌋ = 6 (since 2⁶ = 64 ≤ 100 < 128 = 2⁷).
Trap (A) stops when i reaches 2 (off-by-one on the loop bound). (C) counts the would-be iteration at i = 1. (D) mistakes the loop for one that runs n/2 times.
i = i / 2 是整除,所以值缩小很快、还会向下取整。退出条件是 i > 1(不是 > 0),所以 i 到 1 就退出。
iter | i 执行前 i 执行后 = i/2 count 执行后
1 | 100 50 1
2 | 50 25 2
3 | 25 12 3
4 | 12 6 4
5 | 6 3 5
6 | 3 1 6
loop test: 1 > 1 false → 退出
所以 count = 6 —— 等价于 ⌊log₂(100)⌋ = 6(因为 2⁶ = 64 ≤ 100 < 128 = 2⁷)。
陷阱 (A) 在 i 到 2 时就停(循环边界算错了一)。(C) 多算了 i = 1 时那一轮。(D) 把循环误当作要跑 n/2 次。
x=7; result=""; if(x>0) result+="P"; if(x%2==0) result+="E"; else if(x>5) result+="B"; if(x<10 && x>3) result+="M"; println(result);x=7; result=""; if(x>0) result+="P"; if(x%2==0) result+="E"; else if(x>5) result+="B"; if(x<10 && x>3) result+="M"; println(result);
PBMPEBMPEBPMif statements — but the second one has an attached else if. That changes its behavior: the chain runs at most one of its branches; the independent ifs each run on their own merits.
if (independent): x > 0 → 7 > 0 true → append "P". Result: "P".if/else if (chain): x % 2 == 0 → 7 % 2 = 1 false → check else if (x > 5) → true → append "B". The "E" branch is skipped and so is any sibling once one branch fires. Result: "PB".if (independent): x < 10 && x > 3 → both true → append "M". Result: "PBM"."PBM".
Trap (B) "PEBM" treats the chain as two independent ifs (both E and B branches appended). (C) ignores the final independent if. (D) sees the chain correctly but skips both its branches (forgetting else if).
if 语句——但第二条带了一个 else if。这就改变了它的行为:链式结构里最多只执行一个分支;独立的 if 各自按条件独立判断。
if(独立):x > 0 → 7 > 0 true → 追加 "P"。结果:"P"。if/else if(链式):x % 2 == 0 → 7 % 2 = 1 false → 检查 else if (x > 5) → true → 追加 "B"。"E" 分支被跳过——同一个链里一旦有分支命中,剩下的分支都不会运行。结果:"PB"。if(独立):x < 10 && x > 3 → 两个都为 true → 追加 "M"。结果:"PBM"。"PBM"。
陷阱 (B) "PEBM" 把链式当作两个独立 if(E 和 B 两个分支都追加)。(C) 忽略了最后那条独立 if。(D) 链式判断正确但跳过了两个分支(忘了 else if)。
total=0; for(i=1..6) for(j=i..6) if((i+j)%3==0) total++; println(total);total=0; for(i=1..6) for(j=i..6) if((i+j)%3==0) total++; println(total);
67812j = i (not 1), so we only count unordered pairs (i, j) with i ≤ j ≤ 6 where i + j is divisible by 3. Walk row by row:
i=1, j∈{1..6}: hits at j=2 (sum 3), j=5 (sum 6) → 2
i=2, j∈{2..6}: hits at j=4 (sum 6) → 1
i=3, j∈{3..6}: hits at j=3 (sum 6), j=6 (sum 9) → 2
i=4, j∈{4..6}: hits at j=5 (sum 9) → 1
i=5, j∈{5..6}: no hits (sums 10, 11) → 0
i=6, j=6: hits at j=6 (sum 12) → 1
total = 2+1+2+1+0+1 = 7
Print: 7.
Trap (D) 12 counts the full 6×6 grid's hits (would be right if inner ran j = 1..6 — double-counts unordered pairs and includes ordered duplicates). (A) 6 miscounts a row. (C) 8 double-counts a single pair.
j = i 而不是 1 开始,所以只数无序对 (i, j)(满足 i ≤ j ≤ 6)且 i + j 能被 3 整除的情况。逐行扫描:
i=1, j∈{1..6}: 命中 j=2(和 3)、j=5(和 6) → 2
i=2, j∈{2..6}: 命中 j=4(和 6) → 1
i=3, j∈{3..6}: 命中 j=3(和 6)、j=6(和 9) → 2
i=4, j∈{4..6}: 命中 j=5(和 9) → 1
i=5, j∈{5..6}: 没有命中(和为 10、11) → 0
i=6, j=6: 命中 j=6(和 12) → 1
total = 2+1+2+1+0+1 = 7
打印:7。
陷阱 (D) 12 是把整个 6×6 网格的命中都数上(相当于内层跑 j = 1..6——把无序对重复计数)。(A) 6 某一行数错了。(C) 8 多算了一对。
n=13; count=0; while(n>1) { if(n%2==0) n/=2; else n--; count++; } println(count);n=13; count=0; while(n>1) { if(n%2==0) n/=2; else n--; count++; } println(count);
45612n (when even) or subtracts 1 (when odd). count tracks every iteration, not just halvings. Trace from n = 13:
iter | n before branch n after count after
1 | 13 else 12 1 (odd: decrement)
2 | 12 if 6 2 (even: halve)
3 | 6 if 3 3 (even: halve)
4 | 3 else 2 4 (odd: decrement)
5 | 2 if 1 5 (even: halve)
loop test: 1 > 1 false → exit
Print: 5.
Trap (A) 4 miscounts by 1 — usually skipping the first "odd → 12" iteration. (C) 6 runs one extra loop (forgets the exit is at n = 1, not 0). (D) 12 counts the value of n after one decrement instead of count.
n 减半(偶数时),要么减 1(奇数时)。count 记录的是每一轮的次数,而不仅仅是减半的次数。从 n = 13 推演:
iter | n 执行前 分支 n 执行后 count 执行后
1 | 13 else 12 1 (奇数:减 1)
2 | 12 if 6 2 (偶数:减半)
3 | 6 if 3 3 (偶数:减半)
4 | 3 else 2 4 (奇数:减 1)
5 | 2 if 1 5 (偶数:减半)
loop test: 1 > 1 false → 退出
打印:5。
陷阱 (A) 4 数错了一次——通常是漏掉第一轮"奇数 → 12"。(C) 6 多跑了一轮(忘了退出条件是 n = 1,不是 0)。(D) 12 把一次减 1 后的 n 当作了答案,没看 count。
Greenhouse vent controller. Part (a): write ventLevel(int h), returning the vent level for hour h from the temperature recorded that hour (below 18 → 0; 18–23 → 1; 24–29 → 2; 30 or above → 3). Part (b): write ventName(String label), returning the portion of label before its first #, or the whole label when it contains no #.温室通风控制器。(a) 小题:编写 ventLevel(int h),由第 h 小时记录到的温度返回该小时的通风档位(低于 18 → 0;18–23 → 1;24–29 → 2;30 及以上 → 3)。(b) 小题:编写 ventName(String label),返回 label 中第一个 # 之前的部分;若其中不含 #,则返回整个标签。
if/else if chain on tempAt(h)对 tempAt(h) 作四分支 if/else if 判断 · (b) indexOf to locate the first #, then substring — with the not-found case handled first用 indexOf 定位第一个 #,再用 substring 截取 — 并优先处理「未找到」的情形ventLevel — ordered threshold tests(a) ventLevel — 有序阈值判断Read the temperature once, then test the thresholds in increasing order. Because an else if chain is only reached when every earlier test failed, each branch needs only its upper bound — the lower bound is already guaranteed.先读取一次温度,再按由小到大的顺序检查各阈值。由于 else if 链中的某一分支只有在前面所有判断都为假时才会到达,因此每个分支只需写出上界,下界已被前面的判断所保证。
public static int ventLevel(int h)
{
int t = tempAt(h);
if (t < 18)
{
return 0;
}
else if (t < 24)
{
return 1;
}
else if (t < 30)
{
return 2;
}
else
{
return 3;
}
}
The four bands are contiguous and cover every int, so a final bare else is correct: any temperature that reaches it is 30 or above. Negative temperatures fall into the first branch, as required.四个区间彼此相连并覆盖全部 int 取值,因此最后使用不带条件的 else 是正确的:能到达该分支的温度必然不小于 30。负温度会落入第一个分支,符合题目要求。
ventName — locate, then shorten(b) ventName — 先定位,再截取indexOf answers two questions at once: is the character there, and where. It returns the position of the first occurrence, or -1 if there is none. Ask the -1 question first, so that by the time substring runs the position is known to be valid.indexOf 一次回答两个问题:该字符是否存在,以及它在哪里。它返回首次出现的位置,若不存在则返回 -1。应先判断 -1 的情形,这样在执行 substring 时位置必然已是合法的。
public static String ventName(String label)
{
int pos = label.indexOf("#");
if (pos == -1)
{
return label;
}
return label.substring(0, pos);
}
substring(0, pos) takes the characters from index 0 up to but not including index pos. Because the end index is exclusive, the located # is excluded automatically — no - 1 is needed anywhere. The exclusive end is also what makes ventName("#auto") work: pos is 0, so substring(0, 0) returns the empty string, which is the right answer.substring(0, pos) 截取从下标 0 起、直到下标 pos 但不包含 pos 的那些字符。由于结束下标是开区间(不含),被定位到的 # 会被自动排除 — 任何地方都不需要写 - 1。正是这个开区间使 ventName("#auto") 得到正确结果:此时 pos 为 0,substring(0, 0) 返回空字符串,恰为所求。
Because indexOf returns the first occurrence, a label with several # characters needs no extra handling: ventName("ridge#manual#2") already returns "ridge".由于 indexOf 返回的是首次出现的位置,含有多个 # 的标签无需任何额外处理:ventName("ridge#manual#2") 本来就会返回 "ridge"。
| #序号 | Criterion — awarded for observable behavior评分点 — 依据可观察到的行为给分 | Pts分值 |
|---|---|---|
| A1 | Calls tempAt(h) and uses the value it returns调用 tempAt(h) 并使用其返回值 | 1 |
| A2 | Tests the temperature against the boundary values 18, 24, and 30, placing each boundary temperature itself in the higher band以 18、24、30 为边界进行判断,且使每个边界温度本身落入较高的那个档位 | 1 |
| A3 | Produces all four levels 0, 1, 2, 3, each for its own band能产生 0、1、2、3 四个档位,且各自对应正确区间 | 1 |
| A4 | Returns an int on every path, including temperatures below 18 and at or above 30在所有执行路径上都返回一个 int,包括低于 18 与不低于 30 的情形 | 1 |
| B1 | Locates the first # in label and stores or uses that position定位 label 中第一个 # 的位置,并保存或使用该位置 | 1 |
| B2 | Returns label itself when label contains no #当 label 中不含 # 时,返回 label 本身 | 1 |
| B3 | When a # is present, returns exactly the characters before it — the # itself is not included and no earlier character is dropped当存在 # 时,恰好返回其之前的那些字符 — 既不包含 # 本身,也不漏掉它之前的任何字符 | 1 |
| Total合计 | 7 |
if statements, descending thresholds, or a single return variable instead of the chain shown. In part (b), indexOf may be called with a String or a char argument, the not-found test may be written pos < 0, and a response that loops character by character to find the first # earns B1 provided it stops at the first one. B2 and B3 are judged independently: a response that handles only one of the two cases earns the criterion for the case it handles.任何满足某评分点的实现均可得该分。(a) 小题可以使用嵌套 if、按降序排列阈值,或用一个返回变量代替上述链式结构。(b) 小题中,indexOf 的实参可以是 String 也可以是 char;「未找到」的判断写成 pos < 0 同样可以;若逐字符循环查找第一个 #,只要在首次出现处停止即可得 B1。B2 与 B3 独立评判:只正确处理了其中一种情形的作答,可得该情形所对应的评分点。
if (t <= 18) pushes 18 into level 0; writing if (t > 18 && t < 24) as an independent if leaves 18 itself unhandled. The band edges are 18, 24, 30 and each edge belongs to the higher band.(a) 中区间重叠或留有空隙。写成 if (t <= 18) 会把 18 归入档位 0;把 if (t > 18 && t < 24) 写成独立的 if 则会使 18 本身无人处理。区间边界为 18、24、30,且每个边界值都属于较高的那个档位。# is always there. Calling substring(0, pos) without first testing pos throws StringIndexOutOfBoundsException on a label such as "ridge", because pos is -1. Every indexOf call has two outcomes, and the -1 one is the one that is forgotten.想当然地认为 # 一定存在。未先判断 pos 就调用 substring(0, pos),在遇到 "ridge" 这类标签时会抛出 StringIndexOutOfBoundsException,因为此时 pos 为 -1。每次 indexOf 调用都有两种结果,而被遗忘的总是 -1 那一种。substring(0, pos + 1) keeps the #; substring(0, pos - 1) drops the last character of the name. The second index of substring is exclusive, so pos alone is already correct.截取位置差一。substring(0, pos + 1) 会把 # 一并保留;substring(0, pos - 1) 则会丢掉名称的最后一个字符。substring 的第二个下标是不包含的,因此直接用 pos 本身就已正确。substring(pos) and substring(pos + 1) both return the mode suffix, not the display name.返回了截取点的另一侧。substring(pos) 与 substring(pos + 1) 返回的都是模式后缀,而非显示名称。lastIndexOf. On "ridge#manual#2" it finds the second # and returns "ridge#manual". The specification names the first occurrence, which is what indexOf gives.误用 lastIndexOf。对 "ridge#manual#2",它找到的是第二个 #,从而返回 "ridge#manual"。题目规定的是首次出现的位置,这正是 indexOf 所返回的。indexOf does not return a position when there is nothing to find, it returns -1, and code that treats -1 as an index crashes rather than misbehaving quietly. Shortening fails on the boundary — and the fix is not arithmetic but a fact about the method: substring’s end index is exclusive, so the index you found is already the index you cut at. Internalize the half-open range and the + 1 / - 1 guessing that costs so many String points simply stops.(b) 小题是字符串类自由回答题模式的缩影:先定位,再截取。这两半以不同的方式出错,故需分别评分。定位环节栽在「哨兵值」上 — 当无可查找之物时,indexOf 并不会返回某个位置,而是返回 -1;把 -1 当作下标使用的代码会直接崩溃,而不是悄悄给出错误结果。截取环节栽在「边界」上 — 而其解法并非算术调整,而是关于方法本身的一个事实:substring 的结束下标是不包含的,因此你找到的那个下标本身就是应当下刀的位置。真正理解了这一「左闭右开」区间,那种在 + 1 与 - 1 之间反复猜测、白白丢掉字符串题分数的情况便会自然消失。