函数(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 自带的功能,比如
printf、scanf、sqrt。用它们先包含对应头文件:#include <stdio.h>、#include <math.h>等。 - 自定义函数(user-defined functions),你自己写的。按有没有参数、有没有返回值又可分四种:无参无返、有参无返、无参有返、有参有返。
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),用来放它的参数和局部变量;函数一返回,这块区域自动回收。所以:
- 函数的局部变量互不干扰,同一个函数被调用多次,各有各的一份。
- 栈空间有限,递归太深会把栈用光(下一节讲递归就绕不开这事)。
熟练掌握函数之后,下一章节再来学习函数调用自身的写法,也就是递归。