所爱隔山海,山海皆可平,所念皆星河,星河不可及。
上课!
接着上节课讲的调试(1),本节课进一步讲解调试(2).
校招笔试题
2.如何写出好的(易于调试)代码?
3.编程常见的错误
//代码改错
int main()
{
int i = 0;
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
for (i = 0; i <=12; i++)
{
printf("hehe\n");
arr[i] = 0;
}
return 0;
}
请复制这段代码到本地编译器运行,看看为什么会出现问题。
在内存中大致有下面这三块空间分布,吧栈区放大后,假设顶部是高地址,则底部就是低地址,如图:
先创建局部变量i,i就使用高地址处的一块空间,
再创建数组arr,arr[0]开始创建低地址处的一块空间。
然后再创建到arr[9]时,i和arr[9]之间恰好有两块整型大小的空间,也就是绿色的空间
随后,arr[12]的空间创建自然而然地就和 i 在同一块空间了
你一定会问,为什么在 i 和arr[9]之间恰好有两块整型大小的空间?不能是三块空间吗?不能是一块空间吗?
声明:以上现象只针对于vs2019 X86 Debug调试环境下产生的现象
在 VC6.0环境下 i 和arr 之间没有多余的空间
在gcc环境下 i 和arr之间只有一个整型空间
说那么多东西,只为了说明一件事: 调试的重要性!!调试可以帮助你找到自己的错误!学会调试,你的能力将达到一个质的飞跃。
上节课讲过,Debug和Relese版本的不同
在这个例子中,在不同版本下所出现的情况也不同:
在Release版本底下,i和 arr[12]所在的地址也不一样了。
int main(int argc,char*argv[])
{
long i;
long a[16];
for (i = 0; i <= 17; i++)
{
a[i] = 0;
printf("%d", i);
}
return 0;
}
2.1
代码运行正常
bug很少
效率高
可读性高
可维护性高
注释清晰
文档齐全
常见的coding技巧
下面以一个例子说明上面所有的要点:
模拟实现my_strcpy的功能
strcpy函数,简单来说就是把一个字符串拷贝到另一个字符串里面
//先观察strcpy的功能
#include
int main()
{
char arr[20] = "xxxxxxxxxxxxxxx";
char str[] = "hello world";
strcpy(arr, str);
printf("%s\n", arr);
}
void mystrcpy(char* dst, char* src)
{
while(*dst++=*src++)
//等价于
//*dst = *src;
//dst++,src++;
{
;
}
}
int main()
{
char arr1[20] = { 0 };
char arr2[] = "hello";
my_strcpy(arr1, arr2);
printf("%s\n", arr1);
}
if(*dst ==NULL || *stc == NULL)
{
return ;
}
但是这样写,还是会有问题,为什么呢?
1.每次进入my_strcpy函数内部,都要执行if语句,不管它是不是空指针。
2.它不会暴露错误出来,就算是空指针,也会悄悄规避掉,程序员无法知道自己穿的是空指针。
所以,引用 “断言”-----assert
assert内部可以放一个表达式,表达式如果为假,就报错,为真,啥事没有。
仍然用上面的代码,来举例:
void my_strcpy(char* dst, char* src)
{
assert(dst !=NULL && src != NULL);//断言
while (*dst++ = *src++)
{
;
}
}
int main()
{
char arr1[20] = { 0 };
char* p = NULL;//p指向的常量字符串无法更改
my_strcpy(arr1, p);
//如果是反着来,是无法更改的
printf("%s\n", arr1);
}
假设有一个程序员,将
void my_strcpy(char* dst, char* src)
{
assert(dst && src);
while(*dst++ = *src++ )
{
;
}
}
int main()
{
char arr1[20] = { 0 };
char* p = "hello";//p指向的常量字符串无法更改
my_strcpy(arr1, p);
printf("%s\n", arr1);
}
写成了
while(*src++ =*dst++ )
{
;
}
也就是在while循环里面,将两个指针位置互换了
可以知道,arr里面只有\0,把\0 复制到src的第一个位置后
循环马上停下了,输出的也就是arr1里面的内容,即\0
它说,左值是必须可修改的,这就意味着,加上了const 后,指针指向的内容不可更改
再来看一个例子:
看看有什么不同?
在这个例子中,我把const 放在了 * 的后面,这时候 p不能更改了。
总结:
const修饰指针变量的时候
1.const放在的左边,修饰的是指针指向的内容
表示指针指向的内容,不能通过指针来改变*
2.也可以放在的右边,const修饰的是指针变量本身
表示指针变量本身的内容不能被修改,但是指针指向的内容,
可以通过指针来改变
回到上面的例子,所以当我写成
const char* src的时候,就算写反了位置,也没关系
因为这样写的时候
void my_strcpy(char* dst, const char* src)
{
assert(dst && src);
while(*src++ = *dst++ )
{
;
}
}
int main()
{
char arr1[20] = { 0 };
char* p = hello;
my_strcpy(arr1, p);
printf("%s\n", arr1);
}
这里是char *类型的返回值呀,为什么上面写的都是void类型呢?
别着急,这就马上讲
char* my_strcpy(char* dst, const char* src)
{
assert(dst && src);
char* ret = dst;//存储dst的起始位置
while(*src++ = *dst++ )
{
;
}
return ret ;
//这里不能返回dst,因为dst指向的空间不再是起始位置的地址了
}
int main()
{
char arr1[20] = { 0 };
char* p = hello;
my_strcpy(arr1, p);
printf("%s\n", arr1);
}
大功告成!
总结:
3.1编译型错误
3.2链接型错误
下面是一个链接型错误,链接型错误中,双击错误行是没有什么反应的,看行数也没什么效果,解决办法就是 “搜索”
按Ctrl +F5 打开搜索框进行搜索
下面是最后一个,也是最难的一个
3.3运行时错误
运行时错误,也是最难解决的一个错误,
就需要用到调试,上面讲的第一个例子
就是运行时错误产生的,
调试可以说是专门为了解决这个错误而产生的。
今天的内容就到这里。
总结:调试是重中之重,万事开头难,当你勇敢地迈出第一步,就成功了一半。
下课
————— END —————