C语言函数完整整理
一、函数核心作用
- 模块化拆分代码,减少重复冗余代码,提高复用性;
- 降低代码耦合,逻辑分层清晰,便于阅读、修改、调试;
- 实现功能封装,对外只暴露接口,隐藏内部实现细节
二、函数完整语法
1. 函数定义(实现)
返回值类型 函数名(形参列表) { 函数体(功能代码); return 返回值; // void函数可省略return }各部分说明
- 返回值类型
int/char/float/double/结构体/指针:函数运算结束返回对应数据;void:无返回值,函数仅执行操作,无需return;- 补充:非void函数必须写return,否则返回随机垃圾值
- 函数名 遵循标识符规则:字母、数字、下划线,不能数字开头;推荐动宾命名
get_max() print_arr(),见名知意 - 形参列表 格式:
类型1 变量1, 类型2 变量2; 无参数写void(标准规范,fun(void)),空括号fun()老式写法不推荐 - 函数体 大括号不可省略,内部是实现逻辑;
- return
return 数值/变量;:终止当前函数,把值返回给调用处;return;:仅结束void函数,无返回数据
2. 函数调用
格式:函数名(实参列表);
int max(int a,int b){ return a>b?a:b; } int main(){ int res = max(10,20); // 接收返回值 max(5,8); // 不接收返回值,直接丢弃结果 return 0; }调用规则
- 实参与形参个数、顺序、数据类型必须一一匹配;
- 实参可以是常量、变量、表达式;
- 函数不调用则不执行,调用一次执行一次
3. 函数声明
作用
若函数定义写在main之后,编译器识别不到函数,报隐式声明警告,需提前声明
语法(声明只写头,无函数体)
返回值类型 函数名(形参类型列表); // 示例 int max(int a, int b); void print_str(char str[]);使用场景
- 单个.c文件内,函数定义在主函数下方;
- 多文件工程:函数写在其他
.c,在头文件.h统一声明,外部文件#include即可调用。
三、函数调用底层流程
- 保护现场:调用时将下一条指令地址、局部寄存器压入栈;
- 分配形参栈空间,将实参拷贝给形参(值传递);
- 执行函数内部代码;
- 恢复现场:函数结束,弹出栈中保存的地址,回到调用位置继续执行;
- 释放函数栈内所有局部变量,局部变量生命周期结束。
补充:函数栈空间有限,递归过深会栈溢出程序崩溃
四、函数传参三大核心方式
1. 值传递(普通变量传参)
规则:实参拷贝一份副本给形参,形参修改不影响外部实参
void change(int n){ n = 100; // 仅修改副本,main里num不变 } int main(){ int num = 10; change(num); printf("%d",num); // 输出10 }适用场景:仅读取数据,不需要修改外部变量
2. 地址传递(一级指针传参)
实参传递变量地址,形参用指针接收,通过*修改原变量内存,外部实参同步变化
void change(int *n){ *n = 100; // 直接修改main中num的内存 } int main(){ int num = 10; change(&num); printf("%d",num); // 输出100 }适用场景:
- 函数内需要修改外部普通变量;
- 函数返回多个结果;
- 传递数组(数组名本质首地址)
3. 二级指针传参
实参传递指针变量的地址,形参类型**接收,可在函数内修改外部指针本身的指向
void set_str(char **p){ *p = "hello c"; // 修改外部指针pstr的指向 } int main(){ char *pstr = "test"; set_str(&pstr); }典型用途:操作指针数组、动态分配内存返回堆地址
五、数组作为函数参数
1. 一维整形数组传参
两种等价形参写法,数组形参会退化指针,函数内sizeof(数组名)仅得8字节指针大小,必须手动传长度:
// 写法1 void print(int arr[], int len); // 写法2(推荐,直观体现指针本质) void print(int *arr, int len);2. 一维字符数组(字符串)传参
字符串自带\0结束符,无需传长度:
int mystrlen(char str[]); int mystrlen(char *str);3. 二维数组传参
行数可省略,列绝对不能省略,二维数组名是数组指针int (*)[列]
void show(int arr[][4], int row); void show(int (*arr)[4], int row);二维字符数组同理:
void print_strs(char str[][100], int line);六、递归函数
定义
函数直接/间接调用自身,用递归代替循环完成重复逻辑
必备条件
- 递归出口(终止条件):必须存在结束分支,否则无限递归栈溢出;
- 每次递归向出口靠近
优缺点
- 优点:代码简洁,树、阶乘、斐波那契等场景逻辑直观;
- 缺点:频繁栈保护/恢复,运行效率低于循环,深度过大会栈溢出
// 递归求阶乘 int fact(int n){ if(n == 1) return 1; // 递归出口 return n * fact(n-1); }七、特殊函数拓展(笔记补充)
1. 指针函数(返回指针的函数)
返回值为类型*,禁止返回局部栈变量地址(函数结束栈销毁,野指针)。 合法返回值:全局变量、static静态变量、字符串常量、堆malloc地址、传入的参数地址
char *get_str(){ static char buf[20] = "hello"; // static生命周期全局,合法 return buf; }2. 函数指针(指向函数的指针)
存储函数入口地址,多用于回调函数解耦代码。 语法:返回值(*指针名)(形参列表)
int add(int a,int b){return a+b;} int (*pfun)(int,int) = add; // 定义并赋值 int res = p(3,5); // 通过指针调用函数3. main主函数
程序入口,系统自动调用,标准规范写法:
int main(void){ return 0; // 0代表程序正常退出 }八、变量作用域&函数搭配补充
- 局部变量(函数内/形参) 作用域仅限当前
{},栈存储,函数结束自动销毁,默认随机值; - static局部静态变量 生命周期全局(程序运行全程),仅第一次调用初始化,保留上次值;
- 全局变量 所有函数均可访问,数据区存储,默认初始0,多文件用
extern声明
九、高频易错点
- 函数声明和定义返回值、形参类型必须完全一致,否则编译报错;
- 值传递无法修改外部变量,改值必须传地址;
- 数组作参数退化为指针,不能用sizeof求数组长度;
- 递归必须写出口,否则栈溢出崩溃;
- 指针函数不能返回局部栈数组/变量地址;
- void函数内
return后不能加数值; - 二维数组传参,列数不可省略;
- 函数实参求值顺序自右向左