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 调试技巧总结
- 渐进式调试:从简单情况开始,逐步增加复杂度
- 假设验证:对每个假设都要验证
- 日志记录:在关键位置记录程序状态
- 代码审查:让别人帮忙检查代码
- 单元测试:为每个函数编写测试用例
- 版本控制:使用git等工具跟踪代码变化
常见调试场景:
- 段错误:通常是数组越界或空指针访问
- 逻辑错误:结果不正确,需要跟踪算法执行过程
- 性能问题:使用性能分析工具找出瓶颈
- 内存泄漏:使用valgrind等工具检测
9. 断言调试
断言(Assert)是一种程序调试技术,用于在代码中检查某些条件是否成立。如果条件不成立,程序会立即终止并显示错误信息。
9.1 什么是断言
断言是一种调试工具,用于验证程序在特定点的状态是否符合预期。它帮助我们:
- 及早发现错误:在错误传播之前就发现问题
- 文档化假设:明确代码的前提条件
- 提高代码质量:强制检查关键条件
#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 断言的最佳实践
- 在函数入口检查参数
- 在函数出口检查返回值
- 在循环中检查不变量
- 在关键算法步骤检查中间状态
- 发布版本前考虑是否禁用断言
// 发布版本禁用断言
#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 二分答案总结
适用条件:
- 答案有范围
- 答案具有单调性
- 可以快速验证某个值是否可行
解题步骤:
- 确定答案范围[left, right]
- 写check函数验证
- 二分查找最优解
浙公网安备 33010602011771号