函数定义
看完你能讲清函数定义、参数传递机制与返回值的完整栈帧变化
按 空格/→ 演示下一步
全部页面点击任意一页,跳回舞台从这页播放
函数定义
看完你能讲清函数定义、参数传递机制与返回值的完整栈帧变化
函数到底是什么
定义告诉你函数「长什么样」:名字、参数、返回值。但它真正在做什么?为什么说万物皆可函数化?走进一家工厂,从流水线里看清函数的本质。
原料对应输入,流水线对应函数体,产品对应返回值
函数的组成结构
从中心向外看, 四条分支对应一个函数语法上的四个槽位。
函数定义的基本语法
一段带类型注解的 Python 函数,能清晰看到返回类型、函数名、参数列表和函数体四部分。
L3 是函数名与 def 关键字,L4 是参数列表,L5 用 `->` 声明返回类型,L8 是真正返回值的语句;L11 演示如何调用并接收返回值。
最简函数:无参无返回值
一个最小的 void 函数:不接收参数,也不返回结果,只负责执行一段动作。
L3 用 void 表示无返回值,() 表示无参数;L4 函数体内没有 return;L8 调用时直接写函数名加空括号。三处特征共同定义了一个『无参无返回值』函数。
带参数函数的定义
通过 power 函数,看清形参声明、类型注解和不同调用方式。
形参在 def 行带类型注解;默认参数让调用更灵活;位置与关键字是实参传参的两种方式。
有返回值函数的定义
展示一个返回计算结果的函数,重点看 return 语句与返回类型如何一致。
L1 用 -> float 声明返回类型;L4 return 把值送出;L6 接住返回值;L7 用 type() 校验——三者共同保证「返回类型一致性」。
形参和实参的本质区别
上一页我们写了带参数的函数,知道参数要写在 def 后面的括号里。但同一个括号里其实藏着两种完全不同的角色:定义时写的「形」,和调用时塞进去的「实」。这一页我们就拆清楚。
地址栏是占位的形参,实际填写的「北京市海淀区中关村」是实参
值传递的完整过程
自上而下读,看实参 x 与形参 n 在调用前后的内存地址和值如何变化。
传值 vs 传地址
传值和传地址表面上都是「传参数」,但本质区别是函数能否改动外部变量。混淆时会出现「代码跑了,原数据却没变」的诡异 bug。
- 传递的是实参的副本
- 函数内修改不影响原变量
- 适合只读、计算等不改原数据的场景
- 传递的是实参的内存地址
- 函数内修改会直接影响原变量
- 适合需要修改外部数据的场景
参数传递的常见错误
数组名作为实参传入函数时,会自动退化为指针,长度信息丢失。
形参写 int arr[] 只是语法糖,编译器实际当成指针处理;函数内 sizeof(arr) 是 8 字节而非 20,改 arr[i] 等于直接改原数组。
函数声明的语法
看原型声明里的形参名,为何不会与文件作用域的同名变量打架。
原型里小括号中的形参名只活在「函数原型作用域」里,声明结束就消散,不会污染外层同名变量;定义时形参名也可与原型不同,类型才是契约。
声明与定义的区别与联系
一图看清两者的边界
例题:求最大值函数
从需求分析到函数设计,再到完整实现,看一个求最大值函数如何诞生。
L11 是函数签名;L13 是带返回值的出口;L21 是实参 x、y 传给形参的过程。
例题:阶乘递归函数
用阶乘演示递归:函数在自己的定义里调用自己,靠基准情形终止。
递归三要素齐了:函数定义(L1)、基准情形终止(L3)、递归调用自身(L6);L9 展示一次完整调用并打印。
函数定义要点总结
- ✓返回类型是函数的输出契约,无返回值也是一种约定
- ✓参数传递本质都是值拷贝,区别只在于副本指向什么
- ✓声明是接口、定义是实现,分离后编译器才能跨文件协作
- ✓形参随每次调用诞生又消亡,递归就靠这个新生机制
- ✓多数 bug 出现在边界:缺返回值、空指针、误改实参
课后思考
先自己想,再对照参考答案——三个问题层层递进,答案点到为止。
参考答案C 语言要求先声明后使用——编译器从上往下扫描,看到调用时必须已经知道函数签名(返回类型、参数列表),否则无法做类型检查。声明就是给编译器的一张「身份证」。
参考答案判断要看两方面:是否需要修改原值(语义),以及拷贝代价(性能)。结构体、大数组传地址更高效,普通 int、char 值传递更简单安全。
参考答案每次递归都会压栈,系统栈空间有限(通常 1~8MB),n 太大就会 stack overflow。改用循环迭代即可避免,且常数更小、效率更高。