{ }codelper

学习 › C++

进阶 · 22

常见错误与错误信息

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

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

#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}
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。

把 == 写成了 =C · C++ · JavaScript

if (x = 5) {
    puts("five");
}

→ 不报错:5 被存进 x,条件永远为真(gcc -Wall 会给出警告)

比较用 ==。
Java 和 C# 会编译失败,Python 报 SyntaxError(3.10 起提示 "Maybe you meant '=='"),Craykon 也会当作语法错误拦下。

平均值变成了整数Java · C# · C · C++

int sum = 7, n = 2;
double avg = sum / n;

→ avg 是 3.0:7 / 2 是整数除法,小数部分先被丢掉了

先把一边转成浮点数:(double)sum / n。

各语言的差异
程序因错误而停下,其实是件好事。
真正可怕的是像 C/C++ 的越界访问、JavaScript 的 undefined 那样不报错却给出错误的值。

练习题