C++ 进阶知识点详细教程 - 第2部分

C++ 进阶知识点详细教程 - 第2部分

7. 文件重定向

7.1 什么是文件重定向

文件重定向是将程序的标准输入/输出从控制台重定向到文件的技术,在算法竞赛和大数据处理中非常有用。

7.2 命令行重定向

7.2.1 基本语法

# 输入重定向:从文件读取输入
program < input.txt

# 输出重定向:将输出写入文件
program > output.txt

# 追加输出:将输出追加到文件末尾
program >> output.txt

# 同时重定向输入输出
program < input.txt > output.txt

# 错误重定向
program 2> error.txt

# 同时重定向输出和错误
program > output.txt 2> error.txt

7.2.2 实际示例

假设有以下程序 sum.cpp

#include <iostream>
using namespace std;

int main() {
    int a, b;
    cin >> a >> b;
    cout << "Sum: " << a + b << endl;
    return 0;
}

创建输入文件 input.txt

10 20

使用重定向:

# 编译程序
g++ sum.cpp -o sum

# 从文件读取输入,输出到控制台
./sum < input.txt

# 输入输出都重定向到文件
./sum < input.txt > output.txt

# 查看结果
cat output.txt  # 输出:Sum: 30

7.3 freopen方法

7.3.1 基本用法

#include <iostream>
#include <cstdio>
using namespace std;

int main() {
    // 重定向标准输入到文件
    freopen("input.txt", "r", stdin);
    
    // 重定向标准输出到文件
    freopen("output.txt", "w", stdout);
    
    int a, b;
    cin >> a >> b;  // 从 input.txt 读取
    cout << "Result: " << a + b << endl;  // 写入 output.txt
    
    // 关闭文件(可选,程序结束时自动关闭)
    fclose(stdin);
    fclose(stdout);
    
    return 0;
}

7.3.2 条件编译(竞赛常用)

#include <iostream>
#include <cstdio>
using namespace std;

int main() {
    #ifdef LOCAL
        freopen("input.txt", "r", stdin);
        freopen("output.txt", "w", stdout);
    #endif
    
    int n;
    cin >> n;
    
    for (int i = 1; i <= n; i++) {
        cout << i * i << " ";
    }
    cout << endl;
    
    return 0;
}

// 本地测试时编译:g++ -DLOCAL program.cpp
// 提交时编译:g++ program.cpp

7.3.3 错误处理

#include <iostream>
#include <cstdio>
using namespace std;

int main() {
    if (freopen("input.txt", "r", stdin) == NULL) {
        cerr << "无法打开输入文件!" << endl;
        return 1;
    }
    
    if (freopen("output.txt", "w", stdout) == NULL) {
        cerr << "无法创建输出文件!" << endl;
        return 1;
    }
    
    int a, b;
    cin >> a >> b;
    cout << a + b << endl;
    
    return 0;
}

7.4 文件流方法

7.4.1 基本文件流操作

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    // 输入文件流
    ifstream fin("input.txt");
    if (!fin) {
        cout << "无法打开输入文件!" << endl;
        return 1;
    }
    
    // 输出文件流
    ofstream fout("output.txt");
    if (!fout) {
        cout << "无法创建输出文件!" << endl;
        return 1;
    }
    
    int a, b;
    fin >> a >> b;
    fout << "Sum: " << a + b << endl;
    
    fin.close();
    fout.close();
    
    return 0;
}

7.4.2 读取整个文件

#include <iostream>
#include <fstream>
#include <string>
using namespace std;

int main() {
    ifstream fin("data.txt");
    string line;
    
    // 逐行读取
    while (getline(fin, line)) {
        cout << "读取到: " << line << endl;
    }
    
    fin.close();
    return 0;
}

7.4.3 处理数组数据

#include <iostream>
#include <fstream>
#include <vector>
using namespace std;

int main() {
    // 读取数组
    ifstream fin("numbers.txt");
    int n;
    fin >> n;
    
    vector<int> arr(n);
    for (int i = 0; i < n; i++) {
        fin >> arr[i];
    }
    fin.close();
    
    // 处理数据(排序)
    sort(arr.begin(), arr.end());
    
    // 写入结果
    ofstream fout("sorted.txt");
    fout << n << endl;
    for (int i = 0; i < n; i++) {
        fout << arr[i] << " ";
    }
    fout << endl;
    fout.close();
    
    return 0;
}

7.5 文件操作模式

#include <fstream>
using namespace std;

int main() {
    // 不同的打开模式
    ofstream fout1("file1.txt");                    // 写入模式
    ofstream fout2("file2.txt", ios::app);          // 追加模式
    ofstream fout3("file3.txt", ios::out | ios::trunc); // 清空重写
    
    ifstream fin1("input.txt");                     // 读取模式
    ifstream fin2("input.txt", ios::in);            // 明确指定读取
    
    // 读写模式
    fstream file("data.txt", ios::in | ios::out);
    
    return 0;
}

7.6 实战应用:成绩处理系统

#include <iostream>
#include <fstream>
#include <vector>
#include <algorithm>
#include <iomanip>
using namespace std;

struct Student {
    string name;
    int score;
};

