为什么需要函数?
在学习如何调用之前,我们先明白为什么要用函数。

(图片来源网络,侵删)
- 代码重用: 将常用的功能封装成一个函数,可以在程序的任何地方多次调用,避免重复编写相同的代码。
- 模块化: 将一个复杂的大任务分解成多个小而简单的函数,使代码结构更清晰,易于理解和维护。
- 简化逻辑: 主函数(
main)可以变得更简洁,只负责调用不同的函数来完成整体流程,每个函数负责一个具体的子任务。
函数调用的基本三要素
一个完整的函数调用包含三个核心部分:
- 函数声明: 告诉编译器“这个函数存在,它叫什么名字,返回什么类型,需要什么参数”,这通常放在文件的开头。
- 函数定义: 提供函数的具体实现,即“这个函数具体要做什么”,这可以放在文件的任何位置(通常是声明之后)。
- 函数调用: 在需要执行该功能的地方,使用函数名并传入实际参数来执行函数。
一个最简单的例子
下面是一个完整的、可运行的 C 程序,演示了如何定义、声明和调用一个函数。
示例代码:add_numbers.c
#include <stdio.h>
// 1. 函数声明
// 告诉编译器有一个名为 add 的函数,它接收两个 int 参数,并返回一个 int 值。
// 这个声明也被称为 "函数原型" (Function Prototype)。
int add(int a, int b);
// 2. 函数定义
// 这才是 add 函数的实际实现。
int add(int a, int b) {
printf(" -> 正在执行 add 函数,计算 %d + %d\n", a, b);
int sum = a + b;
return sum; // 返回计算结果
}
// 3. 主函数 - 程序的入口
int main() {
int num1 = 10;
int num2 = 20;
int result;
printf("主函数:准备调用 add 函数...\n");
// 4. 函数调用
// 将 num1 和 num2 作为实际参数传递给 add 函数。
// add 函数执行完毕后,返回值被赋给 result 变量。
result = add(num1, num2);
printf("主函数:add 函数调用结束,结果是 %d\n", result);
return 0;
}
如何编译和运行
- 将代码保存为
add_numbers.c。 - 打开终端或命令提示符。
- 使用 GCC 编译器进行编译:
gcc add_numbers.c -o add_numbers
- 运行生成的可执行文件:
./add_numbers
预期输出
主函数:准备调用 add 函数...
-> 正在执行 add 函数,计算 10 + 20
主函数:add 函数调用结束,结果是 30
代码解析
#include <stdio.h>: 包含标准输入输出库,这样我们才能使用printf函数。int add(int a, int b);: 函数声明,它向编译器承诺:我们稍后会定义一个名为add的函数,它接受两个int类型的参数,并返回一个int类型的值,如果没有这个声明,main函数中的add(num1, num2)调用可能会在编译时出错。int add(int a, int b) { ... }: 函数定义。int add: 函数名是add,返回值类型是int。(int a, int b): 这是形式参数,它们是函数内部的局部变量,用于接收调用时传入的值。- 函数体,包含了要执行的代码。
return sum;:return关键字用于从函数中返回一个值,并立即终止函数的执行。
result = add(num1, num2);: 函数调用。add: 函数名。num1, num2: 这是实际参数,它们是main函数中已经存在的变量,它们的值被复制传递给add函数的形式参数a和b。result = ...:add函数返回的值被赋给result变量。
函数参数传递方式
C 语言中,函数参数传递是“传值调用” (Call by Value)。
这意味着在调用函数时,实际参数的值会被复制一份,传递给形式参数,函数内部操作的是这个副本,不会改变原始实际参数的值。

(图片来源网络,侵删)
示例:证明传值调用
#include <stdio.h>
// 定义一个函数,试图修改传入的参数
void modify_value(int x) {
printf(" modify_value 函数内部:x 的初始值是 %d\n", x);
x = 100; // 修改的是副本 x
printf(" modify_value 函数内部:修改后 x 的值是 %d\n", x);
}
int main() {
int num = 10;
printf("main 函数中:调用 modify_value 前,num 的值是 %d\n", num);
modify_value(num); // 传递 num 的值给 x
printf("main 函数中:调用 modify_value 后,num 的值是 %d\n", num);
return 0;
}
输出结果
main 函数中:调用 modify_value 前,num 的值是 10
modify_value 函数内部:x 的初始值是 10
modify_value 函数内部:修改后 x 的值是 100
main 函数中:调用 modify_value 后,num 的值是 10
可以看到,尽管 modify_value 函数内部将 x 修改为 100,但 main 函数中的 num 变量仍然是 10,这就是传值调用的特性。
无参数和无返回值的函数
函数可以没有参数,也可以没有返回值。
- 无返回值: 使用
void作为返回类型。 - 无参数: 在参数列表中使用
void或者直接留空(现代 C 标准更推荐用void以增加清晰度)。
示例:打印欢迎信息
#include <stdio.h>
// 函数声明:无参数,无返回值
void print_welcome_message(void);
int main() {
print_welcome_message(); // 调用函数
return 0;
}
// 函数定义
void print_welcome_message(void) {
printf("====================================\n");
printf(" 欢迎使用 C 语言世界!\n");
printf("====================================\n");
}
递归函数
一个函数可以调用它自己,这种技术称为递归,递归通常用于解决可以被分解成相似子问题的问题,例如计算阶乘、斐波那契数列等。
示例:使用递归计算阶乘
n! = n * (n-1) * (n-2) * ... * 1
0! = 1

