一、VS实用调试
1.调试(debug):消灭bug用,如果除了问题好寻找问题
2.发布(release):优化后运行的最优解,限于用户使用
3.一些常见快捷键
F9:创建和取消断点,断点的定义就是打断程序执行的一个点
F5:启动调试,结果跳到下一个逻辑上的断点,遇到断点好停下
F10:逐过程,不进入函数内部
F11:逐语句,可进入函数内部,直接完成函数的调用
4.调试中的监视、内存(也可以通过自动窗口观察)(通过F10进行逐过程移到观察)
#如何打开:调试开启后→调试→窗口→监视→输入变量名称,可以通过“&”观察变量地址
5.我们先明白几个概念
##栈区内存的使用习惯是从高地址向低地址使用等等
##在X86环境下,假设你先创建一个变量,然后创建一个数组,数组存放的时候随着下标增大地址也跟着增大,加入你访问的数组元素越界,可能会波及别的变量,会导致你访问的这个越界的数组的值覆盖掉之前变量的值,我们通过调试发现问题
你会惊奇的发现,我的天,为什么我循环着循环着我的i变成了0?诶,好像知道死循环的原因了,就是每次循环数组第十三个元素地址和变量i的地址重叠了,难怪,每次到这里i都被重复赋值
##如果你换成release版本,你会发现不会死循环了,但是会报错
##如果你换成X64环境下的debug和release版本,你会发现也会报错
##这都是为什么呢?原来在这三种情况下,代码都会优化,你不是被我覆盖了值吗,那好,我就直接把变量的地址相较于数组第一个元素的地址往下移,这样你就算访问越界多大的元素,都不会覆盖我变量i的值了
****因此,我们在应对大文件大量代码的时候,调试和断点是我们优化代码的好方法
####话说,写代码有三种境界
代码就是代码→代码是内存→写代码跟喝水一样简单(当然我是最低的境界哈哈)
####编写代码的时候我们可能会犯一些常见的错误####
语法错误(编译错误)
链接型错误(诸如无法解析的外部符号等→一般是没写头文件,函数名错误标识名错误等等)
而对于最麻烦的运行时错误,我们就可以借助调试帮我们发现问题
二、函数递归
1.定义:是一种解决问题的方法,在C语言中指的是函数自己调用自己,其中的“递”是递推,“归”是回归
2.思想:大事化小,把大的且复杂的问题化成小的简单的子问题求解,直到不能再拆分。
3.限制条件:总不能一直无限递归下去吧,我们都会设立终止条件,满足条件后便不再继续;同时,我们要让每次递归调用后越来越接近限制条件
####举个例子:我们通过递归的方式求输入数字的阶乘,我们认定0的阶乘为1
//1!=1,2!=1!*2,3!=2!*3 .....
//我们不难发现,n的阶乘等于n-1的阶乘*n,那符合我们对递归的预期,开始撰写代码(这里我们不求n阶乘的和,若果要写则要多写个函数)
int num(int x)
{
int sum = 0;
if (x == 0)
{
return 1;
}
else
{
return num(x - 1) * x;//这里构成递归,当x大于1来到这,再进入函数继续判断,直到x等于0的时候递推停止,开始回归,计算值
}
}
int main()
{
int input = 0;
scanf("%d", &input);
int ret = num(input);
printf("%d", ret);
return 0;
}
接下来我们开始解释:
1.对于每一次递归,都会在内存中申请一块空间,当递归回归后空间释放
2.对于if(x==0)这个条件,我们称之为递归的限制条件,当最后递推为0的时候,我们终止递推,开始回归,计算
3.当这样一次又一次调用后,一直递推到0的时候,返回1这个值,开始回归,回归到1的时候,由上一次回归的值乘上这一次回归的值,回归到2的时候,由上一次(回归1的时候)回归的值乘上2,以此类推,直到回到最开始的值为止,我们的递归就结束。
4.在递归过程中,我们是一级一级递推,一层一层回归
如果还不是很理解,我们这里有个图,在此感谢比特教育机构给出的图示讲解,感谢
####我们再举个例子,输入某个值,顺序打印你输入的值的每一位元素,这也是利用了递归思想
void print(int x)
{
if (x >= 10)
print(x / 10);//当x大于10,进入这里,再调用一次函数,一次次调用,直到x小于10
printf("%d ", x % 10);//x小于10的时候,if条件不成立,开始执行打印当前x的值,再回到上一级打印上一级x的值,类推直到回到最开始的值
}
int main()
{
int input = 0;
scanf("%d", &input);
print(input);
return 0;
}
接下来我们开始解释
1.原理思想:我们以1234为例,我们把1234拆分成123和4打印,我们再把123拆分成12和3打印,我们再把12拆分成1和2打印,到了1停止拆分,递推结束后回归时才会打印值
2.对于为什么要先写条件判断x大于等于10,除以10之后,再写在取模上10
这是因为假设只有一位数了,不满足递归条件了,我们直接开始打印,而若如果你顺序写反了,那就要先递归一次再打印值,产生错误,不知道这么讲您是否能理解
3.对于这个递归函数,你可以把递归理解成多个相同函数,只有递推全部结束了,在最后一个递推中的x才小于9,执行下面的打印函数。
4.开始回归时,1%10=1,打印1,回到上一级,12%10=2,打印2,再回到上一级,123%10=3,打印3,再回到上一级(也就是最上面的也就四最开始的一级),1234%10=4,打印4;这样我们就成功的顺序打印了1234,而不是通过循环直接取模倒序打印1234
如果还不是很理解,我们这里有个图,在此感谢比特教育机构给出的图示讲解,感谢
三、函数迭代
##递归虽好,简单易理解,但是如果你递归层次太深,每一次都要向内存中申请空间,这样会浪费很多内存资源,程序效率就会很低,内存开销也很大,可能会导致卡顿,那这样看来,有的时候我们还不如使用迭代(循环是一种迭代)
**但是迭代效率高,有时候不一定能想到
##我们来举个例子,要求你求第几个斐波那契数,关于定义这里不多展开,若不清楚可自行搜索
我们先给出递归代码
int count = 0;
int num(int x)
{
if (x == 4)
count++;
if (x <= 2)
return 1;
else
return num(x - 1) + num(x - 2);
}
int main()
{
int input = 0;
scanf("%d", &input);
int z = num(input);
printf("%d\n", z);
printf("%d", count);
return 0;
}
如果你采用递归的话,你会发现计算量特别大,存在很多重复计算
比如我们来统计4被重复计算了多少次,如果输入20,则4被重复计算了1596次,这是非常大的,更何况小一点的数,因此这个地方我们用递归就不行,我们想着用迭代(也就是循环解决问题)
我们再给出迭代代码
int num(int x)
{
int a = 1;
int b = 1;
int c = 1;
while(x>=3)
{
c = a + b;
a = b;
b = c;
x--;//每次循环结束时将x的值减1目的是 控制循环次数.例如:若输入x=5循环会执行5-2=3次。
}
return c;
}
int main()
{
int input = 0;
scanf("%d", &input);
int z = num(input);
printf("%d", z);
return 0;
}
这样通过迭代的方式,我们仅用了三个变量就完成了斐波那契数的计算,怎么样,效率还是要高很多的,关于斐波那契数的拓展延伸,有青蛙跳台问题和汉诺塔问题,我之前文章有写到,感兴趣的可以去看看
作者基础不是很扎实,如果有写错的地方,欢迎指正,我们友好交流讨论
END