通知图标

欢迎访问津桥芝士站

基础篇05-1——函数基础

来自AI助手的总结
文章详细解释了C语言中函数参数的传递机制,包括形参与实参的区别、值传递与地址传递的特点及应用,并通过示例代码加深理解。

一、函数参数:形参与实参的深入理解

1. 形式参数(形参)

定义:形参是函数定义时声明的变量,位于函数名后的括号内。它们像函数的”输入接口”,规定了函数需要接收的数据类型和数量。

特点

  • 形参只在函数内部有效(局部变量特性)

  • 形参在函数调用时被创建,函数结束时销毁

  • 形参本质是实参的临时副本(值传递时)

示例解析

void printSum(int a, int b) { // a和b是形参
    printf("Sum: %d", a + b);
}

这里的int aint b就像两个空的收件箱,等待外界传递数据进来。

2. 实际参数(实参)

定义:实参是调用函数时实际传递的具体数值或变量,是真正参与运算的数据。

特点

  • 实参可以是常量、变量或表达式

  • 实参与形参的数量和类型必须严格匹配

  • 实参的值会被复制到形参(值传递时)

示例解析

int main() {
    int x = 5, y = 3;
    printSum(x, y+2); // x和y+2是实参
    return 0;
}

这里传递的实参x的值是5,y+2的计算结果是5,这两个值会被复制给形参a和b。

3. 值传递 vs 地址传递

通过交换函数示例理解参数传递的本质:

示例代码

#include <stdio.h>

// 值传递版本(无效)
void swap(int x, int y) {
    x = x ^ y;  // 步骤1:x = 3 ^ 5 = 6
    y = y ^ x;  // 步骤2:y = 5 ^ 6 = 3
    x = x ^ y;  // 步骤3:x = 6 ^ 3 = 5
    // 此时形参x=5, y=3,但实参未改变!
}

// 地址传递版本(有效)
void swap1(int *x, int *y) {
    *x = *x ^ *y;
    *y = *y ^ *x;
    *x = *x ^ *y;
}

int main() {
    int a = 3, b = 5;
    
    printf("原始值:a=%d b=%d\n", a, b);
    
    swap(a, b);  // 值传递
    printf("值传递后:a=%d b=%d\n", a, b);  // 输出3 5
    
    swap1(&a, &b); // 地址传递
    printf("地址传递后:a=%d b=%d\n", a, b); // 输出5 3
    
    return 0;
}

内存变化示意图

值传递过程:
实参 a(3) --> 形参 x(3)
实参 b(5) --> 形参 y(5)
交换操作只影响x和y的内存空间

地址传递过程:
&a --> x指针
&b --> y指针
通过*x和*y直接操作实参内存空间

关键理解

  • 值传递像复印文件:修改复印件不影响原件

  • 地址传递像共享文件:通过路径直接修改原件

  • 异或交换原理:利用a ^ a = 0a ^ 0 = a的特性实现无临时变量的交换

二、数组作为函数参数详解

1. 一维数组传参

传递方式:数组名本质是指向数组首元素的指针

两种等效的声明方式

// 数组形式声明(更直观)
void printArray(int arr[], int size) {
    for(int i=0; i<size; i++){
        printf("%d ", arr[i]); // 通过下标访问
    }
}

// 指针形式声明(更本质)
void printArray(int *arr, int size) {
    for(int i=0; i<size; i++){
        printf("%d ", *(arr+i)); // 通过指针运算访问
    }
}

内存示意图

main函数中的数组:
[ 1 | 2 | 3 | 4 | 5 ]
 ↑
arr

传递给函数的是首地址0x1000(假设)
函数内部通过指针访问实际内存

注意事项

  1. 数组长度信息会丢失,必须额外传递size参数

  2. 在函数内修改数组元素会影响原始数组

  3. sizeof运算符在函数内无法获取数组总大小

2. 二维数组传参

内存本质:二维数组按行连续存储,例如int arr[3][4]的内存布局:

行0:1  2  3  4
行1:5  6  7  8
行2:9 10 11 12
实际内存:1,2,3,4,5,6,7,8,9,10,11,12

传参规范

// 正确声明方式(必须指定列数)
void printMatrix(int arr[][4], int rows) {
    // 访问元素arr[i][j]
}

// 等效指针形式(较少使用)
void printMatrix(int (*arr)[4], int rows) {
    // arr是指向含有4个int元素的数组的指针
}

为什么必须指定列数?

  • 编译器需要知道每行的跨度来计算内存地址

  • 访问arr[i][j]的地址计算公式:
    基地址 + i(列数sizeof(int)) + j*sizeof(int)

常见错误示例

// 错误!缺少列数声明
void printMatrix(int arr[][], int rows, int cols) 

// 错误!二维数组不能转换为int**
void printMatrix(int **arr, int rows, int cols)

3. 动态数组传参(C99变长数组)

C99标准支持

void process2DArray(int rows, int cols, int arr[rows][cols]) {
    // 可以处理任意大小的二维数组
    // 但需要注意编译器兼容性
}

使用示例

int main() {
    int rows = 3, cols = 4;
    int arr[rows][cols];
    process2DArray(rows, cols, arr);
    return 0;
}

三、参数传递综合对比表

参数类型 传递方式 内存影响 示例声明 特点
基本类型变量 值传递 不影响 void func(int a) 创建副本,修改不影响原值
指针变量 地址传递 影响 void func(int *p) 可修改指针指向的内容
一维数组 地址传递 影响 void func(int arr[]) 传递首地址,需额外传长度
二维数组 地址传递 影响 void func(int arr[][4]) 必须指定列数
结构体 值传递 不影响 void func(struct s) 大结构体传参效率低
结构体指针 地址传递 影响 void func(struct s *) 推荐使用方式

四、编程实践建议

  1. 参数设计原则

    • 对需要修改的原始数据使用指针传递

    • 对大型数据结构(如结构体)优先使用指针传递

    • 数组传参总是传递指针,注意不要越界访问

  2. 防御性编程技巧

    // 使用const保护不需要修改的参数
    void printArray(const int *arr, int size) {
        // 尝试修改arr[i]会导致编译错误
    }
    
    // 检查指针有效性
    void safeSwap(int *a, int *b) {
        if(a == NULL || b == NULL) {
            printf("Error: Null pointer!");
            return;
        }
        // 交换操作...
    }
  3. 调试技巧
    • 在函数入口打印参数值和地址

    • 使用调试器观察形参/实参的内存变化

    • 对数组参数添加边界检查

void printArray(int *arr, int size) {
    if(size <= 0) {
        printf("Invalid size!");
        return;
    }
    // 正常处理...
}

记住:理解参数传递机制是掌握函数编程的关键!

请登录后发表评论

    没有回复内容