int main() {
    // 读取学生数据
    ifstream fin("students.txt");
    int n;
    fin >> n;
    
    vector<Student> students(n);
    for (int i = 0; i < n; i++) {
        fin >> students[i].name >> students[i].score;
    }
    fin.close();
    
    // 按成绩排序
    sort(students.begin(), students.end(), 
         [](const Student& a, const Student& b) {
             return a.score > b.score;
         });
    
    // 输出结果
    ofstream fout("ranking.txt");
    fout << "成绩排名:" << endl;
    fout << "排名\t姓名\t成绩" << endl;
    
    for (int i = 0; i < n; i++) {
        fout << i + 1 << "\t" 
             << students[i].name << "\t" 
             << students[i].score << endl;
    }
    
    // 统计信息
    int total = 0;
    for (const auto& s : students) {
        total += s.score;
    }
    
    fout << "\n统计信息:" << endl;
    fout << "总人数: " << n << endl;
    fout << "平均分: " << fixed << setprecision(2) 
         << (double)total / n << endl;
    fout << "最高分: " << students[0].score << endl;
    fout << "最低分: " << students[n-1].score << endl;
    
    fout.close();
    cout << "处理完成,结果已保存到 ranking.txt" << endl;
    
    return 0;
}

8. 调试技巧

调试是程序开发中最重要的技能之一。掌握有效的调试方法可以大大提高编程效率。

8.1 输出调试法

输出调试法是最简单、最直接的调试方法,通过在关键位置输出变量值来跟踪程序执行。

8.1.1 基本输出调试

#include <iostream>
using namespace std;

int main() {
    int x = 10, y = 20;
    
    cout << "程序开始执行" << endl;
    cout << "x = " << x << ", y = " << y << endl;
    
    int sum = x + y;
    cout << "计算sum = x + y" << endl;
    cout << "sum = " << sum << endl;
    
    if (sum > 25) {
        cout << "进入if分支" << endl;
        sum *= 2;
        cout << "sum乘以2后: " << sum << endl;
    } else {
        cout << "进入else分支" << endl;
    }
    
    cout << "程序结束,最终sum = " << sum << endl;
    return 0;
}

8.1.2 输出数组和容器

#include <iostream>
#include <vector>
#include <map>
using namespace std;

// 输出一维数组
void printArray(int arr[], int n, string name = "数组") {
    cout << name << ": [";
    for (int i = 0; i < n; i++) {
        cout << arr[i];
        if (i < n - 1) cout << ", ";
    }
    cout << "]" << endl;
}

// 输出二维数组
void print2DArray(int arr[][5], int rows, int cols) {
    cout << "二维数组:" << endl;
    for (int i = 0; i < rows; i++) {
        cout << "  [";
        for (int j = 0; j < cols; j++) {
            cout << arr[i][j];
            if (j < cols - 1) cout << ", ";
        }
        cout << "]" << endl;
    }
}

// 输出vector
void printVector(const vector<int>& v, string name = "vector") {
    cout << name << ": [";
    for (size_t i = 0; i < v.size(); i++) {
        cout << v[i];
        if (i < v.size() - 1) cout << ", ";
    }
    cout << "]" << endl;
}

// 输出map
void printMap(const map<string, int>& m) {
    cout << "map内容:" << endl;
    for (const auto& pair : m) {
        cout << "  " << pair.first << " -> " << pair.second << endl;
    }
}

int main() {
    // 测试数组输出
    int arr[] = {1, 2, 3, 4, 5};
    printArray(arr, 5, "测试数组");
    
    // 测试二维数组输出
    int matrix[3][5] = {
        {1, 2, 3, 4, 5},
        {6, 7, 8, 9, 10},
        {11, 12, 13, 14, 15}
    };
    print2DArray(matrix, 3, 5);
    
    // 测试vector输出
    vector<int> vec = {10, 20, 30, 40};
    printVector(vec, "测试vector");
    
    // 测试map输出
    map<string, int> scores = {
        {"Alice", 95},
        {"Bob", 87},
        {"Charlie", 92}
    };
    printMap(scores);
    
    return 0;
}

8.1.3 高级调试宏

#include <iostream>
#include <vector>
#include <map>
using namespace std;

// 是否开启调试模式
#define DEBUG

#ifdef DEBUG
    // 基本调试宏
    #define debug(x) cout << "[DEBUG] " << #x << " = " << x << endl
    
    // 带行号的调试宏
    #define debugLine(x) cout << "[DEBUG:" << __LINE__ << "] " << #x << " = " << x << endl
    
    // 调试函数进入和退出
    #define debugEnter(func) cout << "[ENTER] " << func << endl
    #define debugExit(func) cout << "[EXIT] " << func << endl
    
    // 调试数组
    #define debugArray(arr, n) do { \
        cout << "[DEBUG] " << #arr << " = ["; \
        for (int i = 0; i < n; i++) { \
            cout << arr[i]; \
            if (i < n - 1) cout << ", "; \
        } \
        cout << "]" << endl; \
    } while(0)
    
    // 调试vector
    #define debugVector(v) do { \
        cout << "[DEBUG] " << #v << " = ["; \
        for (size_t i = 0; i < v.size(); i++) { \
            cout << v[i]; \
            if (i < v.size() - 1) cout << ", "; \
        } \
        cout << "]" << endl; \
    } while(0)
    
    // 条件调试
    #define debugIf(condition, x) do { \
        if (condition) { \
            cout << "[DEBUG-IF] " << #x << " = " << x << endl; \
        } \
    } while(0)
    
#else
    #define debug(x)
    #define debugLine(x)
    #define debugEnter(func)
    #define debugExit(func)
    #define debugArray(arr, n)
    #define debugVector(v)
    #define debugIf(condition, x)
#endif

