【C语言】调试【保姆级教程】 visual stdio 2022 + Dev-c++ + vscode三种编译器的调试方法

什么是调试?

调试是编写好程序后,通过各种手段进行查找错误并且解决错误的过程。其中调试又分为很多种方法,比如断点调试,输出调试,gdb调试。本篇文章主要介绍如何在devc,visual stdio 2022,以及vscode中使用断点调试和输出调试的方法。

同时在文章的最后一部分,会大致介绍一下,在编译运行过程中,一些常见的报错,错误原因以及相应的解决方法。

为什么要调试?

因为写完程序之后,很难保证程序一定能够顺利运行,以及运行的过程和结果是我们所设想的结果,因此我们需要去调试,然后调整代码,使得代码的运行是我们想要的结果。很多时候,难点不在于写代码,而在于调试代码,让代码能够正常跑起来。尤其是对于初学者来说,可能咔咔上来就是一顿写,写的挺爽的,然后一运行,要么是到处报错,要么就是运行出一些匪夷所思的结果,此时调试的作用就尤为重要,它能够让我们快速找到错误所在,少掉一点头发。

如何去调试?

本篇保姆级教程文章将通过手把手,脚把脚的方式,亲手亲脚的教会你基础调试的方法。

首先先介绍断点调试。但其实对于我个人而言,比较少去使用断点调试,更多的是输出调试。


visual stdio 2022

一、最基本的调试过程:

1、我们首先先在visual stdio中写一段简单的代码:

#include<iostream>
using namespace std;

int main() {
	int a, b, c;
	cout << "请输入a,b,c的值:" << endl;
	cin >> a >> b >> c;
	cout << "a的值为:" << a << endl;
	cout << "b的值为:" << b << endl;
	cout << "c的值为:" << c << endl;
	cout << "a+b的值为:" << a + b << endl;
	cout << "a-c的值为:" << a - c << endl;
	cout << "b*c的值为:" << b * c << endl;
	return 0;
}

2、当我们直接运行的时候,控制台会直接返回所有的输出结果。

 3、现在我们想要知道每一步操作的运行情况,那我们可以设置断点。


什么是断点?

断点是指在特定点暂停程序执行的特殊标记,当代码运行到断点所在这一行的时候,程序的运行就会暂停下来。

设置断点的方法:

        方法一:点击想要设置断点的行,然后按一下 F9 。这个时候在这一行的最左边,就会出现一个红色的圈圈,就说明这里已经设置好了断点。如果再按一下 F9 的话,那么就会取消在这一行设置断点,同时这个红色圈圈也会消失。

        注意,如果是平板电脑的话,那么要在 Fn 这个键亮着的时候按 F9 才有效,如果 Fn 没亮的话,按 F9 是打开关闭 wifi 。

 方法二:直接鼠标点击对应行的最左侧的位置(和上面那个图的红点一样的位置),按一下后也会出现这个断点红圈。


 4、设置好断点后,我们再来尝试重新运行一下代码。按快捷键 F5,或者点击“本地Windows 调试器”。 

此时我们可以看到,在我们输入完\(a,b,c\)的值后,程序运行就停止下来了,并没有像最开始我们没有设置断点的时候那样啪啪啪的就把下面的六个结果输出出来,而是停在原地。

控制台的运行情况

同时可以看到在第一个端点处多了一个箭头,这个箭头表示当前程序运行到了这个地方,还没运行,暂停在了这里。 

编译器界面

6、接下来,我们再按一下 F5 快捷键,或者是点击一下“继续”(也就是刚才“本地Windows 调试器”,它现在变成了“继续”)。然后程序就会再次继续运行。

此时我们可以看到,控制台中输出了接下来的三行结果,然后又停下来了。

然后在编译器界面中,那个箭头跑到了下一个断点当中。

 

 7、我们再按一下 F5,此时已经没有下一个断点了,所以代码直接运行完毕。

 就这样,一次对代码的调试就完成了。


