例题给全套:题面、样例、参考代码、以及一步步的讲解。先自己读题想一遍, 再看代码,最后对着讲解核一遍自己想的对不对。
练习只给题面、样例和一句提示,答案是折叠起来的 —— 请先自己写一版跑通了再展开。写不出来也别直接看答案,先回速查那一组找找。
每份代码都能直接点运行:输入框里已经填好了样例输入, 把它改一改再跑,是这一页最值钱的用法。
先把地基摆一遍
★ 先记一条通用的读法:只看第一条错误,改完再编译一遍。C++ 的编译错误会「雪崩」—— 前面一个分号引发后面几十条,而后面那几十条全是噪声。一次只改最上面那一条。
★★ 第二条:报错的行号常常指到出错行的下一行。 因为编译器是读到下一行才发现「上一句没写完」。看到行号先往上看一行。
⚠ 第三条,也是最要紧的:能编译过不等于是对的。 越界、溢出、没初始化、递归太深 —— 这四样编译器一句话都不会说,它们要么在运行时崩,要么安静地给出一个错答案。下面最后三条讲的就是它们。
例题(2 道)
每道都给全套:题面 → 样例 → 参考代码 → 一步步的讲解。
例题 ① 漏了分号
下面这段程序编译不过。请读懂报错,改好它。
改好之后它该做的事是:第一行读一个整数 n,接下来 n 行每行两个整数 x、y,对每一行输出 x + y。
n(1 ≤ n ≤ 100);接下来 n 行,每行两个整数。n 行,每行一个整数。输入
3 1 2 -1 5 0 0
输出
3 4 0
#include <bits/stdc++.h>
using namespace std;
struct Point {
int x, y;
}
int main() {
Point p = {1, 2}
cout << p.x + p.y << "\n";
return 0;
}b1.cpp:6:2: error: expected ‘;’ after struct definition
6 | }
| ^
| ;
b1.cpp: In function ‘int main()’:
b1.cpp:10:5: error: expected ‘,’ or ‘;’ before ‘cout’
10 | cout << p.x + p.y << "\n";
| ^~~~两处都漏了分号,而编译器报的行号一处都不在漏分号的那一行上。先自己指出:这两条报错分别在说哪一行的问题?
// 例题 ①「漏了分号」改好之后的版本 —— 报错了怎么读//// 两处漏分号,而编译器报的行号**一处都不在出错的那一行上**:// struct 那个报在花括号所在行(第 6 行),语句那个报在**下一行**(第 10 行)。// ⇒ 看到 expected ';' 一律先往上看一行。
#include <bits/stdc++.h>using namespace std;
struct Point { int x, y;}; // ← struct / class 的花括号后面**必须**有分号
int main() { int n; cin >> n; for (int i = 0; i < n; i++) { Point p; cin >> p.x >> p.y; // ← 每条语句结尾的分号,同理 cout << p.x + p.y << "\n"; } return 0;}点「运行 ▶」看结果
- 第一条
expected ';' after struct definition指在第 6 行(struct的右花括号那行)——这一条倒是指对了:struct/class的花括号后面必须有分号。⚠ 一般的花括号(if、for、函数)结尾不用分号,唯独这两个要,所以特别容易漏。 - 第二条
expected ',' or ';' before 'cout'指在第 10 行,可真正漏分号的是第 9 行(Point p = {1, 2})。⇒ 因为编译器是读到下一行才发现「上一句还没写完」。 - ★★ 所以这一组的第二条通用读法是:报错的行号常常指到出错行的下一行,看到行号先往上看一行。
- ★ 第一条通用读法是:只看第一条错误,改完再编译一遍。C++ 的编译错误会「雪崩」—— 前面一个分号能引发后面几十条,而那几十条全是噪声。
改好之后顺手记住这个手势:写完 struct 的右花括号,手指自动敲一个分号。
用到的写法:expected ';' before ...
例题 ② max 的两个参数类型必须一样
下面这段程序编译不过。请读懂报错,改好它。
改好之后它该做的事是:读一个 int n 和一个 long long m,第一行输出两者中较大的,第二行输出较小的。
n(|n| ≤ 10⁹)和 m(|m| ≤ 10¹⁸)。输入
3 1000000000
输出
1000000000 3
3 和 1000000000 里较大的是后者,较小的是 3。#include <bits/stdc++.h>
using namespace std;
int main() {
int n = 3;
long long total = 1000000000LL;
cout << max(n, total) << "\n";
return 0;
}b2.cpp: In function ‘int main()’:
b2.cpp:7:16: error: no matching function for call to ‘max(int&, long long int&)’
7 | cout << max(n, total) << "\n";
| ~~~^~~~~~~~~~
b2.cpp:7:16: note: there are 4 candidates
… (后面跟着一长串 candidate: 和 template argument deduction/substitution failed)no matching function for call to … 的意思是「函数名对,但参数对不上」。报错里那个 max(int&, long long int&) 已经把原因写出来了 —— 两个参数的类型不一样。
// 例题 ②「max 的两个参数类型必须一样」改好之后的版本 —— 报错了怎么读//// no matching function for call to 'max(int&, long long int&)'// = 函数名对,但**参数对不上**。// max 要求两个参数类型完全一样,int 和 long long 混着传就找不到匹配的版本。// 两条改法都行:把 int 那个转过去,或者直接指定类型 max<long long>(n, m)。
#include <bits/stdc++.h>using namespace std;
int main() { int n; long long m; cin >> n >> m;
cout << max((long long)n, m) << "\n"; // 改法一:把两边凑成同一个类型 cout << min<long long>(n, m) << "\n"; // 改法二:直接告诉它按哪个类型比 return 0;}点「运行 ▶」看结果
max要求两个参数的类型完全一样。int和long long混着传,它推不出该用哪一个类型,于是「找不到匹配的版本」。- ⚠ 报错后面会跟一长串
candidate:—— 那是它试过的所有版本。不用逐条看,直接回去核对自己传的参数类型就行。 - 改法一:把两边凑成同一个类型 ——
max((long long)n, m)。 - 改法二:直接告诉它按哪个类型比 ——
max<long long>(n, m)。 - ⚠ 顺带一个同类的:
max(1, 2.0)也不行(int 和 double)。⇒ 看到no matching function,第一反应就是去数参数的个数和类型。
这是新手最常撞的一条编译错误,而它的根子不是 max 有问题,是 C++ 不会替你猜「这两个不同类型的东西该按哪个来比」。
练习(3 道)
答案折叠着,先自己写一版。
练习 ① 'sum' was not declared in this scope
下面这段程序编译不过。请读懂报错,改好它。
改好之后它该做的事是:读一个正整数 n,输出 1 ~ n 里所有偶数的和。
n(1 ≤ n ≤ 10⁵)。输入
10
输出
30
2 + 4 + 6 + 8 + 10 = 30。#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
int sum = 0;
sum += i;
}
cout << sum << "\n";
return 0;
}b3.cpp: In function ‘int main()’:
b3.cpp:11:13: error: ‘sum’ was not declared in this scope
11 | cout << sum << "\n";
| ^~~这句话的意思是「这个名字编译器不认识」,按可能性从高到低查三件事:① 拼错了;② 作用域不对(它定义在某对花括号里面,出了那对括号就不存在了);③ 忘了 #include(本书一律用万能头,所以基本不是这个原因)。⚠ 这道题是第 ② 种 —— 而且改对之后还有一个 bug 要顺手修掉(题目要的是偶数的和)。
⚠ 还有一种最气人的「not declared」:在函数 A 里调用了写在它下面的函数 B。C++ 是从上往下读的 ⇒ 要么把 B 挪到 A 前面,要么在最上面先写一行声明。
练习 ② Segmentation fault
下面这段程序编译得干干净净(-Wall 一条警告都没有),可一跑就崩。请找出原因并改好。
它该做的事是:读一个正整数 n,把 a[1] … a[n] 依次填成 1 … n,然后输出 a[n]。
n(1 ≤ n ≤ 10⁶)。输入
1000000
输出
1000000
n = 10⁶ 时答案就是 1000000 —— 前提是数组装得下。#include <bits/stdc++.h>
using namespace std;
const int N = 10;
int a[N];
int main() {
int n;
cin >> n;
for (int i = 1; i <= n; i++) a[i] = i;
cout << a[n] << "\n";
return 0;
}$ g++ -std=c++17 -O2 -Wall -o b6 b6.cpp
(没有任何输出 —— 零错误、零警告)
$ echo 1000000 | ./b6
退出码 = 139退出码 139 就是 Segmentation fault(139 = 128 + 11,11 是 SIGSEGV)。⚠ 越界访问编译期一句话都不会说 —— 查的顺序是:① 数组开够了没;② 下标算出来会不会是负的;③ 递归有没有出口、会不会太深。
⚠ 最快的定位法:把可疑的 a[i] 临时换成 a.at(i)(vector 才有),越界会当场抛异常并告诉你下标是多少,而不是让程序在别处崩。查完记得换回去,at 更慢。★ 而这道题真正该留下的习惯是:数组开多大,是照题面的上限写的,不是照样例写的。
练习 ③ 第一组对,第二组起全错
下面这段程序编译得过、也不崩,样例的第一行还是对的 —— 但第二行开始就错了。请找出原因并改好。
它该做的事是:第一行一个整数 T,接下来 T 组数据,每组第一个数是 n,后面跟着 n 个整数。对每组输出这 n 个数的和。
T(1 ≤ T ≤ 100);接下来 T 组,每组一个 n(1 ≤ n ≤ 1000)和 n 个整数(|aᵢ| ≤ 10⁹)。T 行,每行一个整数。输入
2 3 1 2 3 2 10 20
输出
6 30
1+2+3 = 6,第二组 10+20 = 30。#include <bits/stdc++.h>
using namespace std;
int main() {
int T;
cin >> T;
long long sum = 0;
while (T--) {
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int x;
cin >> x;
sum += x;
}
cout << sum << "\n";
}
return 0;
}$ printf '2\n3 1 2 3\n2 10 20\n' | ./b7
6
36第一组对、第二组起全错 —— 这个症状本身就是答案。问一句:那个攒着的量,是在哪儿被清干净的?
这是多组数据的题里排第一的 bug,而它还有一个更隐蔽的版本:全局数组只在程序启动时清过一次,多组数据之间必须自己清。⇒ 判据很直白:凡是「攒着的东西」(累加器、访问标记、邻接表),都要问一句它在哪儿归零。
← 上一组:零碎但天天用的那几样 · 回 C++ 速查 · 这是最后一组