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,提高解题的成功率和效率。