有关于断点的一些补充:

我们有的时候在进行断点测试的的时候,可能会出现设置了很多断点,但是我并不需要每一次都每个断点都去测试。或者说,根据每次测试的范围不同,每次都要重新取消并且设置设置断点挺麻烦的。这个时候,我们可以进行如下操作。

点击“调试”,“窗口”,“断点”。

然后下方就会出来一个和断点有关的窗口,通过这个窗口就可以很方便的对断点进行管理。

只要我们把前面的勾勾取消掉,然后再进行调试,这些取消掉的断点就会暂时失去它作为断点的功能啦~


接下来,继续深入讲解,其余的调试操作。

F5启动调试,运行程序直到下一个断点处。
Crtl + F5直接运行一遍代码。
F9在当前这一行处设置或取消断点
F10逐过程运行代码,这个过程,可以是一行语句,也可以是一次函数的调用
F11逐语句运行代码,每次运行一句代码,如果有函数的话,就会进入到函数的内部。

 二、逐过程和逐语句运行代码的区别:

我们将上述代码稍作修改,计算 \(a + b\) 的值的时候,不直接计算,而是调用一个函数。

#include<iostream>
using namespace std;

int add(int x, int y) {
	int ans = x + y;
	return ans;
}

int main() {
	int a, b, c;
	cout << "请输入a,b,c的值:" << endl;
	cin >> a >> b >> c;
	cout << "a的值为:" << a << endl;
	cout << "b的值为:" << b << endl;
	cout << "c的值为:" << c << endl;
	cout << "a+b的值为:" << add(a, b) << endl;
	cout << "a-c的值为:" << a - c << endl;
	cout << "b*c的值为:" << b * c << endl;
	return 0;
}

首先我们逐过程运行代码,也就是每次我们都按 F10进行操作。

可以看到在第9行也就是main函数开始的那一行出现了一个箭头,说明此时代码运行到了这一行。

接下来我们继续按 F10 ,当代码运行到第12行的时候,控制台会自动弹出来,让你输入\(a,b,c\),如果你不输入,直接把控制台叉掉了的话,那么就会直接停止调试,我们乖乖的输入\(a,b,c\)的值,然后继续按 F10。我们每按一次 F10,控制台都会输出对应行的结果直到所有代码运行完毕。

 接下来我们进行逐语句运行代码。也就是每次我们都按 F11 进行操作。

前后的操作都和逐过程运行代码一致,直到运行第十六行的时候出现了差异。

 

箭头从第十六行飞到了第四行,也就是第十六行调用的函数中,然后再函数中走完后,又重新回到了第十六行,然后继续向下运行。

总结一下,逐过程运行代码就是一行行的顺序的运行;逐语句运行代码,就是一条条语句按逻辑顺序运行下去。如果要进入函数内部的话,就要用逐语句运行代码。

三、监视

这个功能可以让我们实时的看到每个变量的当前值,同时我们还能实时的去改变每个变量在任意时刻的值。

(为了更直观的让大家看到“监视”实时查询变量当前值的功能,因此下方的这个代码比上面原来的代码多增加了第十六行这一行。)

#include<iostream>
using namespace std;

int add(int x, int y) {
	int ans = x + y;
	return ans;
}

int main() {
	int a, b, c;
	cout << "请输入a,b,c的值:" << endl;
	cin >> a >> b >> c;
	cout << "a的值为:" << a << endl;
	cout << "b的值为:" << b << endl;
	cout << "c的值为:" << c << endl;
    a++;
	cout << "a+b的值为:" << add(a, b) << endl;
	cout << "a-c的值为:" << a - c << endl;
	cout << "b*c的值为:" << b * c << endl;
	return 0;
}

1、当我们开始调试的时候,点击“调试”“窗口”,中间的“监视”,四个“监视”随便点开一个就行。

注意:一定是要开始调试之后再去打开,开始调试后,“调试”的“窗口”那里才会多出来一堆按钮。如下图是开始调试前,按钮肉眼可见的少了很多。

