9. 指针与函数.md 11 KB

9 指针与函数

9.1 完整概念讲解

指针做函数参数(传址调用)

函数参数传递有两种方式:

  1. 传值调用:传递变量的副本,函数内修改不影响原变量
  2. 传址调用:传递变量的地址,函数内通过指针可以修改原变量

    void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    }
    
    int main() {
    int x = 10, y = 20;
    swap(&x, &y);  // 传址调用
    // x=20, y=10
    }
    

函数返回指针

  • 可以返回指向静态变量、全局变量或动态分配内存的指针
  • 绝对不要返回局部变量的地址(函数结束后局部变量被销毁)

    // 错误示例
    int* wrong_func() {
    int local = 10;
    return &local;  // 危险!局部变量地址
    }
    
    // 正确示例1:返回全局变量指针
    int global_var = 100;
    int* correct_func1() {
    return &global_var;
    }
    
    // 正确示例2:返回动态分配内存
    int* correct_func2() {
    int *p = malloc(sizeof(int));
    *p = 200;
    return p;  // 调用者负责free
    }
    

函数指针定义与使用

函数指针是指向函数的指针变量,可以用于动态调用函数。

// 函数指针声明
int (*func_ptr)(int, int);

// 赋值
func_ptr = add;

// 调用
int result = func_ptr(3, 4);

函数指针数组

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }

// 函数指针数组
int (*ops[])(int, int) = {add, sub, mul};

回调模式

将函数作为参数传递给另一个函数,实现灵活的行为定制。

qsort使用

#include <stdlib.h>

