C++ 竞赛入门

测试与调试

用小数据和关键状态把错误变成可观察的问题。

测试与调试

本节学会什么

  • 为程序准备样例、最小、边界、重复和零答案输入
  • 用少量 cout 观察循环变量和关键状态
  • 用手算和反例定位常见的下标错误

为什么需要它

WA 很少会直接告诉你哪一行错了。可靠的做法不是反复猜,而是准备一个足够小的输入,让程序的每一步都能被看见。小数据能让你同时手算和输出关键变量,程序与手算第一次不同的地方,就是最值得检查的地方。

最小可运行示例

下面程序读入 n 个整数并求和。为了检查循环是否走对,在本地暂时输出下标和当前和:

#include <iostream>

int main() {
    int n;
    int a[100];
    int sum = 0;
    std::cin >> n;

    for (int i = 0; i < n; i += 1) {
        std::cin >> a[i];
        sum += a[i];
        std::cout << "i=" << i << ", sum=" << sum << '\n';
    }

    std::cout << "answer=" << sum << '\n';
    return 0;
}

输入 32 5 1 时,手算的累计和应为 2、7、8。确认一致后,删除循环中的调试 cout,只保留题目需要的答案输出。

关键过程与易错点

  • 固定检查五类数据:题目样例、最小输入、边界输入、包含重复值的输入、答案为 0 的输入。不是每题都有所有类型,但应主动寻找。
  • n 个元素的下标是 0n - 1。循环条件写成 i <= n 会多执行一次;当它访问 a[i] 时,最后一次已经访问了错误的 a[n]
  • 先只输出一个或两个关键变量,例如 i、累计和、当前最优值。输出所有变量通常只会制造更多噪声。
  • 调试输出会改变正式输出格式。提交前应删掉或注释掉所有临时 cout,只留下题目要求的内容。
  • 编译时保留 -Wall -Wextra。它能发现一部分未使用变量、可疑比较等问题,但不能替你证明算法正确。

动手检查

对于“读入 n 个数”的循环,先不访问数组,只分别写出 i < ni <= ni 的取值。取 n = 3,你应得到前者为 0、1、2,后者为 0、1、2、3

再给下面的错误条件构造反例:把处理 n 个元素的循环从 i < n 写成 i <= n。选择最小的 n,手算哪一次循环的下标不属于这 n 个元素;不要依赖越界访问“刚好没有崩溃”的偶然结果。

立即练习

难度 题目 题解 训练目标
基础 洛谷 P1428:小鱼比可爱 题解 先用全相等数据和递减数据检查两层循环的边界。
巩固 洛谷 P1046:陶陶摘苹果 题解 为计数程序构造“全部能摘、全部不能摘、恰好相等”三类输入。