2、打开监视后,下方的框框中就出现了监视,他让我们添加要监视的项。

我们点击一下“添加要监视的项”,然后输入我们想要变量的名称,我们就可以对这个变量的值进行监视了。比如我们这里一口气把所有变量都添加进去。

这里是我已经输入了\(a,b,c\)的值,然后下方“监视”处也告诉了我们\(a,b,c\)的值。

3、同时我们也注意到,\(x,y,ans\)处是飘红的,报错说未定义标识符。这是因为我们现在是在主函数中运行,而\(x,y,ans\)是函数中的局部变量。我们继续往下运行。

4、当我们运行完第16行后,程序的运行让\(a\)的值发生了变化,在监视中我们也能看到\(a\)的值立马随之发生改变。也就是说,我们能够从监视中,清楚的看到每一步程序运行时,我所监视的变量的值的变化以及当前变量的值是多少。(除了在监视中查看变量当前值,还可以把鼠标移动到变量上面,稍停一会,就会弹出来个小框,上面会写着这个变量的当前值,因为截图的时候截不到这个框,他会在我按截图快捷键的时候消失,所以这里就不给图了哈)。

5、当我们按语句运行代码时,箭头飞向函数内部的时候,可以看到,我们监视的\(x,y,ans\)不在报错说未定义标识符,因为这个时候我们已经进入了函数内部了,所以这些变量当前是有定义的了。但同时新的问题也来了,这个\(x,y,ans\)的值好像不是我所认为的值啊,这个\(y\)都已经上亿了。这是因为我们刚刚调用函数,尚未对传进函数的参数进行赋值,现在是系统默认的值。

6、当我们运行到这个函数的末尾的时候,\(x,y,ans\)的值就是我们所预期的那样了。

7、我们还可以实时的去改变当前变量的值

比如现在我们记录到\(a,b,c\)的值分别为\(2,2,4\)。如果让我们输出\(a-c\)的结果,那么得到的答案应该是\(-1\)。但是这个时候我们在下方的“监视”中,直接点击一下\(a\)后面的值,然后修改为\(200\),我们再来看看运行结果。

输出结果变成了\(197\),说明我可以在运行的过程中去实时的修改和调整某些变量的当前值。


四、数组与循环 

这一小节主要是通过一个简单的实例讲解如何调试循环语句,以及监视数组。

假设我们现在要做这样一个任务,输入一个数列的元素个数,以及每个元素的大小,然后要求这个数列的所有元素之和。初学者小雁很快就写出了如下的代码,啪的一下就写完了,很快啊。

#include<iostream>
using namespace std;

int a[520];

int main() {
	cout << "请输入数列元素个数:" << endl;
	int n; cin >> n;
	cout << "请输入每个元素的值:" << endl;
	for (int i = 0; i < n; i++) {
		cin >> a[i];
	}
	int sum = 0;
	for (int i = 1; i < n; i++) {
		sum += a[i];
	}
	cout << "数列的总和为:" << sum << endl;
	return 0;
}

然后小雁自己尝试运行了一下,结果一看不太对啊,总和应该是\(15\),怎么变成了\(10\)呢。

 于是小雁决定用刚刚学到的调试方法,对代码进行调试。

 在循环中,如果用 F5 进行调试,如果断点设置在循环的头行上(比如第10行的那个断点),那么只有第一次到这个循环的时候会停下来,如果断点是设置在循环中的某行语句上(比如第15行的那个断点),那么每一次循环都会暂停一次。

小雁看了看两个循环,发现两个循环都能正常运作循环,感觉好像没有什么问题的样子,于是决定去监视数组中的每一个元素的值,以及\(sum\)的变化。

 在数组中,我们可以去直接监视整个数组,也可以只针对其中的某个值进行监视。

当我们完成数组的输入之后,我们不仅可以直接监控数组中的特定的元素,如\(a[0],a[1],a[n-2]\),还可以直接监视整个数组\(a\)来查看数组中的所有元素的值。