int compare(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

int main() {
    int arr[] = {5, 2, 8, 1, 9};
    qsort(arr, 5, sizeof(int), compare);
}

内核file_operations操作表模式

Linux内核中广泛使用函数指针数组实现面向对象的多态。

9.2 核心API/语法

函数指针语法

// 基本语法
返回类型 (*函数指针名)(参数类型列表);

// 示例
int (*add_ptr)(int, int);
void (*print_ptr)(const char*);

回调函数模式

// 定义回调类型
typedef int (*compare_func)(const void*, const void*);

// 使用回调
void sort(int *arr, int size, compare_func cmp) {
    // 使用cmp进行比较
}

qsort函数

#include <stdlib.h>
void qsort(void *base, size_t nmemb, size_t size,
           int (*compar)(const void*, const void*));

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

示例1:传值调用 vs 传址调用

#include <stdio.h>

// 传值调用:无效
void swap_by_value(int a, int b) {
    int temp = a;
    a = b;
    b = temp;
    printf("函数内: a=%d, b=%d\n", a, b);
}

// 传址调用:有效
void swap_by_address(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    printf("函数内: *a=%d, *b=%d\n", *a, *b);
}

int main() {
    int x = 10, y = 20;

    printf("调用前: x=%d, y=%d\n", x, y);

    swap_by_value(x, y);
    printf("传值调用后: x=%d, y=%d\n", x, y);

    swap_by_address(&x, &y);
    printf("传址调用后: x=%d, y=%d\n", x, y);

    return 0;
}

编译命令:gcc -o pass_by_value_vs_address pass_by_value_vs_address.c

示例2:函数返回指针

#include <stdio.h>
#include <stdlib.h>

// 危险:返回局部变量地址
int* dangerous_func() {
    int local_var = 100;
    return &local_var;  // 未定义行为
}

// 正确:返回全局变量指针
int global_var = 200;
int* get_global_ptr() {
    return &global_var;
}

// 正确:返回静态变量指针
int* get_static_ptr() {
    static int static_var = 300;
    return &static_var;
}

// 正确:返回动态分配内存
int* get_dynamic_mem(int value) {
    int *p = (int*)malloc(sizeof(int));
    if (p != NULL) {
        *p = value;
    }
    return p;
}

int main() {
    // 错误用法
    // int *p1 = dangerous_func();
    // printf("危险函数: %d\n", *p1);  // 未定义行为

    // 正确用法
    int *p2 = get_global_ptr();
    printf("全局变量: %d\n", *p2);

    int *p3 = get_static_ptr();
    printf("静态变量: %d\n", *p3);

    int *p4 = get_dynamic_mem(400);
    if (p4 != NULL) {
        printf("动态内存: %d\n", *p4);
        free(p4);
    }

    return 0;
}

编译命令:gcc -o return_pointer return_pointer.c

示例3:函数指针基础

#include <stdio.h>

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div_int(int a, int b) { return b != 0 ? a / b : 0; }

int main() {
    // 函数指针声明与赋值
    int (*operation)(int, int);

    operation = add;
    printf("3 + 4 = %d\n", operation(3, 4));

    operation = sub;
    printf("3 - 4 = %d\n", operation(3, 4));

    operation = mul;
    printf("3 * 4 = %d\n", operation(3, 4));

    operation = div_int;
    printf("3 / 4 = %d\n", operation(3, 4));

    // 函数指针数组
    int (*ops[])(int, int) = {add, sub, mul, div_int};
    char *op_names[] = {"+", "-", "*", "/"};

    for (int i = 0; i < 4; i++) {
        printf("10 %s 5 = %d\n", op_names[i], ops[i](10, 5));
    }

    return 0;
}

编译命令:gcc -o function_pointer function_pointer.c

示例4:回调模式与qsort

#include <stdio.h>
#include <stdlib.h>

// 比较函数:升序
int compare_asc(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

// 比较函数:降序
int compare_desc(const void *a, const void *b) {
    return (*(int*)b - *(int*)a);
}

// 打印数组
void print_array(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 通用排序函数
typedef int (*compare_func)(const void*, const void*);

void sort_array(int *arr, int size, compare_func cmp) {
    qsort(arr, size, sizeof(int), cmp);
}

int main() {
    int arr1[] = {5, 2, 8, 1, 9, 3};
    int arr2[] = {5, 2, 8, 1, 9, 3};
    int size = sizeof(arr1) / sizeof(arr1[0]);

    printf("原始数组: ");
    print_array(arr1, size);

    sort_array(arr1, size, compare_asc);
    printf("升序排序: ");
    print_array(arr1, size);

    sort_array(arr2, size, compare_desc);
    printf("降序排序: ");
    print_array(arr2, size);

    return 0;
}

编译命令:gcc -o callback_qsort callback_qsort.c

示例5:内核file_operations模式

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 模拟内核file_operations结构
struct file_operations {
    int (*open)(const char *path);
    int (*read)(int fd, void *buf, int count);
    int (*write)(int fd, const void *buf, int count);
    int (*close)(int fd);
};

// 模拟设备实现
static int my_open(const char *path) {
    printf("打开设备: %s\n", path);
    return 0;
}

static int my_read(int fd, void *buf, int count) {
    printf("从设备读取 %d 字节\n", count);
    strcpy((char*)buf, "设备数据");
    return strlen("设备数据");
}

static int my_write(int fd, const void *buf, int count) {
    printf("向设备写入: %s (%d字节)\n", (const char*)buf, count);
    return count;
}

static int my_close(int fd) {
    printf("关闭设备\n");
    return 0;
}

// 操作表
struct file_operations my_fops = {
    .open = my_open,
    .read = my_read,
    .write = my_write,
    .close = my_close
};

// 使用操作表的通用函数
int device_operation(struct file_operations *fops, const char *path) {
    int fd = fops->open(path);

    char buf[100];
    fops->read(fd, buf, sizeof(buf));
    printf("读取到: %s\n", buf);

    fops->write(fd, "Hello Device", 12);
    fops->close(fd);

    return 0;
}

int main() {
    device_operation(&my_fops, "/dev/my_device");
    return 0;
}

编译命令:gcc -o kernel_pattern kernel_pattern.c

9.4 注意事项与易错点

返回局部变量地址

  • 函数结束后,局部变量的内存被释放
  • 返回的指针成为悬空指针,解引用是未定义行为
  • 解决方案:使用动态内存、静态变量或全局变量

函数指针类型不匹配

  • 函数指针的返回类型和参数类型必须与目标函数匹配
  • 类型不匹配可能导致未定义行为

回调函数中的this指针

  • C语言没有this指针,通常通过void*参数传递上下文
  • 注意内存管理和类型转换

函数指针数组越界

  • 与普通数组一样,函数指针数组也需要边界检查
  • 越界访问可能导致程序崩溃

内存泄漏

  • 动态分配的内存必须由调用者负责释放
  • 函数返回指针时,文档应明确内存所有权

递归函数的栈溢出

  • 递归层次过深可能导致栈溢出
  • 考虑使用迭代或尾递归优化

9.5 面试要点(3-5个Q&A)

Q1: 传值调用和传址调用有什么区别? A: 传值调用传递变量的副本,函数内修改不影响原变量;传址调用传递变量的地址,函数内通过指针可以修改原变量。传址调用效率更高(避免大结构体复制),且可以修改实参。

Q2: 函数返回指针时需要注意什么? A: 绝对不要返回局部变量的地址(函数结束后内存被释放)。应该返回全局变量、静态变量的指针,或动态分配的内存(调用者负责free)。

Q3: 什么是函数指针?有什么用途? A: 函数指针是指向函数的指针变量,可以用于动态调用函数。用途包括:回调模式、函数指针数组实现分发表、qsort等通用算法、模拟面向对象的多态。

Q4: 如何实现类似qsort的回调机制? A: 定义函数指针类型,将比较函数作为参数传递。例如:typedef int (*compare_func)(const void*, const void*),然后将具体比较函数传入排序函数。

Q5: 内核中的file_operations模式是什么? A: 这是Linux内核中广泛使用的面向对象模式。通过结构体中的函数指针实现多态,不同设备驱动实现相同接口的函数。应用程序通过统一的系统调用接口操作不同设备。

9.6 完整示例程序

#include <stdio.h>
#include <stdlib.h>
#include <string.h}

// 交换函数
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

// 比较函数
int compare(const void *a, const void *b) {
    return (*(int*)a - *(int*)b);
}

// 回调函数类型
typedef void (*callback_func)(int);

// 使用回调的函数
void process_array(int *arr, int size, callback_func cb) {
    for (int i = 0; i < size; i++) {
        cb(arr[i]);
    }
}

// 回调实现:打印
void print_element(int x) {
    printf("%d ", x);
}

// 回调实现:加倍
void double_element(int *x) {
    *x *= 2;
}

int main() {
    // 传址调用
    int a = 10, b = 20;
    printf("交换前: a=%d, b=%d\n", a, b);
    swap(&a, &b);
    printf("交换后: a=%d, b=%d\n", a, b);

    // qsort使用
    int arr[] = {5, 2, 8, 1, 9};
    int size = 5;
    qsort(arr, size, sizeof(int), compare);
    printf("排序后: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // 回调模式
    process_array(arr, size, print_element);
    printf("\n");

    return 0;
}

编译命令:gcc -o function_demo function_demo.c

注意:代码示例中有一个语法错误(#include <string.h} 应该是 #include <string.h>),实际编译时需要修正。