目录
引言: 在学习了函数入门篇之后 我们已经对函数有了一定了理解与掌握了 下面让我们继续深入学习函数吧
1.函数的嵌套调用和链式访问
函数与函数之间是可以根据实际需求进行组合的 也就是互相调用
1.1 嵌套调用
注: 函数之间可以嵌套调用 但不能嵌套定义 在函数内部定义函数是不允许的 因为函数是平等的
如:Name函数负责打印 My name is baby 而threeCopy函数对Name函数进行了三次调用 实现嵌套
1.2 链式访问
函数的返回值可以作为另一个函数的参数
如下 :
调用了strlen函数 计算出了字符串的长度 而返回值可以作为printf函数的参数 这就叫链式访问
现在让我们看一个更有趣的现象 来再次加深印象
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
int main()
{
printf("%d",printf("%d",printf("%d",43)));
return 0;
}
上面的函数会在屏幕上打印什么呢 ? 代码运行结果如下
屏幕上打印了4321 首先理解它我们需要知道 printf 函数的返回类型
从上图中 不难看出返回值是由返回字符总数来决定的 既返回int类型
因为最内层打印43为两个字符 而中间层则打印2为一个字符 因此最外一层打印1 使用输出4321
2.项目中函数的声明和定义
2.1 回顾函数的声明和定义
函数声明:
1.告诉编译器有一个函数叫什么 参数是什么 返回类型是什么 但是具体存不存在 声明无法决定
2.函数的声明一般出现在函数的使用之前 要满足先声明后使用
3.函数的声明一般放在头文件中
函数定义:
函数的定义指的是函数的具体实现 交代函数的功能实现
2.2 项目中函数如何声明和定义
在函数入门篇中 我们已经大概了解了函数的声明和定义 但实际在项目中 我们并不会这样操作
现在让我们去继续了解函数的声明和定义吧 让我举个栗子来具体讲解
不难看出 我调用了add函数 并把它作为了printf的参数打印了出来 但有小伙伴会问了
我并没有看见你的函数定义与声明呀 反而多出了一行 #include"add.h"
这是因为 我将函数的声明与定义分别放在了add.h和add.c文件中
这就是在具体项目中 对函数的声明定义以及调用
我们通常会建立新的.c和.h文件 用于函数的具体实现和声明
而在主函数的.c文件中 我们会加入它的头文件对它进行调用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include"add.h"
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("%d",add(a, b));
return 0;
}
注:
与库函数相似的引用头文件 但使用的不是< > 而是 " "
函数的声明 一般放在头文件中
函数的定义(实现) 都是放在源文件中
目的:
1.模块化开发(实现复杂项目的分工)
2.实现代码的隐藏
3. 函数递归
3.1 什么是递归?
程序调用自身的编程技巧叫做递归
函数递归是编程中一个重要的概念,指函数直接或间接调用自身的行为。并且递归为许多算法提供了优雅的解决方案。它往往可以把大型复杂的问题层层转化为一个规模较小的问题来求解
只需要少量的代码就可以描述出解题过程中所需要的大量重复计算
主要思想:大事化小 小事化了
用大白话讲明白C语言函数递归
想象你在照镜子,镜子里又有一面镜子,镜子里的镜子还有镜子…这就是递归——自己包含自己。
在编程里,递归就是一个函数自己调用自己,就像俄罗斯套娃一样,一层套一层。
举个生活例子:查字典
你查"苹果"这个词,解释里有个词"水果"你不懂
你去查"水果",解释里又有个词"植物"你不懂
你去查"植物",这次终于看懂了
然后你回到"水果"的解释,现在懂了
最后回到"苹果"的解释,完全明白了
这个过程就是递归:遇到不懂的就往下查,直到完全明白,再一层层返回。
而递归往往会出现栈溢出的问题 比如以下情况
int main()
{
printf("Hello World\n")
main;
return 0;
}
现在让我们了解一下什么是栈溢出问题
3.2 栈溢出
什么是栈溢出?
栈溢出是指当程序向栈中的某个变量写入数据时,超出了该变量在栈帧中分配的内存空间,导致数据覆盖了相邻的栈帧内容或返回地址等关键数据。
什么是栈呢?
栈 : 一种后进先出(LIFO)的数据结构,用于存储函数调用时的局部变量、返回地址和参数等。
而为什么函数递归会造成栈溢出呢?
那是因为在调用函数时 函数会在内存的栈区域开辟一块空间用于函数的使用 而递归导致函数的不断调用 从而导致不断地在栈内存上开辟空间 这样就会导致栈内存溢出
4. 递归的必要条件
4.1 递归函数必须有一个或多个明确的终止条件
递归函数若没有终止条件,就会无限制地调用自身,最终导致栈溢出错误。终止条件就像是递归的 “出口”,当满足这个条件时,函数会停止递归调用并返回结果。
并且递归函数的递归调用过程中必须有合理的状态传递
递归函数在每次调用自身时,要能够正确传递问题的状态信息,既必须存在一个或多个情况,在这些情况下函数会调用自身,保证递归过程可以正确处理问题。
4.2 递归函数必须能够向终止条件趋近
每次递归调用都要让问题规模逐渐变小,从而朝着终止条件靠近。要是问题规模没有缩小,递归就会陷入无限循环。
4.3递归的例子:
例子一 :从键盘上录入一个数字 并且按顺序打印
比如输入123456 则打印1 2 3 4 5 6
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
void Print(int n)
{
if(n>9)
{
Print(n / 10);
}
printf("%d ",n%10);
}
int main()
{
int num;
scanf("%d", &num);
Print(num);
return 0;
}
代码运行结果如下:
根据以上知识 我们知道了递归的三个必要条件:
1 终止条件
void Print(int n) { if(n>9) { Print(n / 10); } printf("%d ",n%10); }
当n>9时 我们会调用Print函数本身 既进入递归循环
而当n<9时 我们不在调用Print函数 这样就退出了递归循环状态
2.趋近终止条件
以上代码我们不难看出 我们递归时 给Print函数穿的参数是n/10 而为什么要传递n/10呢
这是因为我们需要满足代码的目的 从第一位往后打印 并且需要我们趋近终止条件
n/10 会不断的让n趋近于0 也就是满足n<9的条件 只有当n<9的条件满足时 才能跳出递归
运行过程-------例如输入1234
第一次判断满足条件既 1234>9 进入第一层递归 传参n/10既123
第二次判断满足条件既 123>9 进入第二次递归 传参n/10既12
第三次判断满足条件既 12>9 进入第二次递归 传参n/10既1
第四次判断不满足条件既 1<9 不在进入递归 转而打印1%10既 打印1
然后依次回到第三层 打印12%10 既打印2
然后依次回到第二层 打印123%10 既打印3
然后依次回到第一层 打印1234%10 既打印4
注意事项:
printf("%d ",n%10);
这行代码是一定会运行的 只不过是运行的时间不一样 最先运行的是最内层既n=1时
然后才是依次往外 这样才能满足我们的目的
例子二 :手动实现strlen函数的作用
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Mystrlen(char* arr)
{
if (*arr != '\0')
return 1+Mystrlen(arr+1);
else
return 0;
}
int main()
{
char arr1[10] = "abcdedg";
int len = Mystrlen(arr1);
printf("%d", len);
return 0;
}
代码运行结果如下:
这个代码我就简单说明一下
定义int类型的函数 传入字符串的第一个字符的地址
使用arr+1来实现地址的推进 arr为数组名 是第一个元素的地址 不懂的可以跳转到数组篇
当推进到字符/0是 返回0
而推进到其他字符时都会返回1+Mystrlen(arr+1)
从而满足最后返回1+1+1+1+1+1+1+0既返回7
具体步骤
字符串为abcdefg
第一层a 返回1+Mystrlen(arr+1)=7<---------------------从第二层返回Mystrlen(arr+1)=6
第二层b 返回1+Mystrlen(arr+1)=6<---------------------从第三层返回Mystrlen(arr+1)=5
第三层c 返回1+Mystrlen(arr+1)=5<---------------------从第四层返回Mystrlen(arr+1)=4
第四层d 返回1+Mystrlen(arr+1)=4<---------------------从第五层返回Mystrlen(arr+1)=3
第五层e 返回1+Mystrlen(arr+1)=3<---------------------从第六层返回Mystrlen(arr+1)=2
第六层f 返回1+Mystrlen(arr+1)=2<----------------------从第七层返回Mystrlen(arr+1)=1
第七层g 返回1+Mystrlen(arr+1)=1<---------------------从第八层返回Mystrlen(arr+1)=0
第八层\0 返回0 给第七层 既Mystrlen(arr+1)=0
最后得到第一层的返回值 1+Mystrlen(arr+1)=7 输出给int len = Mystrlen(arr1);
4.4 递归的练习:
练习一: 写一个函数求n的阶乘
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Fac(int n)
{
if (n > 1)
return Fac(n - 1) * n;
else
return 1;
}
int main()
{
int m;
scanf("%d", &m);
printf("%d", Fac(m));
return 0;
}
代码运行结果如下:
练习二: 斐波那契数列
什么是斐波那契数 ?
斐波那契数列的每一项都是前两项的和。序列的前几项为:1, 1, 2, 3, 5, 8, 13, 21, 34, 55
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Fib(int n)
{
if (n <= 2)
return 1;
else
return Fib(n - 1) + Fib(n - 2);
}
int main()
{
int n ;
scanf("%d", &n);
int m=Fib(n);
printf("%d", m);
return 0;
}
运行结果如下:
代码缺陷 ;
由于递归使用Fib(n) 会导致每一次调用Fib(n)都会衍生出两个分支用来计算Fib(n-1) Fib(n-2)
这样会是计算变得相当的缓慢 下面是代码的改进 不使用递归
代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int Fib(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n > 2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
int n ;
scanf("%d", &n);
int m=Fib(n);
printf("%d", m);
return 0;
}
优点: 运算速度大大加快
感兴趣的还可以去了解一下函数递归的其他问题 :
1.汉诺塔问题
2.青蛙跳台阶问题(斐波那契变形)