网站建设788gg,怎样用自己的主机做网站,wordpress 调用page,郑州正规的网站设计我们日常写代码的时候#xff0c;常常会遇到bug的情况#xff0c;这个时候像我这样的初学者就会像无头苍蝇一样这里改改那里删删#xff0c;调试的重要性也就显现出来#xff0c;这篇文章接着上文来讲解。
上文地址#xff1a;(8条消息) 适合初学者的超详细实用调试技巧常常会遇到bug的情况这个时候像我这样的初学者就会像无头苍蝇一样这里改改那里删删调试的重要性也就显现出来这篇文章接着上文来讲解。
上文地址(8条消息) 适合初学者的超详细实用调试技巧上_陈大大陈的博客-CSDN博客
大概分为以下几个部分 5. 一些调试的实例。 6. 如何写出好易于调试的代码。 7. 编程常见的错误。 话不多说现在开始 5. 一些调试的实例 5.1 实例一 实现代码求 123 ... n! 不考虑溢出
我们失误写出下面的错误代码
#includestdio.h
int main()
{int i 0;int sum 0;//保存最终结果int n 0;int ret 1;//保存n的阶乘scanf(%d, n);for(i1; in; i){int j 0;for(j1; ji; j){ret * j;}sum ret;}printf(%d\n, sum);return 0;
}
我们输入1和2时结果并没有错误。
这时候我们如果输入3期待输出9但实际输出的是15。
为什么呢 首先推测问题出现的原因。初步确定问题可能的原因最好。 实际上手调试很有必要。 调试的时候我们要做到心里有数我们小试牛刀调试一下首先分析问题所在。 编译器没有报错说明代码没有语法的问题。 输入3按f11逐语句进行调试。 一次循环下来sum和ret都变为1i变为1。 第二次循环下来仍然看不到什么问题阶乘和其和也都正确。 在第三次循环我们发现ret增长的速度十分的快这才发现ret的值并没有重置为1 。
我们通过调试发现了错误写出了正确的代码 #includestdio.h
int main()
{int i 0;int sum 0;int n 0;int ret 1;scanf(%d, n);for (i 1; i n; i){int j 0;ret 1;//将ret1置于循环里for (j 1; j i; j){ret * j;}sum ret;}printf(%d\n, sum);return 0;
} 5.2.实例2 给出一个数组越界访问的例子出自《C陷阱与缺陷》 。
#include stdio.h
int main()
{int i 0;int arr[10] {0};for(i0; i12; i){arr[i] 0;printf(hehe\n);}return 0;
} 如图程序会死循环打印hehe。
上一个代码可以不用调试看出来错误但是这个是无法看出来的只能调试来看。 可以看到到这一步为止都十分正常也就是i12之前是正常的。
然而这一步之后i和arr[i]就一同变成了0。 知道了问题所在我们这次通过地址来调试看看。 可以看到当i12时i的地址和arr[i]的地址是相同的也就是说它们在栈区所开辟的空间相同。这样导致的结果就是 arr[i] 0的操作将i也一同变成了0导致死循环。 6. 如何写出好易于调试的代码。 6.1 优秀的代码 优秀的代码应该满足以下条件。 1. 代码运行正常 2. bug很少 3. 效率高 4. 可读性高 5. 可维护性高 6. 注释清晰 7. 文档齐全 为了达到这样的条件我们可以使用以下常见的coding技巧 1. 使用assert 2. 尽量使用const 3. 养成良好的编码风格 4. 添加必要的注释 5. 避免编码的陷阱。 6.2 示范 我们来模拟实现库函数strcpy
函数的参数形式char* strcpy(char*destination,const char*source)
该参数说明了strcpy返回类型是char类型的指针将源头不能被改拷贝到目的地。
strcpy特点和strlen类似遇到‘\0’就停止。
比较容易想到的写法是
#includestdio.h
#includestring.h
#includeassert.h
void my_strcpy(char* a, char* b)
{while (*a ! \0){*b *a;b;a;}*b *a;
}
int main()
{char a[] abcdef;char b[10];my_strcpy(a, b);printf(%s, b);return 0;
} 虽然可以实现strcpy函数的内容但是优化不怎么样将简单的功能写的非常麻烦且无法避免空指针的情况下面为优化后的写法
#includestdio.h
#includeassert.h
void my_strcpy(const char a[],char b[])
{assert(a!NULLb!NULL);//断言函数来避免空指针的情况while (*b *a){;//当*a为\0的时候while里的值为假跳出循环}
}
int main()
{char a[] abcdef;char b[10];//定义第二个数组来拷贝数组my_strcpy(a,b);printf(%s, b);return 0;
} 值得注意的是assert断言函数和const的使用可以大大增加代码的安全性。 6.3 const的作用 #include stdio.h
//代码1
void test1()
{int n 10;int m 20;int *p n;*p 20;//ok?p m; //ok?
}
void test2()
{//代码2int 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修饰指针变量的时候 1. const如果放在*的左边修饰的是指针指向的内容保证指针指向的内容不能通过指针来改 变。但是指针变量本身的内容可变。 2. const如果放在*的右边修饰的是指针变量本身保证了指针变量的内容不能修改但是指 针指向的内容可以通过指针改变。 6.4. const的实例 #define _CRT_SECURE_NO_WARNINGS
#include stdio.h
int my_strlen(const char a[])//用const来使代码更加安全
{int count 0;while (* a ! \0){count;}return count;
}
int main()
{char a[]abcdef;int b my_strlen(a);printf(%d, b);return 0;
} 7.编程常见的错误 7.1 编译型错误 直接看错误提示信息双击解决问题。或者凭借经验就可以搞定。相对来说简单。 如逗号的使用分号的添加括号的对应各类操作符的使用库函数的使用格式等。
对于这种问题我们可以直接通过错误列表的提示来定位问题所在解决问题。 7.2 链接型错误 看错误提示信息主要在代码中找到错误信息中的标识符然后定位问题所在。一般是标识符名不存在或者拼写错误。 如变量、头文件的包含文件的引入常量和宏的定义库函数名的拼写自定义函数名的一致等等。
就例如将main写错为mian这样的错误。 7.3 运行时错误 借助调试逐步定位问题。最难搞。 如栈溢出逻辑漏洞未指针的越界访未初始化的变量字符串溢出数组越界重复释放内存使用无效的指针等等。
就像上文里的几个例子。
对于这样的问题可以通过调试来解决。 说了这么多调试的章节终于结束了
希望大家都能成为20%的时间在写程序但是80%的时间在调试的程序员