一.函数的声明与定义
//函数的声明 int Add(int x, int y); int main() { int num1 = 0; int num2 = 0; scanf("%d %d", &num1, &num2); //计算 //函数的调用(传值调用) //2 int ret = Add(num1, num2); printf("%d\n", ret); return 0; } //函数的定义 int Add(int x, int y) { return x + y; }
正常情况下,如果函数是在主函数main后面定义,那么就需要在前面加上声明。这样才可以使用。
如果不想声明,那就在主函数main前面进行定义。
在未来工程中,代码是比较多的,所以函数都是会放在.h文件中声明,在.c文件中实现。这就是模块化编程。
所以我们先添加2个文件。
把函数放置好后,不要忘记在主函数那里调用add.h文件
二.函数递归
递归的主要思考方式:大事化小
int main() { printf("hehe\n"); main(); return 0; }
这是一个最简单的递归,但最后会因为栈溢出而崩溃。其原因就在于没有结束条件,只能无止境的运行。
递归的两个必要条件:
1.存在限制条件,当满足这个限制条件的时候,递归便不再继续。
2.每次递归调用之后越来越接近这个限制条件。
1.案例一.接收一个整型值,按照顺序打印它的每一位。
例如:输入:1234 输出:1 2 3 4
int main() { //1234 //1234%10=4 //1234/10=123 //123%10=3 return 0; }
目前的基本思路就是先把每个数都单独拎出来,采用模10,除10可以解决,不过又该怎么让这些单独的数字按照顺序排列呢?
采用拆分的方式:
int main() { //1234 //1234%10=4 //1234/10=123 //123%10=3 int num = 0; scanf("%d", &num); Print(num); //Print(1234) //Print(123)+ 4 //Print(12)+ 3 //Print(1)+ 2 return 0; }
只要在每一次打印出单独数字之前再继续调用函数,让其先进入函数内部而无法打印进行递进,最后回归打印全部。
void Prinit(int n) { if (n > 9) { Print(n / 10); } printf("%d ", n % 10); }
而这一步就是递归的精华。
每一次都要等调用函数完后才可以打印,当n=12时,因为要等函数Print(1)结束,当(1)结束时打印1再返回到函数(12)中,代表函数(1)结束所以打印2,以此类推函数(12)结束,函数(123)打印3,最终结果就是1 2 3 4.
红色线条代表递进,通过函数层层递进,而其中会有条件限制函数,并逐渐接近条件。最后再通过蓝色线条层层回归。
下面会引申关于函数栈帧的概念(后续会具体介绍)
2.函数栈帧
函数之所以能实现调用,能实现递归都是因为,函数在调用的时候会维护一个函数栈帧(内存上的一块区域)函数调用开始,函数栈帧创建,函数调用结束后,栈帧销毁。
我们可以看到在每一次的开辟空间中都会存放其中的局部变量。
当把1打印出来的时候,黄色空间就会销毁回收,以此类推。而在销毁之前存放的变量会被打印出来,完成价值。
3.案例二.编写函数不允许创建临时变量,求字符串的长度。
int main() { char arr[] = { "abc" }; size_t len = strlen(arr); printf("%zd\n", len); return 0; }
这是正常的代码,strlen返回的是size_t类型所以需要size_t变量接受,打印也需要用zd。
不过这样就有了临时变量了。所以我们需要的就是模拟实现strlen的功能并返回长度。
接下来演绎用函数求长度。
size_t my_strlen(char* str) { size_t count = 0; while (*str != '\0') { str++; count++; } return count; } int main() { char arr[] = { "abc" }; size_t len = my_strlen(arr); printf("%zd", len); return 0; }
基本思路就是利用\0字符串结束标志来确定字符串长度,不过需要注意的是str++是指往数组下一位进行判断,但还是创建了临时变量。
这里我们可以利用案例一的办法——拆分:只要不是结束标志\0那就+1并继续判断str后一位的字符是否是\n。不过注意一点,str+1不要用str++,因为str++是先用再加,这里就不能达到下一位的目的了。
size_t my_strlen(char* str) { if (*str != '\0') { return 1 + my_strlen(str + 1); } else { return 0; } } int main() { char arr[] = { "abc" }; size_t len = my_strlen(arr); printf("%zd", len); return 0; }
分析如上:红线从上往下递进,蓝线从下往上递归。
4.案例三.求n的阶层
怎么说呢,其实这在数学中是有归纳规律的。就像等差,等比一样,利用递归和迭代写的话,记住规律就好了。
这就是递归套用的函数。
int Fac(int k) { if (k <= 1) { return 1; } else { return k * Fac(k - 1); } } int main() { int k = 0; scanf("%d", &k); int r = Fac(k); printf("%d\n", r); return 0; }
很简单就写出来了。不过如果数字太大,那么就会一直递归下去,很久才往回返,因此会算得很慢。
5.案例四.求第n个斐波那契数
这个是跟阶层同理,可以通过数学原理解答,也有自己的递归规律。
int Fib(int n) { if (n <= 2) return 1; else return Fib(n - 1) + Fib(n - 2); } int main() { int n = 0; scanf("%d", &n); int ret = Fib(n); printf("%d\n", ret); return 0; }
不过还是老问题,有太多大量重复计算,会影响效率。
例如输入50,会发现牵连的数字很多是重复计算的。
这时我们可以尝试不用递归来算,从斐波那契中可以看出,第一,二个数是不用算的,直到第3个数字才开始,可以采用相互赋值逻辑计算。
int Fib(int n) { int a = 1; int b = 1; int c = 1; while (n >= 3) { c = a + b; a = b; b = c; n--; } return c; }
而这个n>-3条件只是确定在第几个数中而已,因为后续的数都需要前面的数作计算。
在没有用递归之后,反而系统很快就算出来了。
后续还有汉诺塔问题和青蛙跳台阶问题,敬请期待。