(图片来源网络,侵删)
#include <stdio.h>
// 函数声明
long factorial(int n);
int main() {
int number = 5;
printf("%d 的阶乘是 %ld\n", number, factorial(number));
return 0;
}
// 函数定义:递归实现
long factorial(int n) {
// 基线条件:递归的终止点
if (n == 0 || n == 1) {
return 1;
}
// 递归步骤:函数调用自身
else {
return n * factorial(n - 1);
}
}
执行过程(计算 factorial(5))
factorial(5)->5 * factorial(4)factorial(4)->4 * factorial(3)factorial(3)->3 * factorial(2)factorial(2)->2 * factorial(1)factorial(1)-> 返回1(满足基线条件)
结果开始层层返回:
6. factorial(2) 返回 2 * 1 = 2
7. factorial(3) 返回 3 * 2 = 6
8. factorial(4) 返回 4 * 6 = 24
9. factorial(5) 返回 5 * 24 = 120
警告:递归必须有一个明确的基线条件来终止递归,否则会导致栈溢出,程序崩溃。
多文件项目中的函数调用
在实际开发中,一个项目通常由多个 .c 文件和 .h 文件组成,这是一种良好的组织方式。
.c文件:源文件,包含函数的定义。.h文件:头文件,包含函数的声明、宏定义、结构体定义等,供其他文件包含。
示例结构
my_project/
├── main.c
├── calculator.h
└── calculator.c
calculator.h (头文件)
#ifndef CALCULATOR_H #define CALCULATOR_H // 函数声明 int add(int a, int b); int subtract(int a, int b); #endif // CALCULATOR_H
#ifndef ... #define ... #endif是一个头文件保护机制,防止同一个头文件被重复包含导致编译错误。
calculator.c (源文件)
#include "calculator.h" // 包含自己的头文件,确保声明和定义一致
// 函数定义
int add(int a, int b) {
return a + b;
}
int subtract(int a, int b) {
return a - b;
}
main.c (主程序文件)
#include <stdio.h>
#include "calculator.h" // 包含我们自己创建的头文件
int main() {
int x = 10, y = 5;
printf("x + y = %d\n", add(x, y));
printf("x - y = %d\n", subtract(x, y));
return 0;
}
如何编译多文件项目
# 将 main.c 和 calculator.c 一起编译 gcc main.c calculator.c -o my_program # 运行 ./my_program
输出
x + y = 15
x - y = 5
这种方式的优势在于:
- 模块化:
calculator.c和calculator.h构成了一个独立的计算器模块。main.c只需要知道函数的声明(通过头文件),不需要关心函数是如何实现的。 - 可重用性:
calculator.c和calculator.h可以被其他项目轻松复用。 - 编译效率:修改
calculator.c时,只需要重新编译它,而无需重新编译main.c(在更复杂的项目构建系统中如 Makefile)。
| 概念 | 描述 | 示例 |
|---|---|---|
| 函数声明 | 告诉编译器函数的签名(名、返回类型、参数),不包含实现。 | int add(int a, int b); |
| 函数定义 | 提供函数的具体实现代码。 | int add(int a, int b) { return a+b; } |
| 函数调用 | 执行函数,传递实际参数。 | result = add(10, 20); |
| 传值调用 | 默认的参数传递方式,传递的是值的副本。 | 函数内修改参数不影响外部变量。 |
| 递归 | 函数调用自身,必须有基线条件。 | long fact(int n) { if (n<=1) return 1; return n*fact(n-1); } |
| 多文件项目 | 将声明放在 .h 文件,定义放在 .c 文件,通过 #include 关联。 |
#include "mylib.h" |
掌握函数调用是编写结构化、可维护 C 程序的基础,从简单的单文件程序开始,逐步过渡到多文件项目,是提升编程能力的必经之路。
