注:linux开发环境调试工具是gdb,后期博客会介绍。
目录
2.4例题:我想知道153是否为自幂数,你是否在傻乎乎的一直按着F10 ,那当i=100000要按多久?
实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。(下面代码有错误)
my_strcpy函数设计返回值类型char*是为了实现函数的链式访问
程序员要掌握的重要技术,便是要学会调试
有的女人就像Windows 虽然很优秀,但是安全隐患太大。
有的女人就像UNIX 她条件很好,然而不是谁都能玩的起。
有的女人就像C# 长的很漂亮,但是家务活不行。
有的女人就像C++,她会默默的为你做很多的事情。
有的女人就像JAVA,只需一点付出她就会为你到处服务。
有的女人就像JAVA script,虽然对她处处小心但最终还是没有结果。
有的女人就像汇编 虽然很麻烦,但是有的时候还得求它。
有的女人就像 SQL,她会为你的发展带来莫大的帮助。
爱情就是死循环,一旦执行就陷进去了。
爱上一个人,就是内存泄露,你永远释放不了。
真正爱上一个人的时候,那就是常量限定,永远不会改变。
女朋友就是私有变量,只有我这个类才能调用。
情人就是指针,用的时候一定要注意,要不然就带来巨大的灾难。
1.1 调试是什么?有多重要?
所有发生的事情都一定有迹可循,如果问心无愧,就不需要掩盖也就没有迹象了,如果问心有愧, 就必然需要掩盖,那就一定会有迹象,迹象越多就越容易顺藤而上,这就是推理的途径。 顺着这条途径顺流而下就是犯罪,逆流而上,就是真相。
调试(英语:Debugging / Debug),又称除错,是发现和减少计算机程序或电子仪器设备中程序 错误的一个过程。
1.2 调试的基本步骤
发现程序错误的存在
以隔离、消除等方式对错误进行定位
确定错误产生的原因
提出纠正错误的解决办法
对程序错误予以改正,重新测试
2.1 Debug和Release
Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。
Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优 的,以便用户很好地使用。(不能调试)
2.2 下面程序在Debug和Release版本的区别
#include <stdio.h>
int main()
{
int i = 0;
int arr[10] = {0};
for(i=0; i<=12; i++)
{
arr[i] = 0;
printf("hehe\n");
}
return 0;
}
在debug版本中,程序死循环;在release版本中,程序可以执行
原因在于:变量在内存中开辟的顺序发生了变化,影响到了程序执行的结果。
代码运行的结果:大概率是死循环
首先arr只有10个元素,但是i循环到12次,arr[10]的时候已经越界访问了.首先i和arr是局部变量,先创建i,再创建arr,又因为局部变量都是放在栈区上的,那栈区的使用习惯是先使用高地址,再使用低地址
i如果先初始化就放在上面,arr就是数组,但是数组的下标的增长地址是由低到高来增长的,数组如果越界了,也许有一次越界到了i所在的地址,把i的地址和arr数组的地址改为了0,for循环就重新开始计算了,为什么说结果大概率是死循环呢,因为你不知道arr数组和i的地址中间差了几个字节,但是如果arr数组越界访问到i的话就会死循环
把arr和i放反也是因为栈区的使用习惯,i如果后初始化的话,i就是低地址空间,这样arr越界访问就永远不会访问到i的地址,所以他会打印完后程序崩溃
中间空了多少是编译器自己的写法问题,比如你去其他平台也许空的都不一样,那为什么会死循环不会报错呢?因为程序一直在跑,for循环没有停下来,虽然arr越界访问了,但是程序必须得先执行完才能够报错,得完成一件事情才行
int main()
{
int i = 0;
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
printf("%p\n", &i);
printf("%p\n", &arr[9]);
}
可以佐证,栈区空间使用习惯先使用高地址,再使用低地址
2.3 快捷键的使用方法
1.F5 开始调试
使用方法:直接按F5程序直接结束了,不能单独使用,需要配合F9(断点)使用
(多次按F5,会在原有逻辑断点处停下,例如循环从一次变为两次,停在下一次循环断点处),有人会发现按F5没有反应,这时候需要加上Fn+F5 or Fn+F10
2.Ctrl +F5 不调试,直接执行代码
3.F9 设置/取消 断点
使用方法:代码执行,按下F5,只有遇到断点F9处才停下
4.Ctrl + F 查找关键字
5.Ctrl +K+C 添加注释(配合全选使用) Ctrl + K+U取消注释
6.C语言中复制不需要选中复制,直接在需要复制的那一行按Ctrl+V,即可复制
此外,还有更多的快捷键,博客添加到下面
有同学发现,我打开调试->窗口,并没有发现监视是为什么?
这是因为,监视里的很多窗口是需要调试开始的时候才会显示出来,监视就是用来配合代码查错,要多练习
2.4例题:我想知道153是否为自幂数,你是否在傻乎乎的一直按着F10 ,那当i=100000要按多久?
int main()
{
int i = 0;
for (i = 0; i <= 100000; i++)
{
int tmp = i;
int n = 1;
//第一步判断是几位数
while (tmp / 10)
{
n++;
tmp = tmp / 10;
}
//计算每一位次方和
tmp = i;
int sum = 0;
while (tmp)
{
sum += pow(tmp % 10, n);
tmp = tmp / 10;
}
//3.判断
if (i == sum)
{
printf("%d ", i);
}
}
return 0;
}
我们可以使用F5配合F9,在F9断点处右键->条件(条件断点),输入i==153(条件设置),这时候按F5,你会发现i跳过了152位,直接来到153
2.5 F10(逐过程,遇到函数不会进入函数内部,直接执行完整函数内容)
F11(逐语句,遇到函数会进到函数,会执行代码的每个细节)
int Add(int a, int b)
{
return a + b;
}
int main()
{
int a = 1;
int b = 2;
int ret = Add(a,b);
printf("%d", ret);
return 0;
}
F10直到在函数Add处,按F11进入函数内部,配合监视窗口使用,此外监视还有自动监视(不推荐),自动监视帮你把所有的变量都放了出来,想要观察指定的数便容易出错,我们只需要在监视里面观察我们想要的数字即可
内存监视
调用堆栈,当函数调用逻辑复杂,可以查看堆栈的调用逻辑
void test2()
{
printf("hehe\n");
}
void test1()
{
test2();
}
void test()
{
test1();
}
int main()
{
test();
return 0;
}
3.1 通过调试找出代码问题
实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。(下面代码有错误)
int main()
{
int sum = 0;//保存最终结果
int n = 0;
int ret = 1;//保存n的阶乘
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
for(int j=1; j<=i; j++)//n的阶乘
{
ret *= j;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
//1!=1
//2!=2
//3!=6
//9
我们想求3!,输入3却发现结果是15,肯定是代码出了问题,于是我们按F10开始调试,再监视中输入我们想观察的变量
第一次执行,
代码没有问题,1!就是1
第二次执行,2!就是2,但是我们继续向下执行,却发现ret值是2,要知道ret的作用是1*2*...*n的作用,ret值变了,结果自然也就错了
int main()
{
int sum = 0;//保存最终结果
int n = 0;
int ret = 1;//保存n的阶乘
scanf("%d", &n);
for(int i=1; i<=n; i++)
{
ret = 1;
for(int j=1; j<=i; j++)//n的阶乘
{
ret *= j;
}
sum += ret;
}
printf("%d\n", sum);
return 0;
}
3.2 如何写出好(易于调试)的代码
1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 5. 可维护性高 6. 注释清晰 7. 文档齐全
常见的coding技巧:
1. 使用assert
2. 尽量使用const
3. 养成良好的编码风格
4. 添加必要的注释
5. 避免编码的陷阱。
3.3 模拟实现库函数:strcpy(字符串拷贝)
把原指针指向的内容拷贝到目的空间指针指向的空间处,同时\0也要拷贝过去
满分10分,这个代码给5分,不及格 ,虽然他也能完成我们想要实现的字符串拷贝
void my_strcpy(char* dest, char* src)//将src空间处的字符拷贝到目标空间dest
{
while (*src != '\0')
{
*dest = *src;
dest++;
src++;
}
*dest = *src;//再把\0拿下来
}
int main()
{
char arr1[] = "hello bit";
char arr2[20] = "xxxxxxxxxxx";
my_strcpy(arr2,arr1);
return 0;
}
如何改进?第一,字符串拷贝进行了两次,我们能否优化
可以改为后置++
void my_strcpy(char* dest, char* src)
{
while (*src != '\0')
{
*dest++ = *src++;
}
*dest = *src;
}
assert是用来断言的,当我们传过来的两个指针为NULL时,没有断言便进行解应用时,我们便非法访问内存了,代码有风险
上面的代码还是不够简化,我们能否将dest = src 两句合在一起,
#include <assert.h>
void my_strcpy(char* dest, char* src)
{
//assert(dest != NULL);//断言
//assert(src != NULL);//断言
assert(dest && src);//优化断言,一个为假(NULL)便报错
while (*dest++ = *src++)//先执行src,src指向hello bit,由于++是后置,先使用再++
//h被*dest内容复制,表达式结果是h的ASCII码值
// \0的ASCII码值为0,先执行完\0 = x,表达式不成立结束循环
{
;
}
}
然后我们看官方版本的strcpy介绍
char *strcpy( char *strDestination, const char *strSource );
my_strcpy函数设计返回值类型char*是为了实现函数的链式访问
还需要再加上const,让src目标空间处的变量不可被修改(变成常变量),以免代码出错
满分的函数设计!
char* my_strcpy(char* dest, const char* src)
{
assert(dest && src);//断言
char* ret = dest;
while (*dest++ = *src++)
{
;
}
return ret;
}
int main()
{
char arr1[] = "hello bit";
char arr2[20] = "xxxxxxxxxxx";
char* p = NULL;
printf("%s\n", my_strcpy(arr2, arr1));
return 0;
}
4.1 Const练习解释
const是为了让我们代码更加健壮性,但是我们要怎么去使用const来精确的限制我们想要的代码呢?
int main()
{
int num = 10;
num = 20;//第一种方法改num变量值
int* p = #
*p = 100;//指针方法改变num变量值
return 0;
}
加const又会怎么样?
int main()
{
const int num =0;//常变量,不可被修改
num = 20; //编译器不通过
int * p =#
*p = 20;//成功改掉,编译器通过,证明我们可以使用指针来去改被修饰的常变量
printf("%d\n", num);//20 这种操作破坏了const,本意不改却被投机取巧改掉了
return 0;
}
把门锁了,不让你从门进,你却砸烂了窗户跳了进去,虽然进去了但是不合规矩
int main()
{
const int * p =#
*p = 20;//X 窗户也给你封死
int * const p =#
p = 20 ; //X 门封死
const int * const p =#//X 窗户, 门封死也给你封死
return 0;
}
const 可以修饰指针:
const 放在*的左边(const int* p;)
const修饰的是*p,表示p指向的对象不能通过p来改变,但是p变量中的地址是可以改变的
const 放在*的右边(int* const p;)
const 修饰的是p,表示p的内容不能被改变,但是p指向的的对象是可以通过p来改变的
5.1 编程常见的错误
5.2 编译性错误就是语法错误
int main()
{
return 0 //漏掉了; 在编译过程中报错,没有运行起来
}
解决方法:直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。
5.3 链接型错误
//没有引头文件
//int Add(int x, int y)//或者没有定义Add函数
//{
//return x + y;
//}
int main()
{
int a = 10;
int b = 20;
int c = add(a, b);//函数名写错了
printf("%d\n", c);
return 0;
}
解决方法:看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不 存在或者拼写错误。
5.4运行时错误 - 借助调试解决的错误 (求阶乘例题)
C语言初阶知识点完结!