// 示例函数
int fibonacci(int n) {
    debugEnter("fibonacci");
    debug(n);
    
    if (n <= 1) {
        debugExit("fibonacci");
        return n;
    }
    
    int result = fibonacci(n - 1) + fibonacci(n - 2);
    debug(result);
    debugExit("fibonacci");
    return result;
}

int main() {
    debug("程序开始");
    
    int x = 10, y = 20;
    debug(x);
    debugLine(y);
    
    int arr[] = {1, 2, 3, 4, 5};
    debugArray(arr, 5);
    
    vector<int> vec = {10, 20, 30};
    debugVector(vec);
    
    debugIf(x > 5, x);
    debugIf(x < 5, x);
    
    int fib5 = fibonacci(5);
    debug(fib5);
    
    return 0;
}

8.1.4 分段调试技巧

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

// 冒泡排序的调试版本
void bubbleSortDebug(vector<int>& arr) {
    int n = arr.size();
    cout << "开始冒泡排序,数组大小: " << n << endl;
    
    // 输出初始数组
    cout << "初始数组: ";
    for (int x : arr) cout << x << " ";
    cout << endl << endl;
    
    for (int i = 0; i < n - 1; i++) {
        cout << "第 " << i + 1 << " 轮排序:" << endl;
        bool swapped = false;
        
        for (int j = 0; j < n - i - 1; j++) {
            cout << "  比较 arr[" << j << "]=" << arr[j] 
                 << " 和 arr[" << j + 1 << "]=" << arr[j + 1];
            
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
                swapped = true;
                cout << " -> 交换";
            } else {
                cout << " -> 不交换";
            }
            cout << endl;
        }
        
        cout << "  第 " << i + 1 << " 轮结束,数组: ";
        for (int x : arr) cout << x << " ";
        cout << endl;
        
        if (!swapped) {
            cout << "  没有发生交换,排序完成!" << endl;
            break;
        }
        cout << endl;
    }
    
    cout << "最终排序结果: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    bubbleSortDebug(arr);
    return 0;
}

8.1.5 性能调试

#include <iostream>
#include <chrono>
#include <vector>
using namespace std;
using namespace std::chrono;

class Timer {
private:
    high_resolution_clock::time_point start_time;
    string name;
    
public:
    Timer(string timer_name) : name(timer_name) {
        start_time = high_resolution_clock::now();
        cout << "[TIMER] " << name << " 开始" << endl;
    }
    
    ~Timer() {
        auto end_time = high_resolution_clock::now();
        auto duration = duration_cast<microseconds>(end_time - start_time);
        cout << "[TIMER] " << name << " 结束,耗时: " 
             << duration.count() << " 微秒" << endl;
    }
};

// 不同的排序算法性能对比
void bubbleSort(vector<int> arr) {
    Timer timer("冒泡排序");
    int n = arr.size();
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
}

void quickSortHelper(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSortHelper(arr, low, pi - 1);
        quickSortHelper(arr, pi + 1, high);
    }
}

void quickSort(vector<int> arr) {
    Timer timer("快速排序");
    quickSortHelper(arr, 0, arr.size() - 1);
}

void stdSort(vector<int> arr) {
    Timer timer("标准库排序");
    sort(arr.begin(), arr.end());
}

int main() {
    // 生成测试数据
    vector<int> data;
    for (int i = 1000; i >= 1; i--) {
        data.push_back(i);
    }
    
    cout << "测试数据大小: " << data.size() << endl << endl;
    
    // 测试不同排序算法
    bubbleSort(data);
    quickSort(data);
    stdSort(data);
    
    return 0;
}

8.2 GDB调试

GDB(GNU Debugger)是一个强大的命令行调试工具,可以帮助我们深入分析程序的执行过程。

8.2.1 GDB基础使用

编译程序(必须加-g选项)

g++ -g -o program program.cpp

启动GDB

gdb program

8.2.2 常用GDB命令

命令 简写 功能
run r 运行程序
break b 设置断点
next n 单步执行(不进入函数)
step s 单步执行(进入函数)
continue c 继续执行
print p 打印变量值
display disp 自动显示变量
info locals 显示局部变量
backtrace bt 显示调用栈
list l 显示源代码
quit q 退出GDB

8.2.3 实战示例:调试递归函数

#include <iostream>
using namespace std;

int factorial(int n) {
    cout << "计算 " << n << " 的阶乘" << endl;
    if (n <= 1) {
        cout << "递归基础情况: " << n << endl;
        return 1;
    }
    int result = n * factorial(n - 1);
    cout << "返回 " << n << " * factorial(" << n-1 << ") = " << result << endl;
    return result;
}

int findMax(int arr[], int n) {
    int max_val = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }
    return max_val;
}

int main() {
    cout << "=== 测试阶乘函数 ===" << endl;
    int n = 5;
    int result = factorial(n);
    cout << n << "! = " << result << endl;
    
    cout << "\n=== 测试查找最大值 ===" << endl;
    int arr[] = {3, 7, 2, 9, 1, 5};
    int size = sizeof(arr) / sizeof(arr[0]);
    int max_val = findMax(arr, size);
    cout << "最大值: " << max_val << endl;
    
    return 0;
}

详细调试步骤

# 1. 编译程序
$ g++ -g -o debug_test debug_test.cpp

# 2. 启动GDB
$ gdb debug_test

# 3. 在GDB中执行以下命令:
(gdb) break main                    # 在main函数设置断点
(gdb) break factorial               # 在factorial函数设置断点
(gdb) run                          # 运行程序

