函数声明和定义
函数声明是告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,怎么实现的,声明不知道。而且调用函数需要满足先声明再调用。
函数的定义就是函数的具体实现。
在我们的现实生活中我们一般把函数的声明,定义,调用分别文件写。
这样写的优点就是:1.可以多人协作 2.可以代码保护
函数递归
什么是函数递归
函数调用自己的方式就是递归,有一个最简单的递归:
int main()
{
printf("hehe\n");
main();
}
这就是递归。
递归的俩个必要条件
1. 有限制条件,当满足限制条件,递归就结束。
2.每次递归之后就越来越接近这个限制条件。
在递归中思想方式就是大事化小,把一个大的问题化成一个相似的小问题来解决。
例子
输入一个整数,按顺序打印每一位:
void Print(int a)
{
if (a > 9)
{
Print(a / 10);
}
printf("%d ", a % 10);//递推+回归
}
int main()
{
int a = 0;
scanf("%d", &a);
Print(a);
}
我们看看这道题来理解一下递归。其实递归就是递进加回归。
一个整数 % 10 剩下的就是他的最后一位数字,如果我们的整数小于10,就直接打印。所以我们的限制条件就是 a > 9,如何再调用函数,a /10 就是除掉了最后一位数字,一直这样除最后一位数字,到最后我们的a 小于10了,剩下了第一位数字,开始回归,打印出第一位数字,然后第二位,这样子下去。假设我们输入的是1234:
递归的过程就是这样。
当然函数可以实现递归是因为调用一次函数就会创建一个函数栈帧在内存中,调用开始栈帧创建,调用结束,栈帧销毁。
递归模拟实现strlen函数
//模拟实现strlen函数(递归)
int MY_strlen(char* p)
{
if (*p == '\0')
{
return 0;
}
else
{
return 1 + MY_strlen(p + 1);
}
}
int main()
{
char arr1[] = "abc";
int ret = MY_strlen(arr1);
printf("%d", ret);
}
注意:传递数组名是首元素的地址。
递归与迭代(循环)
n的阶乘递归:
//n的阶乘(递归不考虑栈溢出)
int factorial(int n)
{
if (n <= 1)
{
return 1;
}
else
{
return n * factorial(n - 1);
}
/*int ret = 1;
for (size_t i = 1; i <= n ; i++)
{
ret = ret * i;
}
return ret;*/
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = factorial(n);
printf("%d", ret);
}//n 太大的话就会栈溢出,如果用迭代的就不会,也就是循环
在这个例子里 递归 n太大的话,会栈溢出也就是内存不够了,一直开辟空间没有销毁,就会溢出。
我注释掉的就是循环实现的代码,用非递归的循环就不会栈溢出,但是可能会因为类型问题出错。
求n 个斐波那契数:
//n 个斐波那契数(不考虑溢出),不适合递归
int fib(int n)
{
if (n <= 2)
{
return 1;
}
else
{
return fib(n - 1) + fib(n - 2);
}
/*int a = 1;
int b = 1;
int c;
while (n > 2)
{
c = b;
b = a;
a = b + c;
n--;
}
return a;*/
}
int main()
{
int n = 0;
scanf("%d", &n);
int ret = fib(n);
printf("%d", ret);
}//这个递归有太多重复计算,太耗时间了,改成非递归
这个问题其实不建议用递归,因为很费时间,我注释掉的就是循环代码,就可以立马算出来。
所以不是所有的问题都适合递归,递归只是把代码变得更简洁一点,他的代码计算量其实一点没少,递归只是看起来简洁了,循环只是看起来复制,有时候循环会比递归更高效。
如果我们使用递归更容易想到,且代码没有什么明显的问题,那就使用递归,如果有问题,那就使用循环。