6.小技巧(监视观察多个值):
void test(int arr[]) { } int main() { int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; test(arr); return 0; }
疑惑:
因为arr指针中存放的是第一个元素的地址 所以只能看到一个元素
解决方法: 在arr后面加入,和数字(必须小于数组长度)
总结:
多多动手,尝试调试,才能有进步。
一定要熟练掌握调试技巧。
初学者可能80%的时间在写代码,20%的时间在调试。
但是一个程序员可能20%的时间在写 程序,但是80%的时间在调试。
我们所讲的都是一些简单的调试。 以后可能会出现很复杂调试场景:多线程程序的调试等。
多多使用快捷键,提升效率。
7一些调试的实例
实例一:实现代码:求 1!+2!+3! ...+ n! ;不考虑溢出。
错误代码:
//实现代码:求 1!+ 2!+3!+...+10!不考虑溢出 int main() { int n = 0; int ret = 1; int i = 0; int sum = 0; for (n = 1; n <= 3; n++) { for (i = 1; i <= n; i++) { ret *= i; } sum = sum + ret; } printf("%d\n", sum); return 0; }
前提说明:
首先按下F10,开始调试,打开监视窗口输入以下的变量
问题出现:
解题顺序:1
可以看出当n=3时,sum已经求完了1!+2!,所以前两个阶乘的之和的值是为3的,接着当代码走完当n=3,i=1时此语句中的条件语句时发现此时ret的值为2,根据前面知识可知i的值是为了求某次阶乘的具体值
所以i=1应该为ret=1!=1*1,答案为啥是2呢,接着往下看
2
i=2时,ret=4,此时应该为ret=2!=1*2=2,答案也不相符
3
到i等于3时竟发现ret=12,但是ret=3!=1*2*3=6,怎么可以等于12呢,推测应该是第一步的时候就错了
4
可以看到,当n=2时,i=2时,此时ret=2,所以导致了n=3,i=1时,ret=1*2=2,说明了此时ret还是保留了那个2的值,没有经历过重置
所以正确的代码是:
//实现代码:求 1!+ 2!+3!+...+10!不考虑溢出 int main() { int n = 0; int ret = 1; int i = 0; int sum = 0; for (n = 1; n <= 3; n++) { ret=1; for (i = 1; i <= n; i++) { ret *= i; } sum = sum + ret; } printf("%d\n", sum); return 0; }
ret要在每次求完一个阶乘的时候重置为1,才能正确算出值
实例二 死循环与数组越界该值
#include<stdio.h> 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("hehe\n"); } return 0; }
代码运行:
研究程序死循环的原因。
图解:
原理:
1.i和arr是局部变量,局部变量是放在栈区上的
2.栈区内存的使用习惯是:先使用高地址处的空间,再使用低地址处的空间
解释一下第2条:这条原理的作用体现在栈上,代码先定义的i,那么i就会在高地址处,后定义的是数组arr,数组在低地址处,所以上图的arr是位于i的下方处的
3.数组随着下标的增长, 地址是由低到高变化的
关于arr和i关系这是我的个人的理解:
假设i的地址是0x11223344,arr这个数组经过循环越界到了i的那块空间里面去,直到数组arr的下标为i时,即为arr[i]时,arr[i]和i是属于同一块空间,所以对于arr[12]=0,进行赋值的时候,相当于将i的值也改成了0
类比为:对于同一个房间,灯是开着的,我进去把灯关了,那你进去的时候,房间就是黑的
8.库函数:strcpy() -- 字符串拷贝
#include<string.h> int main() { char arr1[] = "hello world"; char arr2[20] = { 0 }; strcpy(arr2, arr1); printf("%s\n", arr2); }
代码运行:
模拟实现库函数:
方式一
#include<string.h> void my_stcpy(char* dest, char* src) { while (*src != '\0') { *dest = *src++; } *dest = *src;//\0的拷贝 } int main() { char arr1[] = "hello world"; char arr2[20] = { 0 }; strcpy(arr2, arr1); printf("%s\n", arr2); }
原理:
这种写法在while循环内不会拷贝'\0',所以要单独写一条 *dest = *src;
方式二
#include<stdio.h> void my_stcpy(char* dest, char* src) { while (*dest++ = *src++) { ; } } int main() { char arr1[] = "hello world"; char arr2[20] = { 0 }; my_strcpy(arr2, NULL);//若传参传错了就会抛出问题 printf("%s\n", arr2); }
遇到'\0',其ascll码值是0,0为假,跳出循环,这样写即把字符串拷贝过去,也把'\0'拷贝过去,但是到了'\0'的位置,dest和src还是会++
9.断言
作用:发现问题,就会把问题抛出来
假设传参传错了,错误参数:NULL
my_strcpy(arr2, NULL);
使用断言
#include<stdio.h> #include<string.h> #include<assert.h> void my_strcpy(char* dest, char* src) { //断言 assert(dest&&src != NULL);//条件为真的话就不会报错 while (*dest++ = *src++) { ; } } int main() { char arr1[] = "hello world"; char arr2[20] = { 0 }; my_strcpy(arr2, NULL);//若传参传错了就会抛出问题 printf("%s\n", arr2); }
代码运行:
假设不用断言,就不知道错在哪里
用以下这个代码
#include<stdio.h> void my_strcpy(char* dest, char* src) { if (dest == NULL || src == NULL)//遇到问题不解决问题,逃避问题 { return; } while (*dest++ = *src++) { ; } } #include<string.h> int main() { char arr1[] = "hello world"; char arr2[20] = { 0 }; my_strcpy(arr2, NULL); printf("%s\n", arr2); }
执行:
10.const的作用
const在*左边
int main() { int n = 10; const int m = 0;//m=20;//err,错误 //const修饰指针 //1.const 放在*的左边,*p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他的变量 const int* p = &m; //*p = 20;err p = &n;//ok return 0; }
1.const 放在*的左边,*p不能改了,也就是p指向的内容,不能通过p来改变了。但是p是可以改变的,p可以指向其他的变量
const在*右边
#include<stdio.h> int main(){ int n = 10; const int m = 0;//m=20;err,错误 //2.const 放在*的右边,限制的是p,p不能改变,但是p指向的内容*p,是可以通过p来改变 int* const p = &m; *p = 20; //p = &n; printf("%d\n", m); return 0; }
2.const 放在*的右边,限制的是p,p不能改变,但是p指向的内容*p,是可以通过p来改变
图解:
3.这张图还包括了const两边都有*,那么以上两种赋值方式都是错误的
结论:
const修饰指针变量的时候:
1. const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变。但是指针变量本身的内容可变。
2. const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改,但是指 针指向的内容,可以通过指针改变
11.编程常见的错误
编译型错误
直接看错误提示信息(双击),解决问题。或者凭借经验就可以搞定。相对来说简单。
链接型错误
看错误提示信息,主要在代码中找到错误信息中的标识符,然后定位问题所在。一般是标识符名不 存在或者拼写错误。
运行时错误
借助调试,逐步定位问题。最难搞。
最后:
做一个有心人,积累排错经验。