然后我们继续进行逐语句运行代码,发现第一次循环的时候就出现了问题,数组\(a\)中的第一个元素是5,但是\(sum\)加完第一个元素后的值竟然是4,那么说明错误出现在这个地方附近。于是有了突破口之后,小雁很快就发现了问题所在:他在输入数组的时候,数组下标是从0开始的,可是在进行累加过程的时候,却是从数组下标为1的地方开始,于是每次求和的时候都会漏加上第一个元素。


五、结构体

结构体和前面的数组其实差不多了,监视的时候,可以监视整个结构体数组,也可以监视某个特定的结构体,还可以监视某个特定的结构体中的特定成员。

#include<iostream>
using namespace std;
struct node {
	int x;
	int y;
	int z;
}no[1314];
int main() {
	int n; cin >> n;
	for (int i = 0; i < n; i++) {
		cin >> no[i].x >> no[i].y >> no[i].z;
	}

	for (int i = 0; i < n; i++) {
		cout << no[i].x + no[i].y + no[i].z << endl;
	}
	return 0;
}

 输入:

 监视中的反馈:

 注意一定不能直接监视成员变量,要说清楚监视的是谁的成员变量,不然就会出现上面的未定义标识符“x”的情况。


注意事项:

有些版本的visual stdio可能会出现点击运行代码,运行完了以后,编译器的那个黑色命令控制台一闪而过的情况。这种情况就是代码运行完了,然后编译器就自动关闭了控制台。这种情况只要在代码的末尾中多加一句代码就好。

这一行代码就是在运行代码的时候,当运行到这一行的时候,控制台会停下来,然后输出“请按任意键继续”,当你按了任意键以后才能继续往下运行。从某个角度来看,也挺像断点的哈哈。

当然加了以后,要注意,当调试的时候运行到这一行代码后就会卡在这里无法操作,必须要在命令控制台处按任意键后,才能继续调试或者运行。

    system("pause");



Dev-c++(蓝色的那个)

这里大部分的断点调试操作和visual stdio中的操作差不多,因此主要讲三部分,如何打开dev-c的调试;dev-c中的哪些键位对应上述所讲的visual stdio中的哪些键位;以及一些用dev-c来进行调试时的坑或者是需要注意的地方。

Dev-c++中调试的使用方式:

1、打开dubug模式

一般来说,我们打开编译器的时候,默认的是64位Release版本。

 我们需要切换为64位Debug版本,才能进行调试操作。

 

 2、设置断点,我们只需要在行号那里点一下就可以设置或取消断点了。设置完断电后,设置了断点的整一行都会飘红。

 3、开始调试。

上面的这几个按钮,暂时来说,记住并且会用这五个就行。编译是说,让电脑先弄懂你写的是什么东西。运行就是让电脑跑一遍你的代码。编译并运行就是先编译然后编译完了马上给你运行一下。调试是开始调试功能。停止(因为还没开始调试所以是灰的,等开始调试了就变成红色的了)就是停止调试,退出程序运行。

我们每次写完代码之后,如果想要跑一下看看结果,一定要先点编译,再按运行或者直接点编译并运行就可以了,更方便,记住三个按钮的作用就行,其他的扣掉当不存在就好),编译完了以后再去点击调试。如果直接点击调试或者运行,那么实际上运行的是你上一次按了编译的程序。

举个例子,你写了个程序输出"Hello,wor;d!",然后按了一下编译并运行,然后你发现你的world这个词拼错了,于是你修改正确后,没有按编译,而是直接运行或者点击调试,那么还是会输出原来错误拼写的单词,要按了编译,才能运行修改后的新代码。

4、进入调试模式后,点击下方框格的“调试”,里面就有类似我们在visual stdio的操作按钮。

其中蓝色那一行表示当前程序运行到的地方,和visual stdio中的那个黄色小箭头一个意思。

 大部分情况下能用到的就是下面七个按钮:

