函数是C语言程序设计的核心基石之一,合理运用函数能让代码结构清晰、复用性高、逻辑简洁。本文围绕C语言函数,从概念到实践,结合案例、内存分析等,深入讲解函数知识体系,助你扎实掌握函数用法。
函数是一段可重复使用的代码块,它接收输入(参数),经过内部逻辑处理,输出结果(返回值,可选 )。打个比方,函数就像工厂:你给工厂送原材料(参数),工厂加工后产出产品(返回值),比如计算两数之和的函数,输入两个数,输出相加结果 。
C语言标准库提供大量现成函数(如printf
、scanf
、strcpy
等),使用时需包含对应头文件。头文件像“说明书”,告诉编译器函数的声明(名字、参数、返回值类型)。例如:
printf
输出,要包含
,因为 printf
的声明在这个头文件里;strcpy
,需包含
。strlen
为例#include
#include
int main() {
char str[] = "Hello";
// strlen 计算字符串长度(不包含 '\0')
size_t len = strlen(str);
printf("字符串长度:%zu\n", len);
return 0;
}
关键点:
,让编译器知道 strlen
怎么用;strlen
接收字符串参数,返回无符号整数(size_t
类型 ),表示字符串有效字符数量。库函数文档通常包含:
strlen
是计算字符串长度;size_t strlen(const char *s);
,明确返回值类型、函数名、参数及参数类型;strlen
的 s
是要计算长度的字符串指针;strlen
返回字符串有效字符数;自定义函数的基本语法:
返回值类型 函数名(参数列表) {
// 函数体:实现功能的代码
// 可包含变量定义、逻辑运算、return 语句(按需)
}
示例:实现两数相加的自定义函数
// 返回值类型 int,函数名 Add,参数是两个 int 类型的 x、y
int Add(int x, int y) {
return x + y; // 函数体,返回两数之和
}
#include
// 判断是否为闰年的函数
_Bool is_leap_year(int y) {
if (((y % 4 == 0) && (y % 100 != 0)) || (y % 400 == 0)) {
return 1; // 是闰年返回真(1)
}
return 0; // 不是返回假(0)
}
int main() {
int year = 2024;
if (is_leap_year(year)) {
printf("%d 年是闰年\n", year);
} else {
printf("%d 年不是闰年\n", year);
}
return 0;
}
分析:
is_leap_year
,接收年份参数 y
,通过闰年规则判断,返回布尔值;实参是调用函数时,传递给函数的实际数据,可以是常量、变量、表达式等。比如:
int result = Add(3, 5);
这里调用 Add
函数时,3
和 5
就是实参,是真正传给函数参与运算的值。
形参是函数定义时,括号里的参数,它是“形式上”的参数,用于接收实参的值 。例如:
int Add(int x, int y) {
// x、y 是形参,定义函数时用来“占位”
return x + y;
}
内存角度分析:
形参只有在函数被调用时,才会在栈区(内存区域)分配空间,函数执行结束,形参占用的内存会释放。实参传递给形参,相当于把实参的值“拷贝”给形参
关键点:
void Swap(int a, int b) {
int temp = a;
a = b;
b = temp;
// 这里修改的是形参 a、b,实参不会变
}
int main() {
int x = 1, y = 2;
Swap(x, y);
// x、y 值还是 1、2,因为形参修改没影响实参
printf("x=%d, y=%d\n", x, y);
return 0;
}
return
后可以跟数值、表达式。表达式会先计算,再返回结果。例如:int Add(int x, int y) {
// 先算 x + y,再返回结果
return x + y;
}
void
时,return
可以单独用(也可省略,函数执行到末尾自动返回 ),用于提前结束函数。比如:void PrintInfo() {
printf("执行到这\n");
// 提前返回,后面代码不执行
return;
printf("不会执行到这\n");
}
return
返回的值会隐式转换为函数声明的返回值类型。例如:// 函数返回 int 类型
int Func() {
// 返回 float 类型 3.14,会被转成 int(丢失小数部分,变成 3 )
return 3.14;
}
return
执行后,函数立即结束,后面代码不再运行。比如:int Max(int a, int b) {
if (a > b) {
// a 大,返回 a,函数结束
return a;
}
// a 不大,返回 b
return b;
}
if
等分支时,要保证所有分支都有 return 返回,否则编译器报错。比如:// 错误示例:如果 a == b,没有 return 返回,编译器报错
int Compare(int a, int b) {
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
// 这里少了 a == b 的 return 处理
}
// 正确写法,补充 a == b 的返回
int Compare(int a, int b) {
if (a > b) {
return 1;
} else if (a < b) {
return -1;
}
return 0;
}
数组传参时,实际传递的是数组首元素的地址,函数形参可以写成以下形式(本质一样 ):
// 写法 1:数组形式(常用,直观)
void PrintArray(int arr[], int size) {
// 写法 2:指针形式,等价于写法 1
void PrintArray(int *arr, int size) {
for (int i = 0; i < size; i++) {
// 通过地址访问数组元素
printf("%d ", arr[i]);
}
}
int main() {
int arr[] = {1, 2, 3};
// 传递数组名(首元素地址)和数组长度
PrintArray(arr, 3);
return 0;
}
函数形参里的数组,无法通过 sizeof
获取原数组长度!因为传递的是地址,形参 arr[]
退化成指针,sizeof(arr)
得到的是指针大小(如 4 或 8 字节,取决于系统)。所以必须手动传数组长度(像上面 size
参数 )。
函数里调用另一个函数,就是嵌套调用。比如:
// 计算 a + b
int Add(int a, int b) {
return a + b;
}
// 计算 a + b + c,嵌套调用 Add
int AddThree(int a, int b, int c) {
// 先调 Add 算 a+b,结果再和 c 相加
return Add(Add(a, b), c);
}
int main() {
int sum = AddThree(1, 2, 3);
// 结果是 6(1+2=3,3+3=6 )
printf("sum=%d\n", sum);
return 0;
}
执行流程:AddThree
调用 Add
,Add
执行完返回结果,AddThree
继续用返回值运算,最后返回给 main
。
把一个函数的返回值,作为另一个函数的参数,像链条一样连接调用。例如:
#include
#include
int main() {
char str[] = "Hello";
// 链式调用:strlen 返回字符串长度,作为 printf 的参数
printf("字符串长度:%d\n", strlen(str));
return 0;
}
执行顺序:先执行 strlen(str)
得到长度,再把这个长度传给 printf
输出。再看一个有趣的嵌套 printf
例子:
#include
int main() {
// 从最内层开始执行
printf("%d", printf("%d", printf("%d", 43)));
return 0;
}
执行拆解:
printf("%d", 43)
:输出 43
,返回值是输出字符的个数(2个字符:‘4’和’3’ );printf("%d", 2)
:输出 2
,返回值是输出字符个数(1个字符:‘2’ );printf("%d", 1)
:输出 1
;4321
,这就是链式调用的嵌套执行逻辑,很考验对函数返回值的理解!函数定义(实现逻辑)和调用在同一个 .c
文件时,直接写函数定义即可。例如:
#include
// 函数定义(声明 + 实现一体)
int Add(int x, int y) {
return x + y;
}
int main() {
// 直接调用,没问题
int sum = Add(1, 2);
printf("sum=%d\n", sum);
return 0;
}
实际项目中,代码会拆分到多个 .c
文件,这时候需要函数声明和函数定义分离:
.c
文件(如 func.c
),包含函数的具体实现:// func.c
int Add(int x, int y) {
return x + y;
}
func.h
),告诉其他文件“函数长什么样”:// func.h
// 声明 Add 函数,分号结尾
int Add(int x, int y);
.c
文件(如 main.c
)里,包含头文件,就能用函数:// main.c
#include
// 包含声明,让编译器知道 Add 怎么用
#include "func.h"
int main() {
int sum = Add(3, 4);
printf("sum=%d\n", sum);
return 0;
}
编译链接:
main.c
包含 func.h
,知道 Add
的声明;func.c
里 Add
的定义,把调用和实现关联起来。static
和 extern
(作用域控制)static
修饰局部变量(静态局部变量)void Test() {
// 静态局部变量,只会初始化一次,函数结束后内存不释放
static int num = 0;
num++;
printf("num=%d\n", num);
}
int main() {
Test(); // 输出 num=1
Test(); // 输出 num=2(因为 num 没被释放,继续自增)
Test(); // 输出 num=3
return 0;
}
特点:
static
修饰全局变量// file1.c
// 静态全局变量,作用域限制在 file1.c 文件内
static int g_num = 10;
// file2.c
// 想访问 file1.c 的 g_num?不行!因为 static 限制了作用域
// 编译报错:未定义的标识符
extern int g_num;
作用:用 static
修饰全局变量,能隐藏变量,让它只能在当前 .c
文件里被访问,其他文件看不到,避免全局变量冲突。
static
修饰函数// func.c
// 静态函数,只能在 func.c 文件内被调用
static int Add(int x, int y) {
return x + y;
}
// main.c
// 想调用 func.c 的 Add?不行!static 限制了作用域
extern int Add(int x, int y);
效果:static
修饰函数后,函数只能在定义的 .c
文件内使用,其他文件无法调用,用于封装内部逻辑,减少对外暴露。
extern
关键字(跨文件访问)extern
用来声明外部变量/函数(定义在其他文件里 ),告诉编译器:“这个变量/函数在别的地方定义,你先记着,链接时找它”。
示例(跨文件访问全局变量):
// file1.c
// 定义全局变量 g_data
int g_data = 20;
// file2.c
#include
// 声明外部全局变量 g_data,告诉编译器它在其他文件定义
extern int g_data;
int main() {
// 可以访问到 file1.c 的 g_data,输出 20
printf("g_data=%d\n", g_data);
return 0;
}
注意:extern
用于“声明”,不是“定义”。变量/函数的定义只能有一次,声明可以多次。
函数是C语言编程的核心工具,掌握以下要点,就能灵活运用函数:
return
,避免分支遗漏、类型不匹配等错误;static
、extern
控制变量