欢迎光临
我们一直在努力

函数基础——复用、组织与值传递

为什么需要函数

三个原因:

1. 复用:写一次用多次,不用每次重写
2. 组织代码:给逻辑起名字,calculateArea() 比"第 47-62 行"好找得多
3. 隔离变量:函数里定义的变量不会跟外面的同名变量冲突

函数的基本结构

返回类型 函数名(参数列表) {
      // 函数体
      return 返回值;
  }

  几种形式: 

// 无参数,无返回值
  void sayHello() {
      printf("Hello!\\n");
  }

  // 有参数,有返回值
  int add(int a, int b) {
      return a + b;
  }

  // 有参数,无返回值
  void printSum(int a, int b) {
      printf("%d\\n", a + b);
  }

void 表示没有返回值

参数列表为空可以写 void 或不写:void f() 或 void f(void) 

参数传递:值传递

C 语言只有值传递——函数收到的是参数的副本,不是原变量本身。 

void tryToChange(int x) {
      x = 100;  // 只改了副本
  }

  int main() {
      int a = 5;
      tryToChange(a);
      printf("%d\\n", a);  // 还是 5,没变
      return 0;
  }

想在函数里修改外部变量怎么办?用指针——传地址进去。

返回值

return 结束函数并把值带回
函数类型是 void 时可以省略 return,或写 return;(不带值)
一个函数可以有多个 return(比如 if-else 各一个)

  int max(int a, int b) {
      if (a > b) return a;
      return b;
  }

注意:不写 return 时,返回值是未定义的(垃圾值),不要依赖它。

  声明 vs 定义

这是两个容易混淆的概念:

声明(原型) 定义
内容 只有函数签名 签名 + 函数体
结尾 分号 ; 花括号 {}
作用 告诉编译器函数长什么样

函数的实际实现

  // 声明
  int add(int a, int b);

  // 定义
  int add(int a, int b) {
      return a + b;
  }

判断方法:看有没有 {},不看放在什么位置。

为什么需要声明?

  C 语言编译器从上往下扫描代码。如果函数定义在 main 后面,编译器遇到 add(3, 4) 时还不知道 add 是什么——报错。

  两种解决方式:
  1. 把函数定义写在 main 前面
  2. 在 main 前面加声明(函数原型)

  实际项目中推荐方式 2,把声明放头文件(.h),定义放源文件(.c)。

作用域

变量有作用域——在哪个 {} 里定义的,就只在那个 {} 里有效。

  int x = 10;  // 全局变量——整个文件都能用

  void foo() {
      int y = 20;  // 局部变量——只在 foo 里有效
      printf("%d %d\\n", x, y);  // 能访问 x 和 y
  }

  int main() {
      printf("%d\\n", x);  // 能访问 x
      // printf("%d\\n", y);  // 编译报错!y 不在这里
      foo();
      return 0;
  }

局部变量遮蔽全局变量

  int x = 10;

  void foo() {
      int x = 20;  // 这个 x 遮蔽了全局的 x
      printf("%d\\n", x);  // 输出 20,不是 10
  }

  建议

少用全局变量。 任何函数都能改全局变量,出了 bug 很难定位是哪个函数改的。

递归

函数可以调用自己——这叫递归。

int factorial(int n) {
      if (n <= 1) return 1;         // 基线条件
      return n * factorial(n – 1);  // 递归调用
  }

执行过程(以 factorial(4) 为例):

  factorial(4)
  = 4 × factorial(3)
  = 4 × 3 × factorial(2)
  = 4 × 3 × 2 × factorial(1)
  = 4 × 3 × 2 × 1
  = 24

递归的两个必要条件

  1. 基线条件(停止条件):什么时候不再调用自己
  2. 递归步骤:每次调用必须向基线条件靠近

  没有基线条件 → 无限递归 → 栈溢出(stack overflow)。

递归VS循环

递归 循环
效率 每次调用消耗栈空间 不额外消耗栈空间
可读性 某些问题更直观(树、分治) 简单问题更直观
适用 树遍历、分治、回溯 已知次数的重复操作

  大多数场景用循环更高效,但某些问题用递归更自然。

常见误区速查

误区 正确理解
函数能修改外部变量 值传递,改的是副本,不影响原变量
不写 return 也能拿到正确值 值传递,改的是副本,不影响原变量
不写 return 也能拿到正确值 返回值是未定义的垃圾值,不要依赖
声明和定义看位置 看有没有 {},有是定义,没有是声明
全局变量好用就多用 少用,任何函数都能改,出 bug 难定位
递归不需要基线条件 必须有,否则栈溢出

总结

函数 = 复用 + 组织 + 隔离变量
C 语言只有值传递,函数拿到的是副本
声明只有签名加 ;,定义有 {},判断看有没有花括号
局局部变量只在函数内有效,会遮蔽同名全局变量
递归必须有基线条件,否则栈溢出

赞(0)
未经允许不得转载:171主机测评 » 函数基础——复用、组织与值传递
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址