C++ Assert 调试指南
简介
assert 是 C++ 标准库提供的一个强大的调试工具,它可以在运行时检查程序中的假设条件。在 Codeforces 竞赛编程中,合理使用 assert 可以帮助我们快速发现和定位 bug,提高代码的正确性和调试效率。
基本用法
1. 头文件包含
cpp
copy
1
2
#include <cassert> // 或 #include <assert.h>
2. 基本语法
cpp
copy
1
2
assert(condition);
如果 condition 为 false,程序会立即终止并显示:
- 断言失败的文件名和行号
- 失败的条件表达式
3. 简单示例
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <iostream>
#include <vector>
#include <cassert>
int main() {
std::vector<int> a = {1, 2, 3, 4, 5};
int n = a.size();
// 检查数组索引是否越界
int i = 3;
assert(i >= 0 && i < n); // 正常执行
i = 10;
assert(i >= 0 && i < n); // 会触发断言失败
std::cout << a[i] << std::endl;
return 0;
}
竞赛编程中的实用场景
1. 数组和容器访问
cpp
copy
1
2
3
4
5
6
7
8
9
10
void processArray(const std::vector<int>& arr, int index) {
// 检查索引合法性
assert(index >= 0 && index < (int)arr.size());
// 检查数组不为空
assert(!arr.empty());
std::cout << arr[index] << std::endl;
}
2. 数学计算验证
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int gcd(int a, int b) {
assert(a > 0 && b > 0); // 确保 a, b 都是正数
while (b != 0) {
int r = a % b;
a = b;
b = r;
}
assert(a > 0); // 确保 gcd > 0
return a;
}
bool isPrime(int n) {
assert(n >= 2); // 确保输入至少是 2
if (n == 2) return true;
if (n % 2 == 0) return false;
for (int i = 3; i * i <= n; i += 2) {
if (n % i == 0) return false;
}
return true;
}
3. 排序算法验证
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
void bubbleSort(std::vector<int>& arr) {
int n = arr.size();
for (int i = 0; i < n; i++) {
for (int j = 0; j < n - i - 1; j++) {
if (arr[j] > arr[j + 1]) {
std::swap(arr[j], arr[j + 1]);
}
}
}
// 验证排序结果
for (int i = 0; i < n - 1; i++) {
assert(arr[i] <= arr[i + 1]);
}
}
4. 二分搜索验证
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int binarySearch(const std::vector<int>& arr, int target) {
assert(std::is_sorted(arr.begin(), arr.end())); // 确保数组已排序
int left = 0, right = (int)arr.size() - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
assert(mid >= left && mid <= right); // 确保 mid 在范围内
if (arr[mid] == target) return mid;
else if (arr[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
5. 图算法验证
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void dfs(int u, const std::vector<std::vector<int>>& graph,
std::vector<bool>& visited) {
int n = graph.size();
assert(u >= 0 && u < n); // 检查节点合法性
assert(visited.size() == n); // 检查 visited 数组大小
visited[u] = true;
for (int v : graph[u]) {
assert(v >= 0 && v < n); // 检查邻居节点合法性
if (!visited[v]) {
dfs(v, graph, visited);
}
}
}
自定义断言宏
1. 带消息的 assert
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#define ASSERT_MSG(condition, message) \
do { \
if (!(condition)) { \
std::cerr << "Assertion failed: " << #condition << std::endl; \
std::cerr << "Message: " << message << std::endl; \
std::cerr << "File: " << __FILE__ << ", Line: " << __LINE__ << std::endl; \
std::abort(); \
} \
} while(0)
// 使用示例
void divide(int a, int b) {
ASSERT_MSG(b != 0, "除数不能为零");
std::cout << a / b << std::endl;
}
2. 调试模式专用的 assert
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#ifdef DEBUG
#define DEBUG_ASSERT(condition) assert(condition)
#define DEBUG_PRINT(x) std::cerr << x << std::endl
#else
#define DEBUG_ASSERT(condition) do {} while(0)
#define DEBUG_PRINT(x) do {} while(0)
#endif
// 使用示例
void debugFunction(const std::vector<int>& arr) {
DEBUG_ASSERT(!arr.empty());
DEBUG_PRINT("Array size: " << arr.size());
}
3. 范围检查宏
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#define ASSERT_IN_RANGE(value, min, max) \
assert((value) >= (min) && (value) <= (max))
#define ASSERT_INDEX(arr, index) \
assert((index) >= 0 && (index) < (int)(arr).size())
// 使用示例
void processVector(const std::vector<int>& vec) {
ASSERT_IN_RANGE(vec.size(), 0, 1000000);
for (int i = 0; i < (int)vec.size(); i++) {
ASSERT_INDEX(vec, i);
// 处理 vec[i]
}
}
Codeforces 竞赛最佳实践
1. 数据结构验证
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class SegmentTree {
private:
int n;
std::vector<int> tree;
public:
SegmentTree(int size) : n(size), tree(4 * size) {
assert(size > 0);
assert(size <= 200000); // CF 常见数据范围
}
void update(int pos, int value) {
assert(pos >= 0 && pos < n);
updateImpl(1, 0, n - 1, pos, value);
}
int query(int l, int r) {
assert(l >= 0 && r >= l && r < n);
return queryImpl(1, 0, n - 1, l, r);
}
private:
void updateImpl(int node, int start, int end, int pos, int value) {
assert(node < (int)tree.size());
assert(start <= end);
assert(pos >= start && pos <= end);
// 更新逻辑
}
int queryImpl(int node, int start, int end, int l, int r) {
assert(node < (int)tree.size());
assert(start <= end);
// 查询逻辑
return 0;
}
};
2. 算法不变量检查
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
class DSU {
private:
std::vector<int> parent;
std::vector<int> size;
public:
DSU(int n) : parent(n), size(n, 1) {
assert(n > 0);
for (int i = 0; i < n; i++) {
parent[i] = i;
assert(size[i] == 1);
}
}
int find(int x) {
assert(x >= 0 && x < (int)parent.size());
if (parent[x] != x) {
parent[x] = find(parent[x]);
}
return parent[x];
}
void unite(int a, int b) {
assert(a >= 0 && a < (int)parent.size());
assert(b >= 0 && b < (int)parent.size());
a = find(a);
b = find(b);
if (a != b) {
assert(size[a] > 0 && size[b] > 0);
if (size[a] < size[b]) std::swap(a, b);
parent[b] = a;
size[a] += size[b];
assert(size[a] > 0); // 合并后大小应该增加
}
}
int getSize(int x) {
assert(x >= 0 && x < (int)parent.size());
return size[find(x)];
}
};
3. 边界条件检查
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void solve() {
int n, m;
std::cin >> n >> m;
// 检查输入范围
assert(n >= 1 && n <= 200000); // CF 典型范围
assert(m >= 1 && m <= 200000);
std::vector<std::vector<int>> graph(n);
for (int i = 0; i < m; i++) {
int u, v;
std::cin >> u >> v;
u--; v--; // 转换为 0-based
assert(u >= 0 && u < n);
assert(v >= 0 && v < n);
assert(u != v); // 通常竞赛图没有自环
graph[u].push_back(v);
graph[v].push_back(u);
}
// 验证图的基本性质
for (int i = 0; i < n; i++) {
assert(graph[i].size() <= n - 1); // 度数不超过 n-1
}
}
编译控制
1. 禁用断言
在提交代码时,为了避免运行时开销,可以禁用断言:
bash
copy
1
2
g++ -DNDEBUG your_code.cpp -o solution
或者在代码开头定义:
cpp
copy
1
2
3
#define NDEBUG
#include <cassert>
2. 条件编译
cpp
copy
1
2
3
4
5
6
7
8
#ifndef NDEBUG
// 调试模式代码
#define CHECK(condition) assert(condition)
#else
// 发布模式代码
#define CHECK(condition) do {} while(0)
#endif
3. 竞赛提交模板
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>
using namespace std;
#ifdef LOCAL
#define DEBUG
#define CHECK(condition) assert(condition)
#define LOG(x) cerr << x << endl
#else
#define CHECK(condition) do {} while(0)
#define LOG(x) do {} while(0)
#endif
int main() {
ios::sync_with_stdio(false);
cin.tie(nullptr);
#ifdef DEBUG
freopen("input.txt", "r", stdin);
freopen("output.txt", "w", stdout);
#endif
// 你的代码
return 0;
}
常见错误和陷阱
1. 副作用检查
cpp
copy
1
2
3
4
5
6
7
// 危险:i++ 会被多次执行
assert(i++ < n); // 不要这样!assert 可能被禁用
// 正确方式
assert(i < n);
i++;
2. 浮点数比较
cpp
copy
1
2
3
4
5
6
7
// 危险:浮点数精度问题
double x = 0.1 + 0.2;
assert(x == 0.3); // 可能失败!
// 正确方式
assert(std::abs(x - 0.3) < 1e-9);
3. 复杂条件检查
cpp
copy
1
2
3
4
5
6
7
8
9
// 过于复杂的断言难以理解
assert(a > b && c < d && e != f && g >= h);
// 更好的方式:拆分为多个断言
assert(a > b);
assert(c < d);
assert(e != f);
assert(g >= h);
实际案例分析
案例1:二分搜索边界错误
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int lower_bound(const vector<int>& arr, int target) {
int left = 0, right = arr.size(); // 注意是 size()
while (left < right) {
int mid = left + (right - left) / 2;
assert(mid >= left && mid < right); // 调试检查
if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid;
}
}
assert(left >= 0 && left <= (int)arr.size()); // 结果验证
return left;
}
案例2:快速交换验证
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
template<typename T>
void quickSort(vector<T>& arr, int left, int right) {
assert(left >= 0 && right >= left && right < (int)arr.size());
if (left >= right) return;
int i = left, j = right;
T pivot = arr[(left + right) / 2];
while (i <= j) {
while (i <= j && arr[i] < pivot) i++;
while (i <= j && arr[j] > pivot) j--;
if (i <= j) {
swap(arr[i], arr[j]);
assert(i >= left && j <= right); // 交换范围检查
i++; j--;
}
}
quickSort(arr, left, j);
quickSort(arr, i, right);
// 局部验证(用于调试)
#ifdef DEBUG
for (int k = left; k <= right - 1; k++) {
assert(arr[k] <= arr[k + 1] ||
(k < j && i <= k + 1)); // 允许未完全排序
}
#endif
}
性能考虑
1. 断言开销
cpp
copy
1
2
3
4
5
6
7
// 低效:复杂计算在断言中
assert(complexCalculation() > 0);
// 高效:先计算,再断言
int result = complexCalculation();
assert(result > 0);
2. 循环内断言
cpp
copy
1
2
3
4
5
6
7
8
9
10
11
12
13
// 谨慎:循环内的断言可能影响性能
for (int i = 0; i < n; i++) {
assert(arr[i] >= 0); // 考虑是否必要
sum += arr[i];
}
// 更好:用条件编译保护
#ifdef DEBUG
for (int i = 0; i < n; i++) {
assert(arr[i] >= 0);
}
#endif
总结
assert 是 Codeforces 竞赛编程中不可或缺的调试工具:
优点
- 快速发现错误:在问题发生的立即位置停止程序
- 清晰的错误信息:显示文件、行号和错误条件
- 零运行时成本:发布版本可以通过
-DNDEBUG完全移除 - 文档作用:assert 语句本身就是关于代码预期的文档
最佳实践
- 验证输入参数的合法性
- 检查算法不变量和关键断言
- 验证边界条件和特殊情况
- 使用自定义宏提供更有意义的错误信息
- 合理的条件编译,调试时启用,提交时禁用
注意事项
- 避免有副作用的表达式
- 谨慎处理浮点数比较
- 注意性能影响,在关键算法中少用
- 提交给裁判的代码记得禁用断言
通过合理使用 assert,你可以在 Codeforces 竞赛中更快地定位和修复 bug,提高解题的成功率和效率。