16. 函数基础.md 3.7 KB

16 函数基础

16.1 完整概念讲解

函数是C语言中代码复用的基本单元。它封装了一段可重复调用的代码块,接收输入参数并可返回结果。

函数的组成要素:

  • 返回类型:函数返回值的类型,void表示无返回值
  • 函数名:标识符,遵循命名规则
  • 参数列表:形参(形式参数)的类型和名称
  • 函数体:包含具体实现的代码块

形参与实参:

  • 形参(Parameter):函数定义时声明的变量,是占位符
  • 实参(Argument):函数调用时传递的实际值
  • 实参按值传递给形参,形参是实参的副本

函数调用栈帧:

  1. 调用函数时,将实参从右到左压入栈中
  2. 将返回地址(调用后的下一条指令地址)压栈
  3. 将旧的帧指针(EBP)压栈
  4. 分配局部变量空间
  5. 函数执行完毕,局部变量出栈
  6. 恢复旧的帧指针
  7. 弹出返回地址,跳转继续执行

16.2 核心API/语法

// 函数定义
返回类型 函数名(参数列表) {
    // 函数体
    return 返回值;  // 非void函数必须return
}

// 函数声明(原型)
返回类型 函数名(参数类型1, 参数类型2, ...);

// 函数调用
函数名(实参1, 实参2, ...);

16.3 代码示例(完整可编译,附gcc命令)

#include <stdio.h>

// 函数声明(原型)
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;
}

编译命令:

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语言不能直接返回数组。可以返回指向数组的指针(但不能返回局部数组的指针),或使用结构体包装数组。