{ }codelper

学习 › C#

进阶 · 22

常见错误与错误信息

如何阅读错误信息,以及各种语言都会犯的错误:差一位、= 与 ==、整数除法、null、下标越界。

有些错误在各种语言里都会出现。
把它们和阅读错误信息的方法一起记住。
下面的例子在使用下标和做除法之前,先检查下标是否在范围内、除数是否为 0。

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    }}
stdin
5
3 1 4 1 5
3
2 7 -1
7 0
stdout
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 的 undefined 那样不报错却给出错误的值。

练习题