# 4. 程序在main函数开始处停下
(gdb) list                         # 查看当前代码
(gdb) print n                      # 查看变量n的值
(gdb) next                         # 单步执行到下一行
(gdb) step                         # 进入factorial函数

# 5. 在factorial函数中
(gdb) print n                      # 查看参数n
(gdb) display n                    # 自动显示n的值
(gdb) continue                     # 继续执行到下一个断点

# 6. 查看调用栈
(gdb) backtrace                    # 显示函数调用栈
(gdb) info locals                  # 显示当前函数的局部变量

# 7. 设置条件断点
(gdb) break factorial if n == 2    # 只在n等于2时停下

# 8. 监视变量
(gdb) watch result                 # 当result变量改变时停下

8.2.4 高级GDB技巧

条件断点

(gdb) break 15 if i > 10           # 在第15行设置条件断点
(gdb) break findMax if n > 5       # 在函数入口设置条件断点

监视点

(gdb) watch max_val                # 监视变量变化
(gdb) rwatch max_val               # 监视变量读取
(gdb) awatch max_val               # 监视变量读写

查看内存

(gdb) x/10i main                   # 查看main函数的汇编代码
(gdb) x/5d arr                     # 查看数组前5个元素
(gdb) x/s str                      # 查看字符串

调试多线程程序

(gdb) info threads                 # 显示所有线程
(gdb) thread 2                     # 切换到线程2
(gdb) break thread_func thread 2   # 在特定线程设置断点

8.2.5 GDB调试脚本

创建 .gdbinit 文件自动化调试:

# .gdbinit 文件内容
set print pretty on
set print array on
set print array-indexes on

# 自定义命令
define parray
    if $argc == 2
        print *$arg0@$arg1
    else
        print "Usage: parray array_pointer size"
    end
end

# 启动时自动设置断点
break main
run

8.3 调试策略和技巧

8.3.1 二分调试法

当程序出现错误但不知道具体位置时,使用二分法缩小范围:

#include <iostream>
#include <vector>
using namespace std;

int buggyFunction(vector<int>& arr) {
    // 假设这个函数有bug,我们不知道在哪里
    
    cout << "DEBUG: 函数开始" << endl;  // 检查点1
    
    int sum = 0;
    for (int i = 0; i < arr.size(); i++) {
        sum += arr[i];
    }
    
    cout << "DEBUG: 求和完成,sum = " << sum << endl;  // 检查点2
    
    // 这里有一个bug:应该是arr.size()而不是arr.size()+1
    for (int i = 0; i <= arr.size(); i++) {  // 越界访问!
        arr[i] *= 2;
    }
    
    cout << "DEBUG: 数组修改完成" << endl;  // 检查点3
    
    return sum;
}

int main() {
    vector<int> data = {1, 2, 3, 4, 5};
    
    cout << "DEBUG: 调用函数前" << endl;
    int result = buggyFunction(data);
    cout << "DEBUG: 调用函数后,结果 = " << result << endl;
    
    return 0;
}

8.3.2 对拍调试法

用简单的暴力算法验证复杂算法的正确性:

#include <iostream>
#include <vector>
#include <algorithm>
#include <random>
using namespace std;

// 简单的O(n²)排序算法(用于对拍)
vector<int> bubbleSort(vector<int> arr) {
    int n = arr.size();
    for (int i = 0; i < n - 1; i++) {
        for (int j = 0; j < n - i - 1; j++) {
            if (arr[j] > arr[j + 1]) {
                swap(arr[j], arr[j + 1]);
            }
        }
    }
    return arr;
}

// 复杂的快速排序算法(需要验证)
int partition(vector<int>& arr, int low, int high) {
    int pivot = arr[high];
    int i = low - 1;
    
    for (int j = low; j < high; j++) {
        if (arr[j] < pivot) {
            i++;
            swap(arr[i], arr[j]);
        }
    }
    swap(arr[i + 1], arr[high]);
    return i + 1;
}

void quickSortHelper(vector<int>& arr, int low, int high) {
    if (low < high) {
        int pi = partition(arr, low, high);
        quickSortHelper(arr, low, pi - 1);
        quickSortHelper(arr, pi + 1, high);
    }
}

vector<int> quickSort(vector<int> arr) {
    quickSortHelper(arr, 0, arr.size() - 1);
    return arr;
}

// 对拍测试函数
void compareAlgorithms() {
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution<> dis(1, 1000);
    
    for (int test = 0; test < 1000; test++) {
        // 生成随机测试数据
        vector<int> data;
        int size = dis(gen) % 50 + 1;  // 1-50个元素
        
        for (int i = 0; i < size; i++) {
            data.push_back(dis(gen));
        }
        
        // 分别用两种算法排序
        vector<int> result1 = bubbleSort(data);
        vector<int> result2 = quickSort(data);
        
        // 比较结果
        if (result1 != result2) {
            cout << "发现错误!测试用例 " << test << endl;
            cout << "原始数据: ";
            for (int x : data) cout << x << " ";
            cout << endl;
            
            cout << "冒泡排序结果: ";
            for (int x : result1) cout << x << " ";
            cout << endl;
            
            cout << "快速排序结果: ";
            for (int x : result2) cout << x << " ";
            cout << endl;
            
            return;
        }
        
        if (test % 100 == 0) {
            cout << "已完成 " << test << " 个测试用例" << endl;
        }
    }
    
    cout << "所有测试用例通过!" << endl;
}