dev-c中的调试按钮对应visual stdio中的调试按钮
调试[D]点了一下后回重头开始调试
停止执行结束调试,直接停止
添加查看[A]相当于“监视”功能,可以实时查看变量的值
下一步[N]逐过程运行代码[F10]
单步进入[i]逐语句运行代码[F11]
跳过[S]直接运行到下一个断点[F5],相当于按“继续”
跳过函数如果进入了函数中(逐语句运行代码),按一下这个按钮后会直接跑完这个函数

5、添加查看[A]的具体用法

方法一:点击下方报告窗口中的“添加查看”,然后输入要监视的变量名,点击“OK”,点击完后,在右边的“调试”中,就会出现这个变量的当前值了。

 方法二:直接鼠标右键右边“调试”中的空白处,然后再点击“添加查看”即可。

 修改数据也是一样,鼠标左键点击一下要修改值的变脸,变蓝了就行,然后右键点击“修改数据”。


Dev-c++中调试时可能遇到的问题:

问题一:弹窗说“项目没有调试信息,您想打开项目调试选项并重新生成吗?”,并且点击“Yes”后会伴随着电脑一阵抽搐,然后就把整个编译器关掉了。

出现原因:忘记打开Debug模式,用的是Release版本。

解决方法:详见上述“Dev-c++中调试的使用方式”中的第一步。

 问题二:下方的报告窗口不见了,进行不了调试操作。或者是点了一下报告窗口中的任意一个调试操作后,整个报告窗口直接消失了。

出现原因:自己之前关掉了。

解决方法:

方法一:右键左边的“调试”或者“查看类”,然后点击“浮动报告窗口”。 

方法二:在上方的“视图”中也可以打开“浮动报告窗口”。

 然后下面就出来了这个报告窗口了,可以放大缩小,拖动它就行。默认弹出的是“编译日志”,点击旁边的“调试”后,就能看到文章上述讲的那些按钮了。



VScode

我们还是先在VScode中写下这篇代码

#include<iostream>
using namespace std;

int add(int x, int y) {
	int ans = x + y;
	return ans;
}

int main() {
	int a, b, c;
	cout << "请输入a,b,c的值:" << endl;
	cin >> a >> b >> c;
	cout << "a的值为:" << a << endl;
	cout << "b的值为:" << b << endl;
	cout << "c的值为:" << c << endl;
    a++;
	cout << "a+b的值为:" << add(a, b) << endl;
	cout << "a-c的值为:" << a - c << endl;
	cout << "b*c的值为:" << b * c << endl;
    system("pause");
	return 0;
}

1、debug的模块就在左边从上到下数的第四个(图中已经红圈框起来了)。

 2、打开debug模式。直接点击“运行”中的“启动调试”,或者在debug模式下点击“g++.exe-生成和调试活动文件”旁边的绿色三角形。

 3、进入调试以后,大部分的操作就和前面的差不多了

①:变量区,在这里可以随时更改(鼠标右键点击)变量的值,也可以把变量添加到监视中。

②:监视区,在这里可以随时查看你添加了监视的变量的值。

 

③:断点区,在这里点击旁边的勾勾就可以选择当前调试用哪些,停用哪些断点。

 点击那个“笔”一样的按钮,可以设置断点的触发条件,当运行的程序满足你设定的条件的时候才会启用这个断点。设置完后红点中会多一个等号。

 比如我们可以给两个断点分别设置如下的两个条件

 

 接下来我们运行程序的时候,如果我们还是输入的1,2,3,那么当我们使用“继续[F5]”来进行调试的时候,就只会在第一个断点处停下来,然后略过第二个断点,直接在第三个断点处停下来,因为当程序运行到第一个断电的时候,符合条件,因此会停下来,当运行到第二个断点的时候,不满足表达式,因此这个断点不会被启用。

④:按钮区,里面的按钮和visual stdio以及dev-c中的按钮都差不多。



输出调试:

