前言
调试对程序员来说是必不可少的,没人能保证一辈子写的代码一个bug都没有。所以学会调试,很重要。
没有手绘封面了,apple pencil丢了。。。
1. 调试
1.1 调试是什么?
调试(Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序
错误的一个过程
1.2 怎么调试?
- 发现错误
- 通过 隔离 、消除 等方式,定位错误
- 确定错误原因
- 思考解决办法
- 实现解决办法,对程序除错
- 重新测试
1.3 Debug 和 Release
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优
的,以便用户很好地使用。
2. Windows下的调试
2.1 调试环境
在环境选项中选 Debug
2.2 调试快捷键
F5
启动调试
常用于跳到下一个断点
F9
创建/取消断点
让程序在你希望的地方(至少是有意义的代码处)停下来
F10
逐过程
过程:一次函数调用,一条语句,都是一个过程
F11
逐语句
一次只执行一条语句,常用于进入函数内部
Ctrl + F5
不调试,直接执行程序
2.3 调试的重要部分:“监视”
开始调试后,在“调试” – “窗口” 里,可以任意找到想监视的信息
3. 调试实例
3.1 例1
#include<stdio.h>
int main()
{
int i = 0;
int n = 0;
int sum = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
int j = 0;
for (j = 1; j <= i; j++)
{
ret *= j;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
结果:15
3! + 2! + 1! = 6+2+1 = 9
为什么得到15?
}
对 ret 的需求是每次存储 n 的阶乘,但是调试发现,ret 的值一直累加,正确代码应该是这样:
#include<stdio.h>
int main()
{
int i = 0;
int n = 0;
int sum = 0;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
int ret = 1;
int j = 0;
for (j = 1; j <= i; j++)
{
ret *= j;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
调试过程中,时刻观察程序走向是不是和脑中构思好的一致
3.2 例2
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int i = 0;
for (i = 0; i <= 12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
结果:死循环/报错(越界访问)
是的,这里越界访问了,但是根据 vs 的内存使用规则,越界访问的 arr[12] 刚好是 i ,每当程序要结束 , i 就又被赋成 0 ,导致死循环
vs在栈区使用空间,总是从高地址开始使用
vs数组的使用中,随着下标增加,地址由低到高
*静态区是右下角的方格,不知道怎么跑上去了 -_-
4. 如何写出好,还易于调试的代码?
4.1 优秀的代码
- 运行正常
- bug很少
- 效率高
- 可读性高
- 可维护性高
- 注释明了
- 文档齐全
4.2 如何向优秀代码靠近?
- 使用assert(断言,我视它为保安)
- 尽量使用const
- 良好代码风格(适合的缩进,空格之类)
- 添加必要的注释(明了简洁)
- 避免代码的陷阱
4.3 优秀代码示例(多向优秀代码学习)
#include<stdio.h>
#include<assert.h>
//************
//= char* my_strcpy( dst , src ) - Copy one string over another
//
//= Purpose:
// Copies the string "src" into the spot
// specified by dest ; assumes enough room.
//
//= Entry:
// char* dst - string over which "src" is to be copied
// const char* src - string to be copied over "dst"
//
// = Exit:
// The address of "dst"
//**************
char* my_strcpy(char* dst, const char* src)
{
char* tool = dst;
//两个形参都不为NULL
assert(dst && src);
while (*tool++ = *src++)// \0 = 0
{
;//copy src over dst
}
return dst;
}
int main()
{
char str1[] = "Coding is funny!";
char str2[30] = { 0 };
printf("%s\n", my_strcpy(str2, str1));
return 0;
}
4.4 const 的作用
const:赋予常量属性
看看例子(参考前辈的例子)
#include <stdio.h>
//代码1
void test1()
{
int n = 10;
int m = 20;
int *p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test2()
{
//代码2
int n = 10;
int m = 20;
const int* p = &n;
*p = 20;//ok?
p = &m; //ok?
}
void test3()
{
int n = 10;
int m = 20;
int *const p = &n;
*p = 20; //ok?
p = &m; //ok?
}
int main()
{
//测试无cosnt的
test1();
//测试const放在*的左边
test2();
//测试const放在*的右边
test3();
return 0; }
总结:就是看 const 在谁的左边
- 在 * 左边,就是修饰指针变量指向的内容:
指针指向的值被赋予常量属性- 在 * 右边,就是修饰指针变量:
指针变量里的地址被赋予常量属性
5. 编程常见错误
5.1 编译型错误
多是语法问题,双击错误信息,根据错误信息修正即可
5.2 链接型错误
多是 标识符不存在 、 拼写错误 、 没有声明
5.3 运行时错误
逐步调试,细心对照:“是否和脑中构思的相同?”
5.4 通向 “无bug” 的捷径
不断积累排错经验,两点之间线段最短,脚踏实地就是最快的捷径
今天的分享就到这里
培根的blog,不断进步
:粉丝、阅读量不是初衷,写博客是为了倒逼自己输出,提升自己,不浮躁,不虚荣