int main() {
    compareAlgorithms();
    return 0;
}

8.3.3 边界条件测试

#include <iostream>
#include <vector>
using namespace std;

int binarySearch(vector<int>& arr, int target) {
    int left = 0, right = arr.size() - 1;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        
        cout << "DEBUG: left=" << left << ", right=" << right 
             << ", mid=" << mid << ", arr[mid]=" << arr[mid] << endl;
        
        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    return -1;
}

void testBinarySearch() {
    cout << "=== 测试二分查找 ===" << endl;
    
    // 测试用例1:正常情况
    vector<int> arr1 = {1, 3, 5, 7, 9, 11, 13};
    cout << "\n测试1:正常查找" << endl;
    cout << "查找7: " << binarySearch(arr1, 7) << endl;
    
    // 测试用例2:空数组
    vector<int> arr2;
    cout << "\n测试2:空数组" << endl;
    cout << "查找5: " << binarySearch(arr2, 5) << endl;
    
    // 测试用例3:单元素数组
    vector<int> arr3 = {5};
    cout << "\n测试3:单元素数组" << endl;
    cout << "查找5: " << binarySearch(arr3, 5) << endl;
    cout << "查找3: " << binarySearch(arr3, 3) << endl;
    
    // 测试用例4:查找不存在的元素
    cout << "\n测试4:查找不存在的元素" << endl;
    cout << "查找0: " << binarySearch(arr1, 0) << endl;
    cout << "查找15: " << binarySearch(arr1, 15) << endl;
    cout << "查找6: " << binarySearch(arr1, 6) << endl;
    
    // 测试用例5:边界元素
    cout << "\n测试5:边界元素" << endl;
    cout << "查找1: " << binarySearch(arr1, 1) << endl;
    cout << "查找13: " << binarySearch(arr1, 13) << endl;
}

int main() {
    testBinarySearch();
    return 0;
}

8.4 调试技巧总结

  1. 渐进式调试:从简单情况开始,逐步增加复杂度
  2. 假设验证:对每个假设都要验证
  3. 日志记录:在关键位置记录程序状态
  4. 代码审查:让别人帮忙检查代码
  5. 单元测试:为每个函数编写测试用例
  6. 版本控制:使用git等工具跟踪代码变化

常见调试场景

  • 段错误:通常是数组越界或空指针访问
  • 逻辑错误:结果不正确,需要跟踪算法执行过程
  • 性能问题:使用性能分析工具找出瓶颈
  • 内存泄漏:使用valgrind等工具检测

9. 断言调试

断言(Assert)是一种程序调试技术,用于在代码中检查某些条件是否成立。如果条件不成立,程序会立即终止并显示错误信息。

9.1 什么是断言

断言是一种调试工具,用于验证程序在特定点的状态是否符合预期。它帮助我们:

  1. 及早发现错误:在错误传播之前就发现问题
  2. 文档化假设:明确代码的前提条件
  3. 提高代码质量:强制检查关键条件
#include <cassert>
assert(条件);  // 如果条件为false,程序终止并报错

9.1.1 基本使用示例

#include <iostream>
#include <cassert>
using namespace std;

int divide(int a, int b) {
    assert(b != 0);  // 断言:除数不能为0
    return a / b;
}

int main() {
    cout << "10 / 2 = " << divide(10, 2) << endl;  // 正常执行
    cout << "10 / 0 = " << divide(10, 0) << endl;  // 触发断言,程序终止
    cout << "这行不会执行" << endl;
    
    return 0;
}

运行结果

10 / 2 = 5
Assertion failed: (b != 0), function divide, file test.cpp, line 6.
Abort trap: 6

9.2 断言的类型

9.2.1 前置条件断言(Precondition)

检查函数输入参数的有效性:

#include <iostream>
#include <cassert>
#include <cmath>
using namespace std;

double squareRoot(double x) {
    assert(x >= 0);  // 前置条件:x必须非负
    return sqrt(x);
}

int arraySum(int arr[], int n) {
    assert(arr != nullptr);  // 前置条件:数组不能为空指针
    assert(n > 0);          // 前置条件:数组大小必须为正
    
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += arr[i];
    }
    return sum;
}

int main() {
    // 正常使用
    cout << "sqrt(16) = " << squareRoot(16) << endl;
    
    int arr[] = {1, 2, 3, 4, 5};
    cout << "数组和 = " << arraySum(arr, 5) << endl;
    
    // 触发断言
    // squareRoot(-1);     // 断言失败:负数不能开平方
    // arraySum(nullptr, 5); // 断言失败:空指针
    // arraySum(arr, 0);   // 断言失败:大小为0
    
    return 0;
}

9.2.2 后置条件断言(Postcondition)

检查函数返回值或执行结果的正确性:

#include <iostream>
#include <cassert>
using namespace std;

int factorial(int n) {
    assert(n >= 0);  // 前置条件
    
    int result = 1;
    for (int i = 1; i <= n; i++) {
        result *= i;
    }
    
    // 后置条件:阶乘结果必须为正数(除了0! = 1)
    assert(result > 0);
    // 后置条件:n! >= n(当n > 1时)
    if (n > 1) {
        assert(result >= n);
    }
    
    return result;
}

