内容正文:
7.6、7.7节:函数的嵌套调用和递归调用
C语言程序设计
电子工业出版社
函数嵌套调用和递归调用的基本概念
嵌套执行流程与递归核心要素(终止条件、递推关系)
运用嵌套调用实现结构清晰的模块化程序
运用递归思想解决阶乘、斐波那契、汉诺塔等典型问题
深度培养逻辑思维与抽象思维能力
深刻体会分治思想与模块化设计的工程价值
知识目标
能力目标
素养目标
01
01
01
学习目标
目录
Part 01
教学导入
Part 02
知识讲授
Part 03
师生互动
Part 04
课堂练习
Part 05
课堂小结
教学导入:一个故事
想象一下,我们去一家非常现代化的餐厅...
顾客 ( main 函数 )
你是顾客,坐在餐桌前。你想吃一份“巨无霸套餐”,但你不需要自己去厨房炒菜,只需要把需求清晰地告诉服务员。
这就像程序的入口main函数,它是任务的发起者,负责告诉“系统”该做什么,但并不亲自处理底层的所有细节。
核心类比:顾客 = main函数,负责“发起”而非“执行”。
教学导入:服务员接到订单后,他会做什么?
服务员 (一级函数)
他不会亲自去做汉堡、炸薯条。他会把你的需求拆解成几个具体的小任务,分发给后厨的专业人员。
“请做一个巨无霸汉堡!”
➜ 调用函数:make_burger()
“请炸一份大薯条!”
➜ 调用函数:make_fries()
“请准备一杯冰可乐!”
➜ 调用函数:prepare_drink()
教学导入:任务执行与返回
后厨各司其职,最终完成任务
▍后厨 (二级函数) 的工作流程
接到指令后开始工作。make_burger()还会细分调用grill_patty()和prepare_vegetables()等子任务。
所有食物制作完成后,统一交给服务员。服务员将单品组合成一份完整的套餐,送到用户面前。
核心概念:这正是层层调用、层层返回的函数执行过程。
知识讲授
函数的嵌套调用和递归调用
函数的嵌套调用
函数的递归调用
第一部分
函数的嵌套调用
知识讲授
知识讲授:函数嵌套调用的概念
核心定义:在一个函数的内部,去调用另一个已经定义好的函数,这种代码的组织和调用方式,我们称之为“函数的嵌套调用”。
✅ 允许:嵌套调用
C语言完全支持这种方式,这是实现程序模块化设计的核心基础。通过拆分功能到不同函数,再互相调用,可以让代码逻辑更清晰。
❌ 禁止:嵌套定义
你绝对不能在一个函数的“{}”内部去定义另一个函数。在C语言的语法规则中,所有函数必须是平行的、独立定义的,互不包含。
知识讲授:嵌套调用的执行流程
▍核心执行机制
程序执行遇到函数调用时,会暂停当前函数的执行流,跳转到被调用函数的入口。待被调用函数执行完毕(遇到 return 或执行结束),再返回到原调用处,继续向下执行剩余代码。
▍流程拆解示例
Step 1:main()发起调用,暂停自身,执行function_A()
Step 2:function_A()嵌套调用,暂停自身,执行function_B()
知识讲授:嵌套调用的执行流程详解
执行阶段:层层深入的调用
01. 起点:程序从main()函数开始执行。
02. 跳转:遇到function_A()调用,流程跳转到 A 内部。
03. 嵌套:A 内部继续调用function_B(),流程再次跳转。
04. 执行:function_B()开始执行其核心逻辑代码。
回溯阶段:原路返回的结果
05. 归位:B 执行完毕,返回到 A 的调用处,并带回结果。
06. 续行:A 继续执行调用 B 之后的剩余代码。
07. 回退:A 执行完毕,返回到 main 的调用处。
08. 结束:main 函数执行后续代码,直至程序终止。
核心原则:先进后出 —— 类似“栈”的结构
最先被调用的函数(main)最后结束,最后被调用的函数(B)最先结束。理解这一逻辑是分析复杂程序运行机制的基础。
例题分析
程序文本【7.7】计算s=22!+32!
#include<stdio.h>
long f1(int p)
{ int k; long r;
long f2(int);
k=p*p;
r=f2(k);
return r; }
long f2(int q)
{ long c=1; int i;
for(i=1;i<=q;i++)
c=c*i;
return c; }
main()
{ int i;l ong s=0;
for(i=2;i<=3;i++)
s=s+f1(i);
printf("
s=%ld
",s); }
执行流程拆解
STEP 01 : 入口调用
程序从main()开始,作为主控逻辑主动发起对 f1() 函数的调用。
STEP 02 : 内部嵌套
在f1()内部计算出平方值后,以此值为参数,进一步调用f2()计算阶乘。
STEP 03 : 结果返回
f2 计算结果返回到 f1,f1 再将最终结果返回到 main,完成两层嵌套的闭环。
例题分析
背诵记忆
▍ 函数的嵌套调用
01. 定义
函数嵌套调用的核心概念非常直观:
即在一个函数的内部代码块中,去调用另外一个已经定义好的函数。
02. 核心规则
允许嵌套调用
不同层级间的函数可以互相调用
禁止嵌套定义
函数不能定义在另一个函数内部
03. 执行流程
严格遵循“先进后出”的栈式结构原则:
程序运行时层层深入调用,待最内层函数执行完毕后,再逐层向外返回。
课堂问答
Q1. 在C语言编程规范中,以下哪种函数调用/定义的写法是合法的?
A. 在main函数内部,直接定义一个全新的子函数sub。
本题正确答案
Option B
💡 核心知识点解析
C语言语法规则:严禁“函数嵌套定义”(故A、C错误),但完全允许“函数嵌套调用”(故B正确)。
B. 在main函数内部,调用一个已经在外部定义好的函数sub。
C. 在自定义函数A的内部定义函数B,并在B内部反向调用函数A。
课堂问答
Q2: 分析以下代码的执行顺序,最终输出?
void func2() { printf("2"); }
void func1() { printf("1"); func2(); }
int main() { func1(); printf("3"); return 0; }
请选择正确的输出结果:
A. 123
B. 132
C. 213
正确答案:A
符合函数调用栈的执行逻辑
🔍 执行流程拆解
1. main() 首先调用 func1(),输出 "1"。
2. func1() 继续调用 func2(),输出 "2"。
3. 所有函数返回后,main() 最后输出 "3"。
💡 核心知识点:
函数的嵌套调用严格遵循“先进后出”的栈机制,即先被调用的函数会后结束。
课堂练习
练习一:计算三角形面积
编写一个程序,计算一个三角形的面积。要求将“获取底”、“获取高”、“计算面积”这三个核心功能分别封装成独立的函数,并通过嵌套调用的方式实现整个计算逻辑。
STEP 01 · 获取底边长
定义get_base()函数,用于从用户输入中获取三角形的底边长。
STEP 02 · 获取三角形高
定义get_height()函数,用于从用户输入中获取三角形的对应高度。
核心逻辑:在calculate_area()中嵌套调用前序函数
课堂练习
程序文本:计算三角形面积
#include <stdio.h>
double get_base(); // 获取三角形的底
double get_height(); // 获取三角形的高
double calculate_area(); // 计算面积
main()
{ double area = calculate_area();
printf("三角形的面积是: %.2lf
", area); }
double get_base()
{ double base;
printf("请输入三角形的底: ");
scanf("%lf", &base);
return base; }
double get_height()
{ double height;
printf("请输入三角形的高: ");
scanf("%lf", &height);
return height; }
double calculate_area()
{ double b=get_base(),h=get_height(); // 嵌套调用
return (b*h)/2.0; } // 面积公式
课堂练习
课堂练习
练习二:计算学生平均分
题目要求:编写一个程序,实现对学生三门核心课程(语文、数学、英语)成绩的平均分计算。请严格按照以下两个函数定义的规范来完成代码的编写。
任务 A:定义总分函数
创建函数calculate_total_score(),接收三个成绩参数,计算并返回三门课程的成绩总和。
任务 B:定义平均分函数
创建函数calculate_average_score(),在其内部嵌套调用总分函数获取总和,再除以3得到平均分并返回。
课堂练习
程序文本:计算学生平均分
#include <stdio.h>
float calculate_total_score();
float calculate_average_score();
main()
{ float average=calculate_average_score();
printf("三门课程的平均分是: %.2f
", average);
}
float calculate_total_score()
{ float chinese, math, english;
printf("请输入语文成绩: "); scanf("%f", &chinese);
printf("请输入数学成绩: "); scanf("%f", &math);
printf("请输入英语成绩: "); scanf("%f", &english);
return chinese + math + english; }
float calculate_average_score()
{ float total=calculate_total_score(); // 嵌套调用
return total/3.0f; }
课堂练习
第二部分
知识讲授
函数的递归调用
知识讲授:函数递归调用的概念
知识讲授:函数递归调用的概念
核心定义
一个函数在其函数体内直接或间接地调用它自身,这种特殊的调用方式在程序设计中被称为“函数的递归调用”。
核心思想:大事化小
递归本质上是一种“分治”策略。它将一个大型复杂的问题,层层拆解为一个或多个与原问题逻辑相似,但规模更小的子问题,直到子问题小到可以被直接解决为止。
知识讲授:递归的两大核心要素
▍ 递归的执行两个阶段
递推阶段
不断调用自身,缩小问题规模,直到触发终止条件。
回归阶段
从终止条件开始,逐层返回计算结果,最终得到答案。
▍ 两大核心要素 (缺一不可)
递归终止条件
必须有一个明确的结束点,防止无限递归。
递归递推关系
定义如何将原问题分解为规模更小、同类的子问题。
图解:递归的“递推”与“回归”过程
知识讲授:无限递归的危害
❓ 如果没有递归终止条件会怎样?
程序会陷入无限递归,函数无休止地调用自身。每次调用分配的栈帧不断累积,最终导致栈溢出 ,程序因内存耗尽而崩溃。
💻 错误示范:无终止条件的递归函数
void infinite_recursion() {
printf("hehe
"); // 无限循环输出
infinite_recursion(); // 无终止,持续调用自身
}
图示:递归调用层层堆叠,像积木一样不断增高,最终导致内存“栈”被撑爆。
💡 核心警示:编写递归函数的第一件事,就是确定明确的终止条件!
例题分析:用递归法计算n!
题目核心要求
利用递归思想,编写程序计算正整数 n 的阶乘 (n!),重点掌握递归的“递推”与“回归”过程。
递归逻辑拆解
递推关系:n! = n × (n-1)!(大问题分解为小问题)
终止条件:0! = 1 且 1! = 1(停止递归的边界)
阶乘数学定义
若 n=0 或 1,n! = 1; 若 n>1,n! = n × (n-1)!
例题分析:用递归法计算n!
例题分析:用递归法计算n!
程序文本【7.8】用递归法计算n!
#include<stdio.h>
long ff(int n)
{ long f;
if(n<0)
printf("n<0,input error");
else if(n==0||n==1) f=1;
else f=ff(n-1)*n;
return (f);
}
main()
{ int n;
long y;
printf("
input a inteager number:
");
scanf("%d",&n);
y=ff(n);
printf("%d!=%ld",n,y);
}
例题分析:执行过程 (以 n=5 为例)
递推阶段 · 层层拆解 (Recursion)
ff(5)
= 5 × ff(4)
= 5 × (4 × ff(3))
= 5 × (4 × (3 × ff(2)))
= 5 × (4 × (3 × (2 × ff(1))))
💡 核心逻辑:不断调用自身,直到触发递归终止条件 (ff(1)=1)
回归阶段 · 层层计算 (Regression)
= 5 × (4 × (3 × (2 × 1)))
= 5 × (4 × (3 × 2)) ➔ = 5 × (4 × 6)
= 5 × 24
= 120 (最终结果)
🎉 计算逻辑:从已知的终止值开始,反向层层回代计算,得出最终答案
例题分析:用递归法计算n!
例题分析:典型问题 Hanoi 塔问题
▍ 问题描述
一块板上有三根针 A、B、C。A 针上套有 64 个大小不等的圆盘,大的在下,小的在上。要求将圆盘从 A 移到 C,每次仅移动一个,可借助 B 针,但任何时刻都必须保持大盘在下、小盘在上的规则。
▍ 核心解题思路 (递归思想)
1.基线条件:若 n=1,直接将圆盘从 A 针移动到 C 针,问题结束。
2.递归过程 (n>1):
a. 将 n-1 个圆盘从 A 移到 B (借助 C);
b. 将第 n 个(最大)圆盘从 A 直接移到 C;
c. 将 n-1 个圆盘从 B 移到 C (借助 A)。
A B C
A B C
63个盘子
①
③
②
例题分析:典型问题 Hanoi 塔问题——解题思路
分析将A座上3个盘子移到C座上的过程:
① 将A座上2个盘子移到B座上(借助C座)。
② 将A座上1个盘子移到C座上。
③ 将B座上2个盘子移到C座上(借助A座)。
其中第②步可以直接实现。第①步又可用递归方法分解为:
将A座上1个盘子从A座移到C座;
将A座上1个盘子从A座移到B座;
将C座上1个盘子从C座移到B座。
第③步可以分解为:
将B座上1个盘子从B座移到A座上;
将B座上1个盘子从B座移到C座上;
将A座上1个盘子从A座移到C座上。
将以上综合起来,可得到移动3个盘子的步骤为:
A→C,A→B,C→B,A→C,B→A,B→C,A→C。
共经历7步。由此可推出: 移动n个盘子要经历(2n-1)步。
例题分析:典型问题 Hanoi 塔问题
程序文本【7.9】Hanoi 塔问题
#include<stdio.h>
void move (int n,int x,int y,int z)
{ if(n==1)
printf("%c-->%c
",x,z);
else
{ move(n-1,x,z,y);
printf("%c-->%c
",x,z);
move(n-1,y,x,z);
}
}
main()
{ int h;
printf("
input number:
");
scanf("%d",&h);
printf("the step to moving %2d dishes:
",h);
move(h,'a','b','c');
}
例题分析:典型问题 Hanoi 塔问题
背诵记忆
01 / 递归定义
函数在其内部直接或间接调用自身的过程。这是递归最基础的概念,也是理解后续复杂逻辑的起点。
02 / 核心思想
核心策略是“大事化小”。将一个复杂的原问题,层层拆解为规模更小、但结构完全相同的子问题,直到子问题简单到可以直接求解。
03 / 两大核心要素
•终止条件:必须有明确的停止出口,否则会陷入无限递归。
•递推关系:建立大问题与小问题的转化公式,不断缩小规模。
04 / 完整执行过程
•递推阶段:从上往下,层层分解问题,直到触发终止条件。
•回归阶段:从下往上,将子问题的结果逐层合并,得到最终解。
课堂问答
Q1 · 核心考点:递归机制
递归函数的哪个组成部分是绝对必须的?如果缺失,程序运行时通常会导致崩溃?
A. return 语句
B. 递归终止条件
C. 循环语句 for/while
D. 函数参数
正确答案:B
必须包含“递归终止条件”
💡 考点深度解析
如果缺失递归终止条件,函数会陷入无限递归调用,最终导致栈内存溢出而崩溃。
注:return语句是函数完整性要求,递归逻辑本身并不依赖循环语句实现。
课堂练习
▍ 练习一:递归计算幂次方
📝 题目要求
编写一个递归函数power(int base, int exponent),用于计算基数 `base` 的指数 `exponent` 次幂。
💡 前提假设:输入的 `exponent` 为非负整数 (exponent ≥ 0)。
请在本地编程环境中新建文件,尝试编写代码实现该逻辑,并通过几组测试用例验证结果。
💡 递归核心逻辑提示
终止条件
当 exponent == 0 时,返回 1。
(数学定义:任何数的 0 次幂都等于 1)
递推关系
将大问题拆解为更小的子问题:
power(base, n) = base × power(base, n - 1)
课堂练习
程序文本:递归计算幂次方
#include<stdio.h>
int power(int base,int exponent);
main()
{ int b, e;// 定义底数和指数
printf("请输入底数和指数(非负整数): ");
scanf("%d %d", &b, &e);
int res =power(b, e);
printf("%d^%d = %d
", b, e, res); }
// 递归函数:计算幂次方核心逻辑
int power(int base,int exp)
{ if(exp==0) /*1.终止条件(Base Case)*/
return 1;// 任何数的0次幂为1
/*2.递推关系(Recursive Step)*/
return base*power(base,exp-1); }
课堂练习
课堂练习
练习二:递归计算斐波那契数列
📝 题目定义
已知 F(1)=1,F(2)=1,且当 n > 2 时,F(n) = F(n-1) + F(n-2)。请编写一个递归函数,计算并返回斐波那契数列的第 n 项值。
关键:终止条件
当 n == 1 或 n == 2 时,直接返回 1,结束递归。
核心:递推公式
将大问题拆解:F(n) = F(n-1) + F(n-2),层层递进。
课堂练习
程序文本:递归计算斐波那契数列
#include <stdio.h>
int fibonacci(int n);
main()
{ int n;// 定义项数n
printf("请输入要计算的项数n: ");
scanf("%d", &n);
int res = fibonacci(n);
printf("第 %d 项是: %d
", n, res);
}
int fibonacci(int n)
{ if(n==1||n==2)
return 1;
return fibonacci(n-1)+fibonacci(n-2);
}
课堂练习
课堂小结
课堂小结
函数的嵌套调用
📋 核心定义
在执行函数 A 的过程中,去调用另一个函数 B。
🧩 核心思想
模块化分解,将大任务拆解为多个子任务分工协作。
🔑 关键要素
关注函数的声明顺序与参数的正确传递逻辑。
⚖️ 优劣势分析
✅ 结构清晰,逻辑分明 | ❌ 层次过深可能影响代码可读性
函数的递归调用
🔄 核心定义
在执行函数 A 的过程中,直接或间接调用函数 A 自身。
📉 核心思想
采用分治策略,将复杂的大问题逐步拆解为更小的同类问题。
⚠️ 核心要素 (必须牢记)
终止条件 (Base Case)+ 递推关系 (Recursive Step)
⚖️ 优劣势分析
✅ 代码简洁优雅,解决特定复杂问题 | ❌ 可能造成栈溢出,部分场景效率较低
作业布置:
1. 尝试使用非递归(迭代)方法实现斐波那契数列,并对比递归效率。
2. 有5个学生坐在一起,问第5个学生多少岁,他说比第4个学生大2岁。问第4个学生岁数,他说比第3个学生大2岁。问第3个学生,又说比第2个学生大2岁。问第2个学生,说比第1个学生大2岁。最后问第1个学生,他说是10岁。编写程序求出第5个学生多大。
3.编写一个求X的Y次幂的递归函数,X为double型,Y为int型,要求从主函数输入X,Y的值,调用函数求其幂。
课后任务
谢谢观看
THANKS
$