进阶 · 22
常见错误与错误信息
如何阅读错误信息,以及各种语言都会犯的错误:差一位、= 与 ==、整数除法、null、下标越界。
有些错误在各种语言里都会出现。
把它们和阅读错误信息的方法一起记住。
下面的例子在使用下标和做除法之前,先检查下标是否在范围内、除数是否为 0。
def get(a, i): if 0 <= i < len(a): # a[-1]도 막아요(Python에서는 마지막 값) return a[i] return None def safe_div(x, y): if y == 0: return None return x // y n = int(input())a = list(map(int, input().split()))input()for i in map(int, input().split()): v = get(a, i) if v is None: # if not v: 로 쓰면 0도 걸려요 print(f"a[{i}]: out of range") else: print(f"a[{i}] = {v}")x, y = map(int, input().split())q = safe_div(x, y)print(f"{x} / {y}: cannot divide" if q is None else f"{x} / {y} = {q}")print(f"average {sum(a) / n:.1f}")const tok = require("fs").readFileSync(0, "utf8").trim().split(/\s+/).map(Number);let p = 0;const n = tok[p++];const a = tok.slice(p, (p += n));const q = tok[p++];const idx = tok.slice(p, (p += q));const [x, y] = tok.slice(p, p + 2); // a[7]은 오류 없이 undefined라서 직접 검사해요const get = (arr, i) => (i >= 0 && i < arr.length ? arr[i] : null);const safeDiv = (u, v) => (v === 0 ? null : Math.trunc(u / v)); // u / 0은 Infinity for (const i of idx) { const v = get(a, i); console.log(v === null ? `a[${i}]: out of range` : `a[${i}] = ${v}`);}const d = safeDiv(x, y);console.log(d === null ? `${x} / ${y}: cannot divide` : `${x} / ${y} = ${d}`);let sum = 0;for (let i = 0; i < n; i++) sum += a[i]; // <= n 이면 undefined를 더해 NaNconsole.log(`average ${(sum / n).toFixed(1)}`);import { readFileSync } from "node:fs";const tok: number[] = readFileSync(0, "utf8").trim().split(/\s+/).map(Number);let p = 0;const n = tok[p++];const a = tok.slice(p, (p += n));const q = tok[p++];const idx = tok.slice(p, (p += q));const [x, y] = tok.slice(p, p + 2); // a[7]은 오류 없이 undefined라서 직접 검사해요const get = (arr: number[], i: number): number | null => (i >= 0 && i < arr.length ? arr[i] : null);const safeDiv = (u: number, v: number): number | null => (v === 0 ? null : Math.trunc(u / v)); for (const i of idx) { const v = get(a, i); console.log(v === null ? `a[${i}]: out of range` : `a[${i}] = ${v}`);}const d = safeDiv(x, y);console.log(d === null ? `${x} / ${y}: cannot divide` : `${x} / ${y} = ${d}`);let sum = 0;for (let i = 0; i < n; i++) sum += a[i]; // <= n 이면 undefined를 더해 NaNconsole.log(`average ${(sum / n).toFixed(1)}`);import java.util.Scanner; public class Main { // int에는 null을 못 담아서 Integer로 돌려줘요 static Integer get(int[] a, int i) { if (i >= 0 && i < a.length) return a[i]; // 안 막으면 ArrayIndexOutOfBoundsException return null; } static Integer safeDiv(int x, int y) { if (y == 0) return null; // 안 막으면 ArithmeticException return x / y; } public static void main(String[] args) { Scanner in = new Scanner(System.in); int n = in.nextInt(); int[] a = new int[n]; int sum = 0; for (int i = 0; i < n; i++) { // i <= n 이면 한 칸 넘어가요 a[i] = in.nextInt(); sum += a[i]; } int q = in.nextInt(); for (int t = 0; t < q; t++) { int i = in.nextInt(); Integer v = get(a, i); System.out.println(v == null ? "a[" + i + "]: out of range" : "a[" + i + "] = " + v); } int x = in.nextInt(), y = in.nextInt(); Integer d = safeDiv(x, y); System.out.println(x + " / " + y + (d == null ? ": cannot divide" : " = " + d)); System.out.println(String.format("average %.1f", (double) sum / n)); // sum / n은 2 }}using System;using System.Linq; class Program{ // int?는 '수 또는 null'이에요 static int? Get(int[] a, int i) => i >= 0 && i < a.Length ? a[i] : null; // 안 막으면 IndexOutOfRangeException static int? SafeDiv(int x, int y) => y == 0 ? null : x / y; // 안 막으면 DivideByZeroException static void Main() { int n = int.Parse(Console.ReadLine()); int[] a = Console.ReadLine().Split(' ').Select(int.Parse).ToArray(); Console.ReadLine(); foreach (int i in Console.ReadLine().Split(' ').Select(int.Parse)) { int? v = Get(a, i); Console.WriteLine(v is null ? $"a[{i}]: out of range" : $"a[{i}] = {v}"); } int[] xy = Console.ReadLine().Split(' ').Select(int.Parse).ToArray(); int? d = SafeDiv(xy[0], xy[1]); Console.WriteLine($"{xy[0]} / {xy[1]}" + (d is null ? ": cannot divide" : $" = {d}")); Console.WriteLine($"average {((double)a.Sum() / n).ToString("F1")}"); // a.Sum() / n은 2 }}#include <cstdio>#include <iostream>#include <optional>#include <vector> // optional<int>는 '수 또는 없음'이에요std::optional<int> get(const std::vector<int>& a, int i) { if (i < 0 || i >= (int)a.size()) return std::nullopt; // a[i]는 검사 안 함, a.at(i)는 예외 return a[i];} std::optional<int> safeDiv(int x, int y) { if (y == 0) return std::nullopt; // 0으로 나누면 정의되지 않은 동작 return x / y;} int main() { int n, q, x, y; std::cin >> n; std::vector<int> a(n); long long sum = 0; // = 0 을 빼면 쓰레기 값에서 시작해요 for (int& v : a) std::cin >> v, sum += v; std::cin >> q; for (int t = 0; t < q; t++) { int i; std::cin >> i; if (auto v = get(a, i)) std::cout << "a[" << i << "] = " << *v << '\n'; else std::cout << "a[" << i << "]: out of range\n"; } std::cin >> x >> y; if (auto d = safeDiv(x, y)) std::cout << x << " / " << y << " = " << *d << '\n'; else std::cout << x << " / " << y << ": cannot divide\n"; std::printf("average %.1f\n", (double)sum / n); // sum / n은 2}#include <stdbool.h>#include <stdio.h> /* C에는 null 정수가 없어서 성공 여부(bool)와 값(*out)을 따로 돌려줘요 */bool get(const int *a, int n, int i, int *out) { if (i < 0 || i >= n) return false; /* 안 막으면 정의되지 않은 동작 */ *out = a[i]; return true;} int main(void) { int n, q, x, y, v; int a[100]; int sum = 0; /* = 0 을 빼면 쓰레기 값에서 시작해요 */ scanf("%d", &n); if (n < 1 || n > 100) return 1; /* 배열 크기를 넘지 않게 */ for (int i = 0; i < n; i++) { /* i <= n 이면 한 칸 넘어가요 */ scanf("%d", &a[i]); sum += a[i]; } scanf("%d", &q); for (int t = 0; t < q; t++) { int i; scanf("%d", &i); if (get(a, n, i, &v)) printf("a[%d] = %d\n", i, v); else printf("a[%d]: out of range\n", i); } scanf("%d %d", &x, &y); if (y == 0) printf("%d / %d: cannot divide\n", x, y); /* 0으로 나누면 정의되지 않은 동작 */ else printf("%d / %d = %d\n", x, y, x / y); printf("average %.1f\n", (double)sum / n); /* sum / n은 2 */ return 0;}get(a, i){ 如果 (i >= 0 && i < a.数量) { 返回 a[i]; } 返回 空;} n = 控制台.读行().转数字();a = 控制台.读数字();q = 控制台.读行().转数字();遍历 (i 在 控制台.读数字()){ v = get(a, i); 如果 (v == 空) { 控制台.输出("a[" + i + "]: out of range"); } 否则 { 控制台.输出("a[" + i + "] = " + v); }}xy = 控制台.读数字();如果 (xy[1] == 0){ 控制台.输出(xy[0] + " / " + xy[1] + ": cannot divide");}否则{ 控制台.输出(xy[0] + " / " + xy[1] + " = " + 数学.商(xy[0], xy[1]));}합 = 0;循环 (k = 0; k < a.数量; k++){ 합 += a[k];}控制台.输出("average " + 字符.保留小数(합 / n, 1));5 3 1 4 1 5 3 2 7 -1 7 0
a[2] = 4 a[7]: out of range a[-1]: out of range 7 / 0: cannot divide average 2.8
要点
- 在错误信息里找错误名、原因和行号。
Python 要从最后一行读起。 - 需要编译的语言(C、C++、Java、C#)即使报了很多错误,也要从第一个错误改起。
后面的往往是被它连带出来的。 - 下标从 0 到
n - 1,所以循环要写i < n。
写成i <= n就多走一格,这就是差一位(off-by-one)。 =是赋值,==是比较。- 做除法之前先确认除数不是 0,使用之前先确认值不是空值(null)。
- 创建变量时就给它初值(
int sum = 0;)。
各语言的写法
| Python | 最后一行是错误名和原因,比如 IndexError: list index out of range,上面一行是行号。除以 0 报 ZeroDivisionError,用 None 计算报 TypeError,不存在的名字报 NameError。a[-1] 不是错误而是最后一个元素;if not v: 会把 0 也算进去(用 v is None 判断) |
|---|---|
| JavaScript | 下标越界不报错,返回 undefined;7 / 0 是 Infinity。空值有 null 和 undefined,undefined + 1 是 NaN,null + 1 是 1,悄悄算错。比较用 ===("1" == 1 是真) |
| TypeScript | 和 JavaScript 一样 |
| Java | 第一行是 ArrayIndexOutOfBoundsException: Index 7 out of bounds for length 5,下面的 at Main.main(Main.java:12) 是行号。除以 0 抛出 ArithmeticException,null 抛出 NullPointerException(int 装不下,要用 Integer)。没有初值的变量会编译失败:"might not have been initialized" |
| C# | 越界抛出 IndexOutOfRangeException,除以 0 抛出 DivideByZeroException。空值用 int?。没有初值的变量是编译错误 CS0165 |
| C++ | a[i] 不做检查,越界是未定义行为;a.at(i) 会抛出 std::out_of_range。整数除以 0 也是未定义行为(通常会崩溃)。 空值用 std::optional。int sum; 的初始值是垃圾值 |
| C | 数组不做越界检查,属于未定义行为:乱七八糟的值、覆盖别的变量或崩溃。 除以 0 和未初始化的局部变量与 C++ 相同 |
| Craykon | 越界时提示“列表只有 0~4 号”并停止。7 / 0 是 Infinity,数学.商(7, 0) 提示“不能除以 0”。空 + 1 提示“不能用空值计算”(和字符串连接时显示为 빈값),不存在的名字提示“还没有创建” |
常见错误
i <= n 多走了一格C · C++ · Java · C# · JavaScript · TypeScript
int sum = 0;
for (int i = 0; i <= n; i++)
sum += a[i];→ Java 和 C# 抛出越界异常,JavaScript 加上 undefined 得到 NaN,C 则悄悄加上一个垃圾值
写成 i < n。
平均值变成了整数Java · C# · C · C++
int sum = 7, n = 2;
double avg = sum / n;→ avg 是 3.0:7 / 2 是整数除法,小数部分先被丢掉了
先把一边转成浮点数:(double)sum / n。
把 == 写成了 =C · C++ · JavaScript
if (x = 5) {
puts("five");
}→ 不报错:5 被存进 x,条件永远为真(gcc -Wall 会给出警告)
比较用 ==。
Java 和 C# 会编译失败,Python 报 SyntaxError(3.10 起提示 "Maybe you meant '=='"),Craykon 也会当作语法错误拦下。
各语言的差异
程序因错误而停下,其实是件好事。
真正可怕的是像 C/C++ 的越界访问、JavaScript 的
程序因错误而停下,其实是件好事。
真正可怕的是像 C/C++ 的越界访问、JavaScript 的
undefined 那样不报错却给出错误的值。