int findMax(int arr[], int n) {
    assert(arr != nullptr && n > 0);  // 前置条件
    
    int max_val = arr[0];
    for (int i = 1; i < n; i++) {
        if (arr[i] > max_val) {
            max_val = arr[i];
        }
    }
    
    // 后置条件:返回值必须在数组中存在
    bool found = false;
    for (int i = 0; i < n; i++) {
        if (arr[i] == max_val) {
            found = true;
            break;
        }
    }
    assert(found);
    
    // 后置条件:返回值必须大于等于数组中的所有元素
    for (int i = 0; i < n; i++) {
        assert(max_val >= arr[i]);
    }
    
    return max_val;
}

int main() {
    cout << "5! = " << factorial(5) << endl;
    
    int arr[] = {3, 7, 2, 9, 1, 5};
    cout << "最大值 = " << findMax(arr, 6) << endl;
    
    return 0;
}

9.2.3 不变量断言(Invariant)

检查程序执行过程中应该始终保持的条件:

#include <iostream>
#include <cassert>
#include <vector>
using namespace std;

// 插入排序的不变量检查
void insertionSort(vector<int>& arr) {
    int n = arr.size();
    
    for (int i = 1; i < n; i++) {
        int key = arr[i];
        int j = i - 1;
        
        // 不变量:arr[0...i-1]已经排序
        for (int k = 0; k < i - 1; k++) {
            assert(arr[k] <= arr[k + 1]);
        }
        
        while (j >= 0 && arr[j] > key) {
            arr[j + 1] = arr[j];
            j--;
        }
        arr[j + 1] = key;
        
        // 不变量:arr[0...i]现在已经排序
        for (int k = 0; k < i; k++) {
            assert(arr[k] <= arr[k + 1]);
        }
    }
    
    // 最终不变量:整个数组已排序
    for (int i = 0; i < n - 1; i++) {
        assert(arr[i] <= arr[i + 1]);
    }
}

// 二分查找的不变量检查
int binarySearch(vector<int>& arr, int target) {
    assert(!arr.empty());  // 前置条件:数组不为空
    
    // 前置条件:数组已排序
    for (int i = 0; i < arr.size() - 1; i++) {
        assert(arr[i] <= arr[i + 1]);
    }
    
    int left = 0, right = arr.size() - 1;
    
    while (left <= right) {
        // 不变量:如果target存在,必定在[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;
        }
        
        // 不变量:搜索范围在缩小
        assert(left <= right + 1);
    }
    
    return -1;  // 未找到
}

int main() {
    vector<int> arr = {64, 34, 25, 12, 22, 11, 90};
    cout << "排序前: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    insertionSort(arr);
    cout << "排序后: ";
    for (int x : arr) cout << x << " ";
    cout << endl;
    
    int index = binarySearch(arr, 25);
    cout << "查找25的位置: " << index << endl;
    
    return 0;
}

9.3 数组和指针的断言检查

#include <iostream>
#include <cassert>
using namespace std;

class SafeArray {
private:
    int* data;
    int size;
    
public:
    SafeArray(int n) : size(n) {
        assert(n > 0);  // 大小必须为正
        data = new int[n];
        
        // 初始化为0
        for (int i = 0; i < size; i++) {
            data[i] = 0;
        }
    }
    
    ~SafeArray() {
        delete[] data;
    }
    
    int& operator[](int index) {
        assert(index >= 0 && index < size);  // 边界检查
        return data[index];
    }
    
    const int& operator[](int index) const {
        assert(index >= 0 && index < size);  // 边界检查
        return data[index];
    }
    
    int getSize() const {
        return size;
    }
    
    void resize(int newSize) {
        assert(newSize > 0);  // 新大小必须为正
        
        int* newData = new int[newSize];
        int copySize = min(size, newSize);
        
        for (int i = 0; i < copySize; i++) {
            newData[i] = data[i];
        }
        
        // 如果扩大,初始化新元素为0
        for (int i = copySize; i < newSize; i++) {
            newData[i] = 0;
        }
        
        delete[] data;
        data = newData;
        size = newSize;
        
        // 后置条件:大小已更新
        assert(this->size == newSize);
    }
};

int main() {
    SafeArray arr(5);
    
    // 正常使用
    for (int i = 0; i < arr.getSize(); i++) {
        arr[i] = i * 10;
    }
    
    cout << "数组内容: ";
    for (int i = 0; i < arr.getSize(); i++) {
        cout << arr[i] << " ";
    }
    cout << endl;
    
    // 这些操作会触发断言
    // arr[-1] = 100;     // 负索引
    // arr[10] = 200;     // 超出边界
    // arr.resize(-5);    // 负大小
    
    return 0;
}

9.4 断言的高级用法

9.4.1 自定义断言宏

#include <iostream>
#include <cstdlib>
using namespace std;

// 带消息的断言宏
#define ASSERT_MSG(condition, message) \
    do { \
        if (!(condition)) { \
            cerr << "断言失败: " << #condition << endl; \
            cerr << "消息: " << message << endl; \
            cerr << "文件: " << __FILE__ << endl; \
            cerr << "行号: " << __LINE__ << endl; \
            cerr << "函数: " << __FUNCTION__ << endl; \
            abort(); \
        } \
    } while(0)

// 条件断言宏
#define ASSERT_IF(condition, check, message) \
    do { \
        if (condition) { \
            ASSERT_MSG(check, message); \
        } \
    } while(0)

