Skip to content
难度基础(★)
建议时长45分钟

17.1.5 函数调用方式

函数调用是本章高频点。考试最爱问:函数内部修改形参,函数外的实参到底变不变。答案取决于参数传递方式。

函数调用的基本角色

函数声明通常包括返回类型、函数名、参数列表和函数体。参数列表里的变量叫形参,调用时传入的值或变量叫实参。

text
int add(int x, int y) {
  return x + y;
}

add(a, b)

这里 xy 是形参,ab 是实参。

传值调用

传值调用把实参的值复制给形参。函数内部修改的是副本,不会影响调用者手中的原变量。

调用前函数内操作调用后
a=3, b=4swap(x, y) 中交换 xya=3, b=4

本质上,形参和实参是两个存储单元,只是开始时值相同。

引用/地址调用

引用调用或地址调用把变量的位置传给函数,形参可以访问实参所在的存储单元。函数内部修改目标对象,调用者能看到变化。

调用前函数内操作调用后
a=3, b=4通过引用或指针交换目标值a=4, b=3

这也是为什么引用调用的实参通常必须是可被赋值的对象,不能随便写成一个表达式。表达式 a+b 没有稳定的可写存储位置,不能作为普通引用参数的目标。

调用栈:函数为什么能回来

函数调用时,系统会在调用栈中保存返回地址、参数、局部变量等信息。嵌套调用和递归调用能正确返回,就是因为每次调用都有自己的活动记录。

mermaid
flowchart TB
  A["main 调用 f"] --> B["压入 f 的活动记录"]
  B --> C["f 调用 g"]
  C --> D["压入 g 的活动记录"]
  D --> E["g 返回,弹出 g"]
  E --> F["f 返回,弹出 f"]

递归并不神秘,它只是函数调用自己。每一层递归都占用一份栈空间,所以必须有结束条件,否则会无限递归或栈溢出。

解题方法

  1. 先判断是传值还是引用/地址。
  2. 标出形参是否有独立副本。
  3. 如果是引用/地址,继续判断修改的是指针本身还是指向的对象。
  4. 最后看函数返回后调用者变量是否被改变。

小练习

题:函数 swap(x, y) 内部交换了 xy,若调用方式为传值,调用者的两个变量会交换吗?

答:不会。传值调用只交换形参副本,实参不受影响。

自查

  1. 传值调用和引用调用的根本差别是什么?
  2. 为什么递归需要调用栈?
  3. 引用参数为什么通常不能传入普通表达式?