跳转至

导学案6 · 函数:把重复的逻辑打包

前置要求:导学案5(会数组、会循环、会 switch) 参考课程:浙江大学 翁恺《C 语言程序设计》第 7 章(函数) 预计用时:1.5 小时(按需自取) 本节目标:说清函数是什么、有哪几个部分;会定义、声明、调用函数;理解参数是"值传递"


6.0 序

你已经用过很多函数了:printf、scanf、main——它们都是函数。这一节你要学会自己写函数。

为什么需要函数?三句话:

  1. 重复的代码只写一遍。 同一段逻辑要用五次,就写成一个函数调用五次,改 bug 只改一处。
  2. 把大问题拆成小块。 一个 500 行的 main 没人看得懂;拆成十个各司其职的函数,每个都能单独看懂、单独测试。
  3. 别人的成果直接拿来用。 你调 printf 时从没看过它的源码。以后 STM32 的 HAL 库、实验室的飞控代码,全是"别人写好的函数",你要做的就是调用。

本节路线:函数是什么 → 四个组成部分 → 定义、调用与声明 → 参数怎么传 → 库函数 → 练习

本节不讲:函数指针、递归、多文件工程(.h / .c 分离)。另外 C 语言没有"重载"这个词对应的东西,别处看到不用管。


6.1 函数是什么

把函数想成一台加工机器:你往里投料(参数),它干活,然后吐出结果(返回值)。

         参数(投料)
             ↓
     ┌───────────────┐
     │   函数(机器) │
     └───────────────┘
             ↓
         返回值(成品)

第一个自己写的函数:

#include <stdio.h>

void sayHello(void)            /* 自定义函数:打招呼 */
{
    printf("你好,飞行器创新实验室\n");
}

int main(void)
{
    sayHello();                /* 调用:想调几次调几次 */
    sayHello();
    return 0;
}

输出:

你好,飞行器创新实验室
你好,飞行器创新实验室

注意两件事:

  • 定义函数不会执行它,那只是"登记了一台机器";调用(写 sayHello();)才会真的跑。
  • main 也是一个函数,是程序的大门。你写的其他函数,都是从 main 里被叫起来的。

6.2 函数的四个组成部分

返回值类型   函数名(参数列表)
{
    函数体(要执行的代码)
    return 返回值;
}

拿一个真函数对号入座:

int max(int a, int b)      /* 返回值类型 int,函数名 max,参数列表 (int a, int b) */
{
    if (a > b) {
        return a;
    }
    return b;              /* 返回值 */
}
部分 例子 说明
返回值类型 int 这台机器吐出什么类型的结果;不需要吐东西就写 void
函数名 max 命名规则和变量完全一样:字母数字下划线、不能数字开头、区分大小写
参数列表 (int a, int b) 投什么料;不需要投料就写 (void)
函数体 + return { ... return b; } 怎么干活、吐什么;return 同时会立刻结束这个函数

两个必须记住的点:

  1. void 有两种用法:写在返回值位置表示"不返回任何东西";写在参数位置表示"不接受任何参数"。
  2. 非 void 函数必须每条路都有 return。 如果某条路走到函数末尾还没 return,返回值就是垃圾,而且编译器常常只给警告、不给错误。
#include <stdio.h>

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

int main(void)
{
    int r = max(3, 7);                          /* 返回值可以存起来 */
    printf("max(3, 7) = %d\n", r);
    printf("max(10, max(4, 6)) = %d\n", max(10, max(4, 6)));   /* 也可以套着用 */
    return 0;
}

输出:

max(3, 7) = 7
max(10, max(4, 6)) = 10

6.3 定义、调用与声明

函数要先"登记"才能被调用。登记有两种方式:定义(连机器一起造出来)和声明(只贴一张"有这么台机器"的说明书)。

最省事的写法:把函数定义写在 main 前面,本导学案全部采用这种写法。

但有时你希望 main 在最上面(读代码时先看主干),函数放在后面。这时编译器从上往下读,读到 main 里的 max(3, 7) 时还不认识 max,会报错。解决办法是先声明:

#include <stdio.h>

