目录
前言
注意:
正文
调试的基本步骤为:
对于问题(Bug)的发现一般有以下三种方式:
Debug版本与Release版本
Debug(调试版本)
Release(发布版本)
调试使用的各种快捷键
F5:
Ctrl+F5
F9
F10
F11
断点
调试时查看程序信息
要想写出好代码,就得多动手、多调试才能有进步!
调试示例1
调试示例2
预防错误
优秀的代码往往具有以下特征:
常见的Coding(代码)技巧:
这里举一个绝妙代码的例子
编程常见错误
总结
鸣谢
前言
有一句话是这样说的:一个入门级的程序员可能发80%的时间在写代码,花20%的时间在调试;而一个老练的程序员则是花20%的时间写代码,花另外80%的时间在调试上面。可能这种比喻比较夸张,但能很好的说明调试的重要性,一段优秀的代码是由一个程序员构思+不断重复的调试找错误共同组合而成的,话不多说,我们直接进入调试。
注意:
文中图片上的代码所引用的头文件#include
正文
调试(Debug/Debugging),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程,一名优秀的程序员就像一名出色的侦探一样,把调试当作破案。
调试的基本步骤为:
1.发现程序错误的存在
2.以隔离、消除等方式对错误进行定位
3.确定错误产生的原因
4.提出纠正错误的解决方法
5.对程序错误予以改正,重新测试
对于问题(Bug)的发现一般有以下三种方式:
1.程序员自己调试发现——自己解决,没啥影响
2.软件测试人员测试发现——反馈给程序员解决,影响不大
3.用户使用时发现——影响严重!这里@下某凡
Debug版本与Release版本
这两个版本是我们写程序时编译器所提供的,Debug(调试版本)是开发人员用的版本,Release(发布版本)是测试人员和用户所用的版本,两者虽说代码内容一样,但功能上差别还是挺大的。
Debug(调试版本)
这个版本主要特征就是可供开发者进行调试,包含了调试信息,并且不作任何优化,便于程序员调试程序, 同时因为其内含了调试信息,代码所产生的文件也会更大
Release(发布版本)
发布版本一般是由已经写好的程序打包而成,往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优的,以便用户很好的使用
调试使用的各种快捷键
在VS中提供了各种快捷键,比如最常用的Ctrl+F5让代码运行起来,下面我就简单介绍下使用频率极高的几个快捷键
F5:
启动调试快捷键,经常用来直接跳到下一个断点处。
Ctrl+F5
开始执行但不进行调试快捷键,如果你想让程序直接运行起来而不调试就直接使用的话,可以使用这个快捷键
F9
创建断点和取消断点的重要快捷键,可以在程序的任意位设置断点,这样就可以使得程序在想要的位置随意停止执行,继而一步步的执行下去
F10
逐过程快捷键,通常用来处理一个过程,一个过程可以是一次函数调用,或者是一条语句
F11
逐语句快捷键,就是每次都执行一条语句,但是这个快捷键可以使我们的执行逻辑进入到函数内部,这是最常用的快捷键之一。
限于篇幅问题,还有很多的快捷键这里不能给大家一一介绍,下面这个链接是一个大牛总结的快捷键合集,挺全的,感兴趣的同学可以点进去看看 VS中常用的快捷键_MrLisky的博客-CSDN博客_vs快捷键
断点
既然上面的F9提到了断点,那么我就在这给大家介绍下,断点是程序运行时截断的点,用于在调试中跳过一些没必要的语句,直接进入问题区域寻找问题,断点出现于代码的左侧,是一个红点,有点红绿灯的感觉,不让程序继续执行,断点还可以进行条件限制,比如在循环中,我想停到第五次循环,这时只需要在对应语句的断点上单击鼠标右键,选择条件并设置即可。
调试时查看程序信息
调试时我们想要一个工具来对程序中的各种变量进行监视,或者说我们想看看程序运行时的内存信息,都可以通过调试-窗口中的各种窗口来进行监视
要想写出好代码,就得多动手、多调试才能有进步!
调试示例1
下面是一个逻辑错误代码,语法没问题,能跑。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //计算n的阶乘相加 int main() { int n = 0; scanf("%d", &n); int i = 1; int ret = 1; int sum = 0; int j = 0; for (j = 1; j <= n; j++) { //ret = 1;不加就会出错 for (i = 1; i <= j; i++) ret *= i; sum += ret; } printf("%dn", sum); return 0; } |
示例调试纠正
想要找到问题也很简单,一步步调试就行
经过调试找出问题:ret为初始化
改正
可以看到代码运行结果正确,这就是利用调试解决问题的一个例子
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //计算n的阶乘相加 int main() { int n = 0; scanf("%d", &n); int i = 1; int ret = 1; int sum = 0; int j = 0; for (j = 1; j <= n; j++) { ret = 1;//不加就会出错 for (i = 1; i <= j; i++) ret *= i; sum += ret; } printf("%dn", sum); return 0; } |
调试示例2
示例2是一个很有意思的代码,很多人可能一眼就能看出它越界的问题,认为会直接报错,但实际情况真的如此吗?
老样子,直接调试
问题已经找出来了,经过鹏哥的详细讲解,我重复给大家看
| 1 2 3 4 5 6 7 8 9 10 11 12 | //初始化数组-死循环 int main() { int i = 0; int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; for (i = 0; i <= 12; i++) { arr[i] = 0; printf("hehen"); }//虽然越界,但一直在死循环,没有机会报错 return 0; }//死循环,需要在x86平台下才有用 |
至于为什么会出现这个问题,有两个原因:
1.i 和 arr 是局部变量,局部变量是放在栈区之上的,栈区的使用习惯是:先使用高地址空间,再使用低地址空间。栈区和栈不是同一个概念
2.数组随着下标的增长,地址是由低到高变化的
在vs x86平台下,i 和 arr 间隔有2个整型,arr[12]可触发死循环
在vc 6.0 平台下,i 和 arr 间隔有0个整型,arr[10]就可以触发死循环
在 GCC 平台下,i 和 arr 间隔有1个整型,arr[11]可触发死循环
预防错误
有错误就会有解决错误的方法,通过编码的技巧,减少错误
优秀的代码往往具有以下特征:
1.代码运行正常
2.Bug很少
3.效率高
4.可读性高
5.可维护性高
6.注释清晰明了
7.文档齐全
常见的Coding(代码)技巧:
1.使用assret
2.尽量使用const
3.养成良好的编程风格
4.添加必要的注释
5.避免编码的陷阱,比如上面那个例2
这里举一个绝妙代码的例子
版本1,欠优化
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //模拟strcpy函数 void my_strcpy(char* dest, char* src) {//版本1 while (*src != 0) { *dest = *src; dest++; src++; } *dest = *src; } int main() { char arr1[20] = "xxxxxxxxxxxxxxx"; char arr2[] = "Hello Word"; my_strcpy(arr1, arr2); strcpy(arr1, arr2); printf("%sn",arr1); return 0; } |
版本2,稍微优化了下,但还不够
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | void my_strcpy(char* dest, char* src) {//版本2 while (*src != 0) { *dest++ = *src++; } *dest = *src; } int main() { char arr1[20] = "xxxxxxxxxxxxxxx"; char arr2[] = "Hello Word"; my_strcpy(arr1, arr2); strcpy(arr1, arr2); printf("%sn",arr1); return 0; } |
版本3,称得上是绝妙代码了,集成度高,风格简洁
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | void my_strcpy(char* dest,char* src) {//版本3 while (*dest++ = *src++) { ; } } int main() { char arr1[20] = "xxxxxxxxxxxxxxx"; char arr2[] = "Hello Word"; my_strcpy(arr1, arr2); strcpy(arr1, arr2); printf("%sn",arr1); return 0; } |
为什么说版本3是绝妙代码呢?因为它巧妙的运用了C语言中的多个特征,做到了一行代码解决问题,实在是优雅实用,值得学习!
版本4,在版本3的基础上再完善
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | //模拟strcpy函数 //版本4 void My_strcpy(char* arr,char* ch) { assert(arr!=NULL);//断言,防止空指针的传入 assert(ch != NULL); while (*arr++ = *ch++) { ; } } int main() { char arr[20] = { "XXXXXXXXXXXXXXXXX" }; char ch[] = { "Hello" }; My_strcpy(arr, ch); printf("%sn",arr); return 0; } |
代码4中使用了assert函数,这个函数可以在我们犯小错误时提醒我们错出在哪里
版本5,最终版
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //模拟strcpy函数 // 版本5 char * My_strcpy(char* arr,const char* ch) { assert(arr!=NULL); assert(ch != NULL); char* ret = arr; while (*arr++ = *ch++) { ; } return ret; } int main() { char arr[20] = { "XXXXXXXXXXXXXXXXX" }; char ch[] = { "Hello" }; printf("%pn", arr); printf("%sn",My_strcpy(arr,ch)); printf("%pn", My_strcpy(arr, ch)); return 0; }//模拟实现标准的strcpy库函数 |
在版本5中进一步完善了这个函数,包括 const 对不变量进行保护,char * 返回标准类库函数地址 经过五个版本的迭代,这个函数迎来了最终版,一个真正意义上的优秀代码!!!
健壮性(鲁棒性)——增加 const 让代码更靠谱
编程常见错误
常见错误一般分为三类:编译型、链接型、运行型
编译型错误:直接看错误提示信息(在绿色虚线处双击鼠标左键),解决问题,或者凭借经验解决,相对来说犯这种错误比较好解决
链接型:看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般都是原因标识符不存在或者拼写错误而引起
运行型错误:借助调试,逐步定位解决问题,最难搞的问题
温馨提示:代码千万条,细心第一条。编码不规范,调试两行泪
总结
本文就调试实用技巧做了一些阐述,希望能让大家对调试这个重要概念进一步理解。
学会调试,不再求人!
希望我的文章能够为大家再写代码时提供帮助!
希望各位大牛能够及时指出我文中存在的不足和错误,我好加以改正!
最后希望以后写的文章越来越好
鸣谢
感谢比特科技鹏哥所传授的知识,让我受益匪浅,写出这篇文章!@比特鹏哥