输出调试是一种普遍而常用的调试手段,就是在需要测试的地方打印输出信息,以此达到观测某个变量的值或者某些条件的判断的目的。

举个例子,我们需要写一个求平均数的代码,第一行输入一个数字\(n\)表示一共有几个数,接下来第二行输入\(n\)个数字。输出一个数字表示这些数的平均数。

输入:10
1 2 3 4 5 6 7 8 9 10

输出:5.5

#include<bits/stdc++.h>
using namespace std;

int a[110];

int main() {
    int n; cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += a[i];
    }
    
    sum /= n;
    cout << sum << endl;
    
    return 0;
}

然后我们试运行了一下,发现输出结果竟然是5。于是为了找到错误的原因,我们可以使用输出调试的方法。我们可以在代码中加入如下几行代码。

#include<bits/stdc++.h>
using namespace std;

int a[110];

int main() {
    int n; cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    //这一段是测试输入的数字是否正常存入数组中
    //将数组中的元素全部打印出来看看是否正常
    for (int i = 1; i <= n; i++) {
        cout << a[i] << " ";
    }
    cout << endl;

    int sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += a[i];
        //这一行是看每次sum累加a[i]是否正常
        cout << "i = " << i << " , sum = " << sum << endl;
    }

    sum /= n;
    cout << sum << endl;
    system("pause");

    return 0;
}

这个时候我们再去看输出信息,就能够对代码的运行情况有一个很直观的结果。

 这个时候我们就能够发现,数组的输入存储没有问题,\(sum\)的累加计算也没有问题,问题错在最后面的总和除以数量,小数点没了。所以我们可以知道,应该用double类型而不是int类型。正确代码如下:

#include<bits/stdc++.h>
using namespace std;

int a[110];

int main() {
    int n; cin >> n;
    for (int i = 1; i <= n; i++) {
        cin >> a[i];
    }

    //这一段是测试输入的数字是否正常存入数组中
    //将数组中的元素全部打印出来看看是否正常
    // for (int i = 1; i <= n; i++) {
    //     cout << a[i] << " ";
    // }
    // cout << endl;

    double sum = 0;
    for (int i = 1; i <= n; i++) {
        sum += a[i];
        //这一行是看每次sum累加a[i]是否正常
        // cout << "i = " << i << " , sum = " << sum << endl;
    }

    sum /= n;
    cout << sum << endl;
    system("pause");

    return 0;
}

特别需要注意的一点是,提交代码的时候,一定要把输出调试的那几行代码删掉或者注释掉,不然直接叫上去的话就会WA。


VScode中其他好用的宝藏功能

这里只讲几个比较基础的功能,因为本篇文章的重点在于讲解这三个编译器的基本调试方法,所以其他的很多好用的功能就不在次介绍了。

1、在vscode中多写注释有一个很明显的好处,那就是当我们把鼠标移到变量上面的时候,就能够随时查看我们自己给这个变量起的注释。

2、当我们按住键盘上“Crtl”键时,鼠标左键单击某个变量,然后光标就会自动跳转到你初始化定义这个变量的位置。

 3、当你再次按住“Crtl”键然后单击定义的变量时,就会再次弹出一个窗口,右边是在这篇代码中哪些地方出现过用过这个变量,中间的蓝色主体部分就是等于给你开了个双屏去看代码。

 



$$\color{blue}{绝}\color{yellow}{域}\color{red}{殊}\color{purple}{方}\color{green}{画}\color{gold}{秋}\color{cyan}{雁}\color{lavender}{,}\color{magenta}{笑}\color{orangered}{看}\color{springgreen}{春}\color{gray}{风}\color{aquamarine}{阅}\color{violet}{千}\color{greenyellow}{帆}\color{thistle}{。}$$

如果大家文章中存在疑问或者解释不清楚额地方,可以在下方评论区中提问。如果我的文章中出现了什么错误,也烦请各位大佬指出,我马上修改。谢谢各位!

  • 41
    点赞
  • 45
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值