C 函数基础
更新时间:2026-08-26。本文是
languages/c/主题入门层第 18 篇,函数主题的第一篇。前面的程序都是"一坨"代码,函数让代码有了"零件"——每个零件干一件事,可以反复使用、单独调试。这也是整个 C 系列的重要转折:从此程序不再是直线,而是拼装。
本文要回答的问题
- 一个函数由哪几部分组成?
- 形参和实参是什么关系?
- 函数调用时,计算机内部发生了什么?
一、函数的结构
c
返回类型 函数名(参数列表) {
函数体
return 返回值;
}c
int add(int a, int b) { // 返回 int,接收两个 int 参数
int sum = a + b;
return sum; // 把结果还给调用者
}| 部件 | 说明 | 示例 |
|---|---|---|
| 返回类型 | 函数返回什么类型的值 | int |
| 函数名 | 标识符,snake_case | add |
| 参数列表 | 调用时需要提供的数据 | int a, int b |
return | 结束函数并把值送回 | return sum; |
void | 不返回值 / 不收参数 | void print_log(void) |
main 也是一个函数,由系统调用。程序的"主线"就是从 main 开始,依次调用别的函数,像接力一样。
二、调用:实参 → 形参
c
#include <stdio.h>
int add(int a, int b) { // a、b 是形参
return a + b;
}
int main(void) {
int x = 3, y = 5;
int r = add(x, y); // x、y 是实参
printf("%d\n", r); // 8
return 0;
}- 实参(调用时写的
x, y)传给形参(函数定义里的a, b); - 传的是值:
add(x, y)把 x 的值 3 复制给 a,y 的值 5 复制给 b。函数里改 a 不影响外面的 x——这就是"值传递",下一篇细讲; - 参数个数和类型必须匹配(编译器会检查)。
三、返回值:三种形态
c
int f1(void) { return 42; } // 返回一个值
void f2(void) { printf("无返回\n"); } // 不返回值,void 类型
int f3(void) { return; } // 提前结束但不返回值(函数类型非 void 时是错误)void 函数可以没有 return,也可以 return; 提前结束。void 函数里写 return 5; 是编译错误;非 void 函数"跑完不 return"是未定义行为(返回值是垃圾)。
c
int check(int n) {
if (n < 0) return -1; // 提前返回
return n * 2; // 正常返回
}函数里可以有多个 return,每个都是"到此为止,把值送回"。别为了"只能有一个出口"把代码绕晕——清晰的提前返回比单出口强。
四、函数放在哪:先声明后使用
c
#include <stdio.h>
int add(int a, int b); // 声明(原型):告诉编译器"有这个东西"
int main(void) {
printf("%d\n", add(1, 2));
return 0;
}
int add(int a, int b) { // 定义:真正的实现
return a + b;
}C 要求"先声明后使用"。把定义写在 main 前面就能直接用(定义自带声明);定义在后面的,得先写原型声明。多文件工程的声明放头文件,见函数声明、头文件与原型。
漏写声明的报错:implicit declaration of function 'add'(C99 起是错误)——"我没见过这个函数,你怎么就用了"。
五、调用时发生了什么(简化版)

这个"栈帧"模型解释了三个现象:参数是复制(值传递)、局部变量函数结束就消失(作用域/生命周期)、递归为什么能无限层直到栈溢出。专家层内存布局与 ABI会展开到寄存器级别。
六、常见坑对照
| 坑 | 现象 | 对策 |
|---|---|---|
| 定义在调用之后没声明 | implicit declaration | 提前写原型 |
| 函数内改形参影响实参 | 外面没变(其实是正常的) | 理解值传递,想改外面用指针 |
| void 函数写 return 值 | 编译错误 | 记得类型是 void |
| 非 void 函数忘 return | 垃圾返回值 | 所有路径都要 return |
| 函数名和变量名冲突 | 编译错误 | 命名区分 |
七、与本站主线衔接
- 值传递的细节与"想改实参怎么办",见下一篇 值传递与传地址;
- 函数调用的栈帧/寄存器级细节,见专家层内存布局与 ABI;
- 函数调用是性能剖析的热点维度(perf 的调用图、火焰图),见 tools/perf。
一句话总结
函数 = 返回类型 + 名字 + 参数 + return;实参按值复制给形参,函数内改形参动不了实参;先声明后使用,原型要提前——函数是代码的零件箱,从这开始程序从"直线"变成"拼装"。