C 语言书
模块二 · 流程与数据 / 第 10 课

10 · 函数

核心 40 分钟 跳到过关题
学习目标
  • 会定义和调用函数
  • 理解参数是「拷贝」进去的
  • 会用返回值把结果交回来

为什么拆函数

函数 = 一段有名字、可重复使用的代码。写函数时想清楚三件事:

  1. 输入:参数列表(要哪些信息)
  2. 输出:返回类型(算完交回什么);只做事可返回 void
  3. 职责:名字要能说明它干什么,例如 add、print_line、max2

调用 add(3, 4) 时:把 3、4 拷贝给 a、b → 执行函数体 → return 把结果交回调用处。

#include <stdio.h>

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

void say_hello(void)
{
    printf("hello\n");
}

int main(void)
{
    int s = add(3, 4);
    say_hello();
    printf("sum = %d\n", s);  // 7
    return 0;
}

返回值类型

  • int add(...):算完交回一个 int
  • void say_hello(void):只做事,不交回值
  • 调用时:int s = add(3,4); 或 say_hello();

return 的类型必须和声明一致。声明 int 却不 return,返回值是垃圾;声明 void 却 return 10,编译报错。

参数是拷贝

把变量传给函数,函数里改的是自己的那份副本,外面的变量不会变。这叫传值(pass by value)。

推论:若参数是 int/float 这类小盒子,函数无法「顺手改掉调用方的变量」——要么 return 新值,要么传地址(指针,第 13 课)。

#include <stdio.h>

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

int main(void)
{
    int n = 1;
    try_change(n);
    printf("%d\n", n);  /* 还是 1,不是 100 */
    return 0;
}

声明(原型)

如果函数写在 main 后面,要在前面先「打招呼」,否则编译器不认识:

#include <stdio.h>

int max2(int a, int b);  /* 声明 */

int main(void)
{
    printf("%d\n", max2(3, 9));
    return 0;
}

int max2(int a, int b)   /* 定义 */
{
    return a > b ? a : b;
}

局部变量与作用域

函数里定义的变量,出了大括号就没了。两个函数可以各自有同名变量,互不干扰。

#include <stdio.h>

void f1(void)
{
    int n = 1;
    printf("f1 n=%d\n", n);
}

void f2(void)
{
    int n = 2;   /* 和 f1 的 n 无关 */
    printf("f2 n=%d\n", n);
}

int main(void)
{
    f1();
    f2();
    return 0;
}

函数设计小原则

  • 一个函数尽量只做一件事
  • 名字用动词或动宾:print_line、calc_sum
  • 参数不要太多(超过 4 个就考虑用结构体)
  • 先想「输入是什么、输出是什么」,再写函数体

三元运算符(函数里很好用)

条件 ? A : B 条件真取 A,否则取 B。

int max = (a > b) ? a : b;
int absx = (x < 0) ? -x : x;

单片机场景 · 函数:输入、输出与范围

一个函数完成一件事。小类型相加也可能产生更大的结果,避免截断返回值。

边界与验证:面向常见 8 位 unsigned char 环境的桌面示例,输出 300。返回类型改为 unsigned int;桌面程序用 int main(void),不照搬特定嵌入式环境的 void main。

#include <stdio.h>

unsigned int add(unsigned char a, unsigned char b)
{
    return (unsigned int)a + (unsigned int)b;
}

int main(void)
{
    printf("%u\n", add(200, 100));
    return 0;
}

本课过关

必做

函数里修改 int 形参,会改变调用处的实参吗?

必做作业
  1. 写函数 int square(int n) 返回 n*n。
  2. 写 void print_line(void) 打印一行分隔线。
  3. 解释为什么 try_change 改不了 main 里的 n。
先自己写完,再点开看解答

1:square

int square(int n)
{
    return n * n;
}

2:分隔线

void print_line(void)
{
    printf("----------------\n");
}

3:为何改不了 n:调用 try_change(n) 时,实参 n 的值被复制给形参 x。x 在函数自己的栈帧里,x=100 只动了副本。main 的 n 从未被写入。要改外面,用指针:void try_change(int *x){ *x=100; } 调用写 try_change(&n);。