int max(int a, int b);        /* 声明(函数原型):只有签名,没有函数体,以分号结尾 */

int main(void)
{
    printf("max(3, 7) = %d\n", max(3, 7));
    return 0;
}

int max(int a, int b)         /* 定义:真正的机器在这里 */
{
    if (a > b) {
        return a;
    }
    return b;
}

输出:

max(3, 7) = 7

声明和定义的差别只有一处:声明没有函数体,结尾是分号。 声明里的参数名甚至可以省略(int max(int, int);),但写上更清楚。

一句话预告(不展开):以后写多文件的 STM32 工程,声明放在 .h 文件里,定义放在 .c 文件里。你在 HAL 库里看到的那满屏 .h,干的就是这件事。


6.4 参数是怎么传的:值传递(本节最重要的一节)

先看一个实验:

#include <stdio.h>

void swap(int a, int b)       /* a、b 叫形参 */
{
    int t = a;
    a = b;
    b = t;
    printf("函数内:a = %d, b = %d\n", a, b);
}

int main(void)
{
    int x = 1, y = 2;         /* x、y 叫实参 */
    swap(x, y);
    printf("函数外:x = %d, y = %d\n", x, y);
    return 0;
}

输出:

函数内:a = 2, b = 1
函数外:x = 1, y = 2

函数里明明换好了,外面的 x 和 y 却纹丝不动。为什么?

C 语言的参数传递是"值传递":调用时把实参的值复制一份交给形参,函数里操作的是副本。 函数一结束,副本销毁,原件毫发无损。

所以 swap(x, y) 里被交换的,是 x、y 的两份复印件。这个结论非常重要,请记牢。

顺带两个推论:

  1. 形参名和实参名可以不同(上面就是 a/b 对 x/y),它们是互相独立的变量。
  2. 函数里声明的变量叫局部变量,只在这个函数活着期间存在;两个函数里的同名变量互不相干,main 里的也一样。

那如果我真的想让函数改到外面的变量呢?——把地址传进去。这就是下一节(导学案7 · 指针)要解决的事,也是 scanf 为什么要写 & 的原因。这里先留个悬念。


6.5 库函数:调用别人写好的函数

#include <stdio.h> 的本质,是把别人写好的函数的"说明书"拿进来,然后你就能调用了。常用的几个头文件:

头文件 里面有什么 例子
<stdio.h> 输入输出 printf、scanf、getchar
<stdlib.h> 杂项:随机数、取绝对值、内存 rand、srand、abs
<math.h> 数学 sqrt、fabs、pow
<string.h> 字符串 strlen、strcpy、strcmp

6.5.1 随机数

#include <stdio.h>
#include <stdlib.h>          /* rand / srand */
#include <time.h>            /* time */

int main(void)
{
    srand((unsigned)time(NULL));        /* 用当前时间做种子,每次运行结果才不一样 */

    for (int i = 0; i < 5; i++) {
        printf("%d ", rand() % 101);    /* 0 ~ 100 的随机整数 */
    }
    printf("\n");

    printf("学号范围示例:%d\n", rand() % 11 + 100);   /* 100 ~ 110 */
    return 0;
}

输出(每次运行都不同,下面是其中一次的结果):

63 44 34 18 81
学号范围示例:101

两条要点:

  • rand() 返回一个很大的随机整数,取余是控制范围的通用办法:rand() % 101 得 0~100,rand() % 11 + 100 得 100~110。
  • 以上随机数本质是伪随机,它通过一个种子通过特定规律生成一串数字,如果种子一样那么生成的数字也就一样,写 srand(...) 的目的就是让时间当种子,实现每次调用时种子变化,产生的数字也就变成了随机数。

6.5.2 数学函数

#include <stdio.h>
#include <math.h>

int main(void)
{
    printf("sqrt(2)    = %.4f\n", sqrt(2.0));
    printf("fabs(-3.5) = %.1f\n", fabs(-3.5));
    printf("2 的 10 次方 = %.0f\n", pow(2.0, 10.0));
    return 0;
}

输出:

sqrt(2)    = 1.4142
fabs(-3.5) = 3.5
2 的 10 次方 = 1024