// 范围检查宏
#define ASSERT_RANGE(value, min, max) \
    ASSERT_MSG((value) >= (min) && (value) <= (max), \
               "值 " #value " 不在范围 [" #min ", " #max "] 内")

int divide(int a, int b) {
    ASSERT_MSG(b != 0, "除数不能为0");
    return a / b;
}

void processAge(int age) {
    ASSERT_RANGE(age, 0, 150);
    cout << "处理年龄: " << age << endl;
}

void processArray(int* arr, int size, bool checkSorted = false) {
    ASSERT_MSG(arr != nullptr, "数组指针不能为空");
    ASSERT_MSG(size > 0, "数组大小必须为正");
    
    ASSERT_IF(checkSorted, 
              [&]() {
                  for (int i = 0; i < size - 1; i++) {
                      if (arr[i] > arr[i + 1]) return false;
                  }
                  return true;
              }(), 
              "数组必须已排序");
    
    cout << "处理数组,大小: " << size << endl;
}

int main() {
    // 正常使用
    cout << "10 / 2 = " << divide(10, 2) << endl;
    processAge(25);
    
    int arr[] = {1, 2, 3, 4, 5};
    processArray(arr, 5, true);
    
    // 这些会触发断言
    // divide(10, 0);
    // processAge(-5);
    // processAge(200);
    
    return 0;
}

9.4.2 调试模式下的断言

#include <iostream>
#include <cassert>
using namespace std;

// 只在调试模式下启用的断言
#ifdef DEBUG
    #define DEBUG_ASSERT(condition) assert(condition)
    #define DEBUG_PRINT(msg) cout << "[DEBUG] " << msg << endl
#else
    #define DEBUG_ASSERT(condition)
    #define DEBUG_PRINT(msg)
#endif

class Matrix {
private:
    int** data;
    int rows, cols;
    
public:
    Matrix(int r, int c) : rows(r), cols(c) {
        assert(r > 0 && c > 0);  // 总是检查
        
        data = new int*[rows];
        for (int i = 0; i < rows; i++) {
            data[i] = new int[cols];
            for (int j = 0; j < cols; j++) {
                data[i][j] = 0;
            }
        }
        
        DEBUG_PRINT("创建矩阵 " << rows << "x" << cols);
    }
    
    ~Matrix() {
        for (int i = 0; i < rows; i++) {
            delete[] data[i];
        }
        delete[] data;
        DEBUG_PRINT("销毁矩阵 " << rows << "x" << cols);
    }
    
    int& operator()(int r, int c) {
        assert(r >= 0 && r < rows);  // 总是检查边界
        assert(c >= 0 && c < cols);
        
        DEBUG_ASSERT(data != nullptr);  // 只在调试模式检查
        DEBUG_ASSERT(data[r] != nullptr);
        
        return data[r][c];
    }
    
    void multiply(const Matrix& other, Matrix& result) {
        assert(cols == other.rows);  // 矩阵乘法前提条件
        assert(result.rows == rows && result.cols == other.cols);
        
        DEBUG_PRINT("执行矩阵乘法: " << rows << "x" << cols 
                   << " * " << other.rows << "x" << other.cols);
        
        for (int i = 0; i < rows; i++) {
            for (int j = 0; j < other.cols; j++) {
                result.data[i][j] = 0;
                for (int k = 0; k < cols; k++) {
                    result.data[i][j] += data[i][k] * other.data[k][j];
                }
                DEBUG_ASSERT(result.data[i][j] >= 0 || result.data[i][j] < 0);  // 检查计算结果有效
            }
        }
    }
    
    int getRows() const { return rows; }
    int getCols() const { return cols; }
};

int main() {
    Matrix a(2, 3);
    Matrix b(3, 2);
    Matrix c(2, 2);
    
    // 填充矩阵
    for (int i = 0; i < a.getRows(); i++) {
        for (int j = 0; j < a.getCols(); j++) {
            a(i, j) = i + j + 1;
        }
    }
    
    for (int i = 0; i < b.getRows(); i++) {
        for (int j = 0; j < b.getCols(); j++) {
            b(i, j) = i * 2 + j + 1;
        }
    }
    
    a.multiply(b, c);
    
    cout << "矩阵乘法完成" << endl;
    
    return 0;
}

// 编译命令:
// g++ -DDEBUG program.cpp  // 启用调试断言
// g++ program.cpp          // 禁用调试断言

9.5 断言 vs 异常处理

特性 断言 异常处理
用途 调试,检查程序员错误 处理运行时错误
性能 可以完全禁用 总是存在开销
恢复 程序终止 可以捕获和恢复
发布版本 通常禁用 保留
错误类型 逻辑错误 运行时错误
#include <iostream>
#include <cassert>
#include <stdexcept>
#include <fstream>
using namespace std;

class BankAccount {
private:
    double balance;
    
public:
    BankAccount(double initial) : balance(initial) {
        assert(initial >= 0);  // 断言:初始余额不能为负(程序员错误)
    }
    
    void withdraw(double amount) {
        assert(amount > 0);  // 断言:提取金额必须为正(程序员错误)
        
        // 异常处理:余额不足(用户错误,可以恢复)
        if (amount > balance) {
            throw runtime_error("余额不足");
        }
        
        balance -= amount;
        
        assert(balance >= 0);  // 断言:余额不应该为负(程序逻辑错误)
    }
    
    double getBalance() const {
        assert(balance >= 0);  // 断言:余额应该始终非负
        return balance;
    }
};

void processFile(const string& filename) {
    assert(!filename.empty());  // 断言:文件名不能为空(程序员错误)
    
    ifstream file(filename);
    if (!file.is_open()) {
        // 异常处理:文件打开失败(外部错误,可以恢复)
        throw runtime_error("无法打开文件: " + filename);
    }
    
    // 处理文件...
}

int main() {
    try {
        BankAccount account(1000);
        cout << "初始余额: " << account.getBalance() << endl;
        
        account.withdraw(500);
        cout << "提取500后余额: " << account.getBalance() << endl;
        
        // 这会抛出异常(可以捕获)
        account.withdraw(600);
        
    } catch (const runtime_error& e) {
        cout << "捕获异常: " << e.what() << endl;
        cout << "程序继续执行..." << endl;
    }
    
    // 这些会触发断言(程序终止)
    // BankAccount badAccount(-100);  // 负初始余额
    // account.withdraw(-50);         // 负提取金额
    
    return 0;
}

9.6 断言的最佳实践

  1. 在函数入口检查参数
  2. 在函数出口检查返回值
  3. 在循环中检查不变量
  4. 在关键算法步骤检查中间状态
  5. 发布版本前考虑是否禁用断言
// 发布版本禁用断言
#ifdef RELEASE
    #define NDEBUG
#endif
#include <cassert>

10. 二分答案

10.1 什么是二分答案

二分答案是一种算法思想:

  • 答案具有单调性
  • 可以快速检查某个值是否满足条件
  • 在答案范围内二分查找最优解

10.2 基本模板

int binarySearchAnswer(int left, int right) {
    int ans = -1;
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (check(mid)) {  // 检查mid是否满足条件
            ans = mid;
            // 根据题意调整搜索范围
            left = mid + 1;   // 或 right = mid - 1;
        } else {
            right = mid - 1;  // 或 left = mid + 1;
        }
    }
    return ans;
}

