函数(function)是拥有名称、可以反复调用的代码块。接收传入参数,执行一段逻辑,还可以按需返回数据。它构成 C 程序的基础单元,我们一直在写的 main,本身就是一个函数。把重复逻辑封装进函数,零散的代码语句才能组织成完整程序。

函数能做什么、能做到什么程度

先划定边界,C 语言的函数本身不复杂,但也有明确的限制:

  • 函数可以接收固定数量的参数(可变参数是后面才学的高级话题)。
  • 函数只能返回一个值,或者什么都不返回。
  • 参数是按值传递的,传进去的是那份值的拷贝。函数里改参数,改不到调用方外面的变量。想让函数改掉外面的变量,得用指针,那是后面章节的内容。

最基本的写法

下面的例子定义一个函数 foo:接收一个整数 bar,翻倍后返回:

int foo(int bar) {
    /* 干点什么 */
    return bar * 2;
}

int main() {
    foo(1);
    return 0;
}

调用函数就是"名字 + 括号":foo(1) 会把 1 作为 bar 传进去,执行完整个函数体。

先声明,后定义

C 语言有个规矩:函数在使用之前必须先被认识。你可以提前声明它(只给签名,不给实现),把真正的实现放在后面:

/* 函数声明:告诉编译器有这个函数 */
int foo(int bar);

int main() {
    /* 在 main 里调用 foo */
    printf("foo 的返回值是 %d\n", foo(1));
    return 0;
}

/* 函数定义:真正的实现 */
int foo(int bar) {
    return bar + 1;
}

声明(declaration)只交代返回类型、名字、参数类型,没有函数体;定义(definition)才给出大括号里的实现。声明一般放文件顶部,或放进头文件 .h 里让别人也能用。

void:不需要返回值的函数

有些函数只干活、不回话,返回类型写 void

void moo() {
    /* 干点什么,但不返回任何值 */
}

int main() {
    moo();   // 直接调用,没有返回值
    return 0;
}

无返回值和"返回 0"是两码事:void 函数不能用 return 值;当然你可以用不带值的 return; 提前结束它。

类型按功能分:库函数 vs 自定义函数

C 语言的函数分两大类:

  • 库函数(library functions),C 自带的功能,比如 printfscanfsqrt。用它们先包含对应头文件:#include <stdio.h>#include <math.h> 等。
  • 自定义函数(user-defined functions),你自己写的。按有没有参数、有没有返回值又可分四种:无参无返、有参无返、无参有返、有参有返。
C 语言函数定义与调用示例运行
int square(int x) {   // 有参有返
    return x * x;
}

void greet() {        // 无参无返
    printf("你好!\n");
}

完整例子:求一个数的平方

#include <stdio.h>

/* 函数定义 */
int square(int x) {
    return x * x;
}

int main() {
    /* 调用并接收返回值 */
    int result = square(5);
    printf("5 的平方是:%d\n", result);   // 25
    return 0;
}

语法拆开看四件事:

  • 返回类型,函数返回什么类型就写什么,不返回写 void
  • 函数名,和变量名同一套命名规则,要唯一。
  • 参数列表,传进来的输入;没有输入就空着或写 void
  • 函数体,大括号里真正执行的代码。

按值传递:改不到外面的变量

C 参数默认按值传递。看这个例子,函数里怎么改参数都影响不了 main 里的变量:

#include <stdio.h>

void addOne(int x) {
    x = x + 1;          // 改的是 x 的拷贝
    printf("函数里的 x:%d\n", x);   // 6
}

int main() {
    int num = 5;
    addOne(num);
    printf("main 里的 num:%d\n", num);  // 还是 5
    return 0;
}

想让函数真的改动外部变量,只能把那个变量的地址传进去,这正是下一章指针的用武之地。

函数调用时发生了什么(栈帧)

每次调用函数,系统都会在栈上给它单独划一块区域,叫栈帧(stack frame),用来放它的参数和局部变量;函数一返回,这块区域自动回收。所以:

  • 函数的局部变量互不干扰,同一个函数被调用多次,各有各的一份。
  • 栈空间有限,递归太深会把栈用光(下一节讲递归就绕不开这事)。

熟练掌握函数之后,下一章节再来学习函数调用自身的写法,也就是递归。