<math.h> 里的函数参数和返回值都是 double,所以写 sqrt(2.0) 比写 sqrt(2) 更规范(写 2 也能跑,会被自动转成 double)。


6.6 综合练习

本节练习用于自练与自查,不强制完成、不需要提交;需要提交的是实验室统一布置的作业(作业一 / 作业二)。 参考代码统一放在本节末尾,做完之前不要翻。

练习 1|偶数判断

写一个函数 int isEven(int n),是偶数返回 1,否则返回 0。主函数读入一个整数,输出"是偶数"或"不是偶数"。

练习 2|三数取最大

写一个函数 int max3(int a, int b, int c),返回三个数里的最大值。要求函数体内调用两次两数取大的逻辑(先写一个 max2)。

练习 3|温度转换函数

把导学案2的摄氏转华氏改写成函数 double c2f(double c),主函数读入摄氏温度,输出华氏温度(保留一位小数)。

练习 4|质数与选数

写一个函数 int isPrime(int n) 判断质数(n >= 2)。然后读入 4 个正整数,统计它们两两相加得到的 6 个和里有几个质数。(这就是作业二第 2 题)

练习 5(选做)|用函数写冒泡排序

用函数实现导学案5的冒泡排序:void sortDesc(int a[], int len),把数组从大到小排好。提示:数组作参数的写法导学案7会正式讲,但你现在就可以先试 int a[] 这种写法。


6.7 本节小结与自查

学完本节,你应该能做到:

  • 说清函数的四个组成部分,知道 void 在返回值位置和参数位置分别是什么意思
  • 自己定义并调用一个函数,知道"定义不等于执行"
  • 知道声明(函数原型)长什么样、什么时候必须写
  • 说清"值传递":为什么函数里改形参,改不到外面的实参
  • 知道局部变量的作用范围
  • 会用 rand() / srand() 产生指定范围的随机数

下一节:导学案7 · 指针。6.4 留下的悬念——怎么让函数改到外面的变量——在那里解开。

6.8 练习参考代码(做完再看)

练习 1 参考代码

#include <stdio.h>

int isEven(int n)
{
    if (n % 2 == 0) {
        return 1;
    }
    return 0;
}

int main(void)
{
    int n = 0;

    printf("请输入一个整数:");
    scanf("%d", &n);

    if (isEven(n)) {
        printf("%d 是偶数\n", n);
    } else {
        printf("%d 不是偶数\n", n);
    }

    return 0;
}

练习 2 参考代码

#include <stdio.h>

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

int max3(int a, int b, int c)
{
    return max2(max2(a, b), c);
}

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

输出:max3(3, 9, 5) = 9

练习 3 参考代码

#include <stdio.h>

double c2f(double c)
{
    return c * 9.0 / 5.0 + 32.0;
}

int main(void)
{
    double c = 0.0;

    printf("请输入摄氏温度:");
    scanf("%lf", &c);

    printf("%.1f 摄氏度 = %.1f 华氏度\n", c, c2f(c));

    return 0;
}

练习 4 参考代码

#include <stdio.h>

int isPrime(int n)
{
    if (n < 2) {
        return 0;
    }
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) {
            return 0;
        }
    }
    return 1;
}

int main(void)
{
    int a[4] = {0};
    int count = 0;

    printf("请输入 4 个正整数:");
    for (int i = 0; i < 4; i++) {
        scanf("%d", &a[i]);
    }

    for (int i = 0; i < 4; i++) {
        for (int j = i + 1; j < 4; j++) {
            int s = a[i] + a[j];
            if (isPrime(s)) {
                count++;
            }
        }
    }

    printf("质数的个数 = %d\n", count);

    return 0;
}

输入 1 2 3 4,输出 质数的个数 = 4,和作业二给的示例一致。


函数是"把一段逻辑起个名字"。从这一节起,你写的不再是"一长串步骤",而是"一堆各司其职的小机器"。

这个习惯会直接决定你以后能不能读懂飞控代码:那里几乎每一行都在调用别人写好的函数。

作者:Mike-lin300

最后更新:2026-09-25

更新说明:无(新编写)