# 16 函数基础 ## 16.1 完整概念讲解 函数是C语言中代码复用的基本单元。它封装了一段可重复调用的代码块,接收输入参数并可返回结果。 **函数的组成要素:** - **返回类型**:函数返回值的类型,`void`表示无返回值 - **函数名**:标识符,遵循命名规则 - **参数列表**:形参(形式参数)的类型和名称 - **函数体**:包含具体实现的代码块 **形参与实参:** - **形参(Parameter)**:函数定义时声明的变量,是占位符 - **实参(Argument)**:函数调用时传递的实际值 - 实参按值传递给形参,形参是实参的副本 **函数调用栈帧:** 1. 调用函数时,将实参从右到左压入栈中 2. 将返回地址(调用后的下一条指令地址)压栈 3. 将旧的帧指针(EBP)压栈 4. 分配局部变量空间 5. 函数执行完毕,局部变量出栈 6. 恢复旧的帧指针 7. 弹出返回地址,跳转继续执行 ## 16.2 核心API/语法 ```c // 函数定义 返回类型 函数名(参数列表) { // 函数体 return 返回值; // 非void函数必须return } // 函数声明(原型) 返回类型 函数名(参数类型1, 参数类型2, ...); // 函数调用 函数名(实参1, 实参2, ...); ``` ## 16.3 代码示例(完整可编译,附gcc命令) ```c #include // 函数声明(原型) int add(int a, int b); void print_message(const char *msg); double calculate_average(int arr[], int size); int main() { // 函数调用 int result = add(10, 20); printf("10 + 20 = %d\n", result); print_message("Hello, Function!"); int scores[] = {85, 92, 78, 95, 88}; double avg = calculate_average(scores, 5); printf("Average: %.2f\n", avg); return 0; } // 函数定义 int add(int a, int b) { return a + b; } void print_message(const char *msg) { printf("%s\n", msg); } double calculate_average(int arr[], int size) { int sum = 0; for (int i = 0; i < size; i++) { sum += arr[i]; } return (double)sum / size; } ``` **编译命令:** ```bash gcc -Wall -Wextra -o function_basics function_basics.c ./function_basics ``` ## 16.4 注意事项与易错点 1. **实参求值顺序未定义**:C语言不保证实参从左到右求值,避免在实参中修改同一变量 2. **数组作为参数退化为指针**:传递数组时只传递首地址,需额外传递长度 3. **返回局部变量地址**:函数返回后局部变量已销毁,返回其地址是未定义行为 4. **缺少return语句**:非void函数未return,返回值是未定义的 5. **形参类型不匹配**:实参与形参类型不一致可能导致隐式转换错误 ## 16.5 面试要点(3-5个Q&A) **Q1: 值传递和引用传递的区别?** A: C语言只有值传递。传指针本质上也是值传递(传递指针的副本),但可以通过指针修改原数据。C++才有真正的引用传递。 **Q2: 函数调用时栈帧是如何变化的?** A: 调用时:压入参数→压入返回地址→保存旧EBP→分配局部变量。返回时:释放局部变量→恢复EBP→弹出返回地址→跳转继续执行。 **Q3: 为什么需要函数原型?** A: 函数原型让编译器在调用前知道函数的签名(返回类型、参数类型),可以进行类型检查,并确保调用方式正确。 **Q4: 数组作为函数参数时,sizeof得到的是什么?** A: 数组退化为指针,sizeof得到的是指针大小(32位系统4字节,64位系统8字节),而非数组总大小。因此必须额外传递数组长度。 **Q5: 函数可以返回数组吗?** A: C语言不能直接返回数组。可以返回指向数组的指针(但不能返回局部数组的指针),或使用结构体包装数组。