前言
在函数(1)中已经详细介绍了什么是库函数和自定义函数,以及函数参数的两种类型(实参和形参)的区别和用法。接下来将接着介绍函数的其他用法,让读者全面理解C语言函数用法以及背后得细节。
1. 函数的嵌套调用和链式访问
函数和函数之间可以根据实际需求进行组合,即相互调用。
1.1 嵌套调用
在C语言中,函数调用简单来说就是函数内部调用其他函数,以实现嵌套
#include <stdio.h> void new_line() { printf("hehe\n"); } void three_line() { int i = 0; for (i = 0; i < 3; i++) { new_line(); //调用函数 } } int main() { three_line(); //调用three_line()函数 return 0; }
Tips:
函数可以嵌套调用,但不能嵌套定义。
1.2 链式访问
在C语言中,把一个函数的返回值作为另一个函数的参数叫链式访问。
#include <stdio.h> int main() { //函数printf()的返回值作为了printf()的参数 printf("%d\n", printf("%d\n", printf("%d\n", 43))); //注:printf()函数的返回值是打印在屏幕上字符的个数 return 0; }
2. 函数的声明和定义
2.1 函数声明
- 函数声明告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但具体是不存在的,函数声明决定不了。
- 函数的声明一般在函数使用之前,要满足先声明后使用。
- 函数的声明一般要放在头文件中。
2.2 函数定义
函数的定义是指函数的具体实现,交代函数的功能实现。
3. 函数递归
3.1 什么是递归
程序调用自身的编程技巧称为递归。
递归作为一种算法在程序设计语言中广泛应用。一个过程或函数在其定义或说明中直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。递归只需要少量的程序就可描述出解题过程所需要的多次重复计算,大大减少了程序的代码量。
递归的主要思想在于:把大事化小
//main()函数不断嵌套调用自身 //但由于没有相关限制,该程序会一直执行,死循环,最后栈溢出,程序结束 int main() { printf("hehe\n"); main(); return 0; }
3.2 递归的两个必要条件
前面一个程序我们已经见识到了如果递归没有相关条件限制,程序可能会出现异常。所以递归应满足以下两个必要条件:
- 存在限制条件,当满足这个限制条件时,递归便不再继续。
- 每次递归调用后越来越接近这个限制条件。
3.3 递归和迭代
3.3.1 练习1
求n的阶乘
函数实现功能参考代码:
int factorial(int n) { if (n <= 1) return 1; else return n + factorial(n - 1); return 0; }
3.3.2
求第n个斐波那契数(不考虑溢出)
函数实现功能参考代码:
int fib(int n) { if (n <= 2) return 1; else return fib(n - 1) + fib(n - 2); }
虽然上面两个函数可以实现需求,但我们会发现一些问题:
- 使用fib这个函数的时候如果我们计算比较大的数(如第50个斐波那契数)时特别耗时间。
- 使用factorial函数求第10000的阶乘时(不考虑结果正确性),程序会崩溃。
为什么呢?
- 我们发现fib函数在调用过程中许多计算一直在重复。
以第40个斐波那契数,fib(3)被重复计算为例:
#include<stdio.h> int count = 0; int fib(int n) { if (n == 3) count++; 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); printf("count=%d\n", count); return 0; }
运行结果:
查看输出结果,我们发现第40个斐波那契数,fib(3)被重复计算39088169次,这是一个很大的数!
Tips:
- 在调试函数时,日如果你的参数比较大,那就会报错:stack overflow(栈溢出),这样的信息。
- 系统分配给程序的栈空间是有限的,但是如果出现死循环或者死递归时,这样可能导致一直开辟栈空间,最终产生栈空间耗尽的情况,我们称这种现象为栈溢出。
那要怎么解决问题呢?
- 将递归写成非递归
- 使用static对象替代nonstatic局部变量。在递归函数设计中,可以使用static对象代替nonstatic局部对象(即栈对象)。这不仅可以减少每次递归调用和返回时产生和释放的nonstatic对象的开销,而且static对象还可以保存递归调用的中间状态,并且可为各个调用层所访问。
比如下面代码采用了非递归的方式来实现:
//求n的阶乘 int factorial(int n) { int result = 1; while (n > 1) { result *= n; n -= 1; } return result; } //求第n个斐波那契数 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; }
总结:
1.许多问题是以递归的方式来进行解释的,这只是应为它比非递归的形式更清晰。
2. 但这些问题的迭代实现往往比递归实现的效率要高,虽然代码的可读性稍差。
3. 当一个问题相当复杂时,难以用迭代实现时,此时递归实现的简洁性便可以补偿它运行时所带来的开销。
结尾
本片文章到此就圆满结束了。如果对你有帮助,记得点赞加关注哦!感谢您的支持!!