文章目录

C 语言函数:参数传递与返回值

发布于 2026-04-05 08:20:56 · 浏览 14 次 · 评论 0 条

C 语言函数:参数传递与返回值

函数是 C 语言的基石。掌握参数的传递机制和返回值的处理方式,是写出健壮代码的关键一步。本文将深入剖析 C 函数的核心机制,帮助你理解底层原理并规避常见陷阱。


返回值:函数如何"反馈"结果

C 语言通过 return 语句将计算结果传递回调用者。返回值的类型必须在函数声明时明确指定。

基本返回规则

#include <stdio.h>

// 计算两数之和,返回 int 类型
int add(int a, int b) {
    return a + b;  // 将结果返回给调用者
}

int main() {
    int result = add(10, 20);
    printf("10 + 20 = %d\n", result);
    return 0;
}

函数 add 执行完毕后,其值会直接替换调用位置。这意味着你可以将返回值直接用于表达式:

int final = add(5, 10) * 2;  // 先计算 add(5,10),再乘以 2

返回指针的注意事项

C 语言允许函数返回指针,但必须确保返回的地址在调用者访问时仍然有效。以下是常见错误:

int* bad_example() {
    int local = 100;
    return &local;  // 错误!local 在函数结束后被销毁
}

正确做法是返回动态分配内存或静态变量的地址:

int* good_example() {
    static int value = 200;
    return &value;  // 正确:静态变量生命周期贯穿程序始终
}

无返回值的函数

当函数不需要返回结果时,使用 void 关键字:

void greet(char* name) {
    printf("Hello, %s!\n", name);
    // 无需 return 语句
}

参数传递:值传递与指针传递

C 语言采用值传递方式,这意味着函数接收的是实参的副本。对副本的修改不会影响原始数据。

值传递机制

#include <stdio.h>

void increment(int x) {
    x = x + 1;  // 修改的是副本
    printf("Inside increment: x = %d\n", x);
}

int main() {
    int num = 5;
    increment(num);        // 传递副本
    printf("After call: num = %d\n", num);
    return 0;
}

输出结果清晰展示了值传递的特性:

Inside increment: x = 6
After call: num = 5

函数内部的 x 被修改为 6,但 main 中的 num 仍保持原值 5。

指针传递:实现"修改原始数据"

当需要函数修改调用者的变量时,必须传递变量的地址(指针):

void increment_ptr(int* ptr) {
    *ptr = *ptr + 1;  // 通过解引用修改指针指向的值
}

int main() {
    int num = 5;
    increment_ptr(&num);  // 传递 num 的地址
    printf("After call: num = %d\n", num);
    return 0;
}

这次输出变为:

After call: num = 6

指针传递的本质是传递地址值。函数获得地址后,可以通过解引用运算符 * 访问和修改原始数据。

指针传递的典型应用场景

1. 交换两个变量的值

void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 调用方式
swap(&x, &y);

2. 在函数内部修改结构体

typedef struct {
    int x;
    int y;
} Point;

void move(Point* p, int dx, int dy) {
    p->x += dx;
    p->y += dy;
}

数组参数传递

C 语言中,数组作为参数传递时会自动退化为指向其首元素的指针。

一维数组参数

void print_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
}

以下三种写法完全等价,编译器都会将 arr[] 视为 int*

void print_array(int arr[], int size);    // 惯用写法
void print_array(int arr[10], int size);  // 尺寸仅作提示
void print_array(int* arr, int size);     // 最底层的写法

重要特性:数组参数是指针类型的副本,函数内部可以修改原始数组的元素。

void double_array(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // 直接修改原始数组
    }
}

int main() {
    int nums[] = {1, 2, 3};
    double_array(nums, 3);
    // nums 变为 {2, 4, 6}
    return 0;
}

多维数组参数传递

传递二维数组时,除了第一维以外的维度大小必须明确:

void print_matrix(int matrix[][3], int rows) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < 3; j++) {
            printf("%d ", matrix[i][j]);
        }
        printf("\n");
    }
}

如果需要传递任意维度,使用指针运算:

void process_2d(int* data, int rows, int cols) {
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            int value = *(data + i * cols + j);
            // 处理元素
        }
    }
}

实际编程模式

错误码返回模式

C 语言没有异常机制,通常通过返回值表示操作是否成功:

#define SUCCESS 0
#define ERROR_NULL_PTR -1
#define ERROR_INVALID_ARG -2

int process_data(Data* data) {
    if (data == NULL) {
        return ERROR_NULL_PTR;
    }
    if (data->size <= 0) {
        return ERROR_INVALID_ARG;
    }
    // 执行处理逻辑
    return SUCCESS;
}

// 调用示例
int result = process_data(&my_data);
if (result != SUCCESS) {
    // 处理错误
}

函数指针传递

函数可以作为参数传递给其他函数,这在回调和算法抽象中非常有用:

int apply(int x, int (*func)(int)) {
    return func(x);
}

int square(int x) {
    return x * x;
}

int triple(int x) {
    return x * 3;
}

int main() {
    printf("Square(5) = %d\n", apply(5, square));
    printf("Triple(5) = %d\n", apply(5, triple));
    return 0;
}

可变参数函数

标准库中的 printfscanf 使用可变参数机制。C 语言提供 <stdarg.h> 头文件来实现这一功能:

#include <stdarg.h>
#include <stdio.h>

int sum(int count, ...) {
    va_list args;
    va_start(args, count);

    int total = 0;
    for (int i = 0; i < count; i++) {
        total += va_arg(args, int);
    }

    va_end(args);
    return total;
}

// 使用示例
int result = sum(4, 1, 2, 3, 4);  // 返回 10

参数传递方式对比

传递方式 函数内部获取 修改影响 适用场景
值传递 副本 只影响副本 只读操作、小数据
指针传递 地址 影响原始数据 需要修改数据、大数据
const 指针 地址 只读不修改 避免复制开销,同时保护数据

const 指针的最佳实践

// 明确表示不会修改指向的数据
void print_student(const Student* s) {
    printf("Name: %s\n", s->name);
    // s->name = "New Name";  // 编译错误,确保安全
}

使用 const 指针作为参数具有文档作用:阅读函数签名即可知道该函数不会修改传入的数据。


内存管理要点

当函数需要分配内存并返回给调用者时,调用者负责释放:

char* create_string(const char* prefix) {
    char* result = malloc(strlen(prefix) + 6);  // 分配内存
    if (result == NULL) {
        return NULL;
    }
    strcpy(result, prefix);
    strcat(result, "_new");
    return result;  // 返回分配的内存
}

// 调用者负责释放
char* str = create_string("test");
if (str) {
    printf("%s\n", str);
    free(str);  // 防止内存泄漏
}

实践建议

1. 优先传递 const 指针
不需要修改的数据应使用 const 指针传递,既避免复制开销,又保护数据不被意外修改。

2. 明确函数的前置条件和后置条件
通过注释或文档说明参数的有效范围和返回值的含义。

3. 检查指针参数是否为 NULL
在函数入口处验证指针参数,避免解引用空指针导致的崩溃。

4. 保持函数参数简洁
函数的参数数量不宜过多。如果参数超过 4 个,考虑是否需要封装为结构体。

评论 (0)

暂无评论,快来抢沙发吧!

扫一扫,手机查看

扫描上方二维码,在手机上查看本文