C++对拍即调试技巧
一、生成数据
若是生成数据输出文件操作应写成 freopen("something.in","w",stdout);
\(1.\) 生成随机序列
int p[N];
for(int i=1;i<=n;i++) p[i]=i;
random_shuffle(p+1,p+n+1);//打乱一个数组
for(int i=1;i<=n;i++) cout<<p[i]<<" ";
这样生成一个 \(1 \sim n\) 的随机排列
\(2.\) 生成随机一棵树
for(int i=2;i<=n;i++)
cout<<rand%(i-1)+1<<" "<<i<<endl;
这样生成一棵以 \(1\) 为根的树
\(3.\) 生成随机无自环无重边的无向图
//预定n为点数,m为边数
set<pair<int,int> >s;//边的集合
for(int i=1;i<=m;i++)
{
int from=rand%n+1,to=rand%n+1;
if(from==to)//判自环
{
i--;
continue;
}
if(s.count(make_pair(from,to))||s.count(make_pair(to,from)))//判重边,如果是有向图把后面(to,from)的删掉
{
i--;
continue;
}
s.insert(make_pair(from,to));//加入边的集合
cout<<from<<" "<<to<<endl;//输出
}
生成一个 \(n\) 个点,\(m\) 条边,无自环无重边的无向图
二、对拍程序怎么写
我们令数据生成器、自己猜想的正解与暴力放在同一个根目录下
令数据生成器可执行文件的文件名名为 produce
,自己猜想的正解可执行文件名为a
,暴力可执行文件名为b
,a
的输出文件为 A.out
,b
的输出文件为 B.out
(Linux下可执行文件文件名没有后缀,Windows下为.exe
)
对拍程序 check.cpp
写法如下:(要对拍直接运行 check
即可,默认在Linux下运行)
#include<bits/stdc++.h>
using namespace std;
int main()
{
int cnt=0;//统计对的个数
while(true)
{
system("./produce");
system("./a");
system("./b");//运行produce,a,b三个程序
if(system("diff A.out B.out"))//比较A.out与B.out,如果不一样会返回1(Linux下)
{
puts("WA");
return 0;
}
else printf("AC %d\n",++cnt);//对的个数加1
}
return 0;
}
//另外,想要停止要按 Ctrl + Z
数据生成器除了用来生成数据进行对拍外,还可以用来测试时间复杂度是否正确。生成一组极限数据, 然后在 \(\text{Linux}\) 下使用命令 time
来测试程序运行的时间
比如假设正解名称还是上文中的 a.cpp
,那可以在控制台输入命令:
time ./a
返回时会由上到下返回三个时间:\(\text{real,user,sys}\)
其中 \(\text{user}\) 是程序将要评测机上运行的时间,如果 \(\text{user}\) 所指的时间大于题面要求时限,说明程序 百分百 会 \(\text{TLE}\)
三、调试
\(1.\) 输出中间变量调试
输出变量以调试,直到找到出错的部分
建议输出加一些提示的信息
这就不详细讲了吧……?
\(2.\) 单步调试 \(GDB\)
这个默认在 Windows 下调试,而且所涉及的 路径名 与 文件名 中 不可以有空格!
首先得找到并编译文件,按 Win + R
在里面输入 cmd
先切换到文件所在的硬盘,在终端里输入 硬盘 :
,比如D盘输入 D:
然后再复制要编译调试的文件所在的文件夹的位置,在终端里输入 cd 路径名
,比如输入 D:\vscode_code
再编译文件。编译格式:g++ -g -o 生成可执行文件名.exe 要编译的文件名.cpp
(本文默认 C++
,Python
、C
等也行)
然后如果想运行的话,直接输入可执行文件名.exe
即可
若想调试则输入 gdb 可执行文件名.exe
不懂的可以看这张图,说的就是上面的步骤:(图中的 Temp_the_test.cpp
是 洛谷P1850换教室 的 \(\text{AC}\) 代码)
找到并编辑文件后,我们就可以进入调试时间了!
首先开始调试文件,格式上面已经说过了。
按下回车以后会有很多英文蹦出来,不用担心,这些都是在讲 \(GDB\) 版权(
调试需要记住一些调试指令:
全称 | 简写 | 作用 |
---|---|---|
\(\text{list}\) | \(\text{l}\) | 显示当前程序停止的位置附近的源代码(我试过,我的电脑上最多显示 \(10\) 行) |
\(\text{run}\) | \(\text{r}\) | 从头开始运行程序,直到断点处为止(没有断点的话直接运行完) |
\(\text{break}\) | \(\text{b}\) | 设置断点,后面跟行号,也可以跟函数名(在这个函数这个参数的状态时停止)、\(\text{if}\) 语句(当……时停止) |
\(\text{next}\) | \(\text{n}\) | 执行下一条语句,但不进入函数 |
\(\text{step}\) | \(\text{s}\) | 执行下一条语句,如果有函数则执行函数内语句 |
\(\text{print}\) | \(\text{p}\) | 在运行到当前行数时显示一个变量或数组的值 |
\(\text{display}\) | \(\text{disp}\) | 永久显示一个变量或数组的值,直到程序结束 |
\(\text{clear}\) | \(\text{cl}\) | 清除某一行的断点,如果什么都不跟则是清除所有断点 |
\(\text{undisplay}\) | \(\text{undis}\) | 后面跟变量的编号(之前显示的 \(\$ 1\) 等,只需要输入序号,不需要输入 \(\$\)),可以不再显示某个变量的值 |
\(\text{watch}\) | \(\text{wa}\) | 后面跟变量名,可以在变量发生变化时停止程序运行并输出变化前后的值 |
\(\text{continue}\) | \(\text{c}\) | 在程序当前停止的为止继续运行,直到运行到下一个断点处 |
\(\text{until}\) | \(\text{u}\) | 从当前位置开始,执行到 \(\text{until}\) 要求的行号或者函数名或者 \(\text{if}\) 条件(不懂得可以看 \(\text{break}\) 语句) |
\(\text{Enter(回车)}\) | \(\text{无}\) | 寻找从输入该回车上方最近的一条明确指示了的语句,执行它 |