10.3 经典例题1:砍树

问题:有n棵树,高度为h[i]。需要砍掉高度超过H的部分,使得砍下的木材总长度至少为M。求H的最大值。

#include <iostream>
#include <algorithm>
using namespace std;

int n, m;
int h[100005];

// 检查高度H能否砍下至少m的木材
bool check(int H) {
    long long sum = 0;
    for (int i = 0; i < n; i++) {
        if (h[i] > H) {
            sum += h[i] - H;
        }
    }
    return sum >= m;
}

int main() {
    cin >> n >> m;
    int maxH = 0;
    for (int i = 0; i < n; i++) {
        cin >> h[i];
        maxH = max(maxH, h[i]);
    }
    
    int left = 0, right = maxH;
    int ans = 0;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (check(mid)) {
            ans = mid;
            left = mid + 1;  // 尝试更大的高度
        } else {
            right = mid - 1;
        }
    }
    
    cout << ans << endl;
    return 0;
}

10.4 经典例题2:分配问题

问题:n个物品,分给m个人,每人得到的物品是连续的。最小化最大值。

#include <iostream>
using namespace std;

int n, m;
int arr[100005];

// 检查是否能在maxSum限制下分成m份
bool check(int maxSum) {
    int groups = 1;
    int currentSum = 0;
    
    for (int i = 0; i < n; i++) {
        if (arr[i] > maxSum) return false;
        
        if (currentSum + arr[i] > maxSum) {
            groups++;
            currentSum = arr[i];
        } else {
            currentSum += arr[i];
        }
    }
    
    return groups <= m;
}

int main() {
    cin >> n >> m;
    int sum = 0, maxVal = 0;
    for (int i = 0; i < n; i++) {
        cin >> arr[i];
        sum += arr[i];
        maxVal = max(maxVal, arr[i]);
    }
    
    int left = maxVal, right = sum;
    int ans = sum;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (check(mid)) {
            ans = mid;
            right = mid - 1;  // 尝试更小的值
        } else {
            left = mid + 1;
        }
    }
    
    cout << ans << endl;
    return 0;
}

10.5 经典例题3:跳石头

问题:河上有n块石头,要移除m块,使得剩余石头间的最小距离最大。

#include <iostream>
#include <algorithm>
using namespace std;

int L, n, m;
int stones[50005];

// 检查最小距离为minDist时,是否最多移除m块石头
bool check(int minDist) {
    int removed = 0;
    int lastPos = 0;
    
    for (int i = 1; i <= n + 1; i++) {
        if (stones[i] - lastPos < minDist) {
            removed++;
        } else {
            lastPos = stones[i];
        }
    }
    
    return removed <= m;
}

int main() {
    cin >> L >> n >> m;
    stones[0] = 0;
    for (int i = 1; i <= n; i++) {
        cin >> stones[i];
    }
    stones[n + 1] = L;
    
    sort(stones, stones + n + 2);
    
    int left = 0, right = L;
    int ans = 0;
    
    while (left <= right) {
        int mid = left + (right - left) / 2;
        if (check(mid)) {
            ans = mid;
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    
    cout << ans << endl;
    return 0;
}

10.6 浮点数二分

double binarySearchFloat(double left, double right) {
    const double EPS = 1e-6;  // 精度
    
    while (right - left > EPS) {
        double mid = (left + right) / 2;
        if (check(mid)) {
            left = mid;
        } else {
            right = mid;
        }
    }
    
    return left;
}

10.7 二分答案总结

适用条件

  1. 答案有范围
  2. 答案具有单调性
  3. 可以快速验证某个值是否可行

解题步骤

  1. 确定答案范围[left, right]
  2. 写check函数验证
  3. 二分查找最优解
posted @ 2025-11-14 17:24  surprise_ying  阅读(0)  评论(0)    收藏  举报