10. 多级指针与void指针.md 13 KB

10 多级指针与void指针

10.1 完整概念讲解

二级指针 int**pp

二级指针是指向指针的指针,用于存储指针变量的地址。

int a = 10;
int *p = &a;    // 一级指针,指向int
int **pp = &p;  // 二级指针,指向int*

内存布局:

pp -> p -> a
&pp = 0x1000, pp = 0x2000 (p的地址)
&p = 0x2000, p = 0x3000 (a的地址)
&a = 0x3000, a = 10

指针做函数参数修改外部指针

当需要在函数内部修改外部指针变量本身时,必须传递指针的地址(二级指针)。

void func(int **pp) {
    *pp = malloc(sizeof(int));  // 修改外部指针
}

int main() {
    int *p = NULL;
    func(&p);  // 传递指针的地址
    *p = 100;
    free(p);
}

void*万能指针

void* 是通用指针类型,可以指向任意类型的数据。

应用场景:

  1. 内核链表(Linux内核的list_head)
  2. 通用容器(如qsort的base参数)
  3. 回调函数的上下文参数

    void *generic_func(void *data) {
    int *int_ptr = (int*)data;
    return int_ptr;
    }
    

void*不能直接算术运算

C标准规定不能对void*进行算术运算,因为void没有大小信息。

void *vp = malloc(100);
// vp++;  // 错误:不能对void*算术运算
// 必须先转换为具体类型
char *cp = (char*)vp;
cp++;  // 正确:移动1字节

const与多级指针组合

const与指针的组合有多种方式,需要注意const的位置。

const int *p;        // 指向const int的指针(不能通过p修改值)
int *const p;        // const指针(不能修改p本身)
const int *const p;  // 既不能修改值也不能修改指针

10.2 核心API/语法

多级指针声明

int a = 10;
int *p = &a;      // 一级指针
int **pp = &p;    // 二级指针
int ***ppp = &pp; // 三级指针(很少用)

二级指针使用

int a = 10;
int *p = &a;
int **pp = &p;

printf("**pp = %d\n", **pp);  // 10
*pp = malloc(sizeof(int));    // 修改p指向新内存
**pp = 20;                    // 通过二级指针修改值

void*使用

void *vp;
int a = 10;
vp = &a;                     // void*指向int
printf("%d\n", *(int*)vp);   // 转换后解引用

double b = 3.14;
vp = &b;                     // void*指向double
printf("%f\n", *(double*)vp);

const指针

int a = 10, b = 20;

const int *p1 = &a;    // 指向const int
// *p1 = 20;           // 错误:不能通过p1修改值
p1 = &b;               // 正确:可以修改指针本身

int *const p2 = &a;    // const指针
*p2 = 20;              // 正确:可以通过p2修改值
// p2 = &b;            // 错误:不能修改指针本身

const int *const p3 = &a;  // 既不能修改值也不能修改指针

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

示例1:二级指针基础

#include <stdio.h>

int main() {
    int a = 10;
    int *p = &a;
    int **pp = &p;

    printf("a = %d\n", a);
    printf("*p = %d\n", *p);
    printf("**pp = %d\n", **pp);

    printf("\n地址信息:\n");
    printf("&a = %p\n", (void*)&a);
    printf("p = %p\n", (void*)p);
    printf("&p = %p\n", (void*)&p);
    printf("pp = %p\n", (void*)pp);
    printf("&pp = %p\n", (void*)&pp);

    // 通过二级指针修改值
    **pp = 20;
    printf("\n修改后 a = %d\n", a);

    // 通过二级指针修改一级指针
    int b = 30;
    *pp = &b;  // 修改p指向b
    printf("修改后 *p = %d\n", *p);

    return 0;
}

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

示例2:二级指针做函数参数

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

// 错误:无法修改外部指针
void wrong_allocate(int *p) {
    p = (int*)malloc(sizeof(int));  // 只修改了局部副本
    if (p != NULL) {
        *p = 100;
    }
    // 调用者的p仍然是NULL
}

// 正确:使用二级指针
void correct_allocate(int **pp) {
    *pp = (int*)malloc(sizeof(int));  // 修改了外部指针
    if (*pp != NULL) {
        **pp = 200;
    }
}

// 通用初始化函数
void init_array(int **arr, int size) {
    *arr = (int*)malloc(size * sizeof(int));
    if (*arr != NULL) {
        for (int i = 0; i < size; i++) {
            (*arr)[i] = i * 10;
        }
    }
}

int main() {
    int *p = NULL;

    wrong_allocate(p);
    if (p == NULL) {
        printf("wrong_allocate失败: p仍然是NULL\n");
    }

    correct_allocate(&p);
    if (p != NULL) {
        printf("correct_allocate成功: *p = %d\n", *p);
        free(p);
    }

    int *arr = NULL;
    init_array(&arr, 5);
    if (arr != NULL) {
        printf("数组: ");
        for (int i = 0; i < 5; i++) {
            printf("%d ", arr[i]);
        }
        printf("\n");
        free(arr);
    }

    return 0;
}

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

示例3:void*万能指针

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

// 通用交换函数
void generic_swap(void *a, void *b, size_t size) {
    char *temp = (char*)malloc(size);
    if (temp == NULL) {
        return;
    }

    // 逐字节交换
    char *pa = (char*)a;
    char *pb = (char*)b;
    for (size_t i = 0; i < size; i++) {
        temp[i] = pa[i];
        pa[i] = pb[i];
        pb[i] = temp[i];
    }

    free(temp);
}

// 通用打印函数
void generic_print(void *data, char type) {
    switch (type) {
        case 'i':
            printf("int: %d\n", *(int*)data);
            break;
        case 'f':
            printf("float: %f\n", *(float*)data);
            break;
        case 'c':
            printf("char: %c\n", *(char*)data);
            break;
        default:
            printf("未知类型\n");
    }
}

// 通用容器示例
struct Container {
    void *data;
    size_t size;
    char type;
};

void container_set(struct Container *c, void *value) {
    if (c->data == NULL) {
        c->data = malloc(c->size);
    }
    if (c->data != NULL) {
        // 复制数据
        char *dst = (char*)c->data;
        char *src = (char*)value;
        for (size_t i = 0; i < c->size; i++) {
            dst[i] = src[i];
        }
    }
}

int main() {
    // void*基础
    int a = 10;
    double b = 3.14;
    char c = 'A';

    void *vp;

    vp = &a;
    printf("void* -> int: %d\n", *(int*)vp);

    vp = &b;
    printf("void* -> double: %f\n", *(double*)vp);

    vp = &c;
    printf("void* -> char: %c\n", *(char*)vp);

    // 通用交换
    int x = 100, y = 200;
    printf("交换前: x=%d, y=%d\n", x, y);
    generic_swap(&x, &y, sizeof(int));
    printf("交换后: x=%d, y=%d\n", x, y);

    // 通用容器
    struct Container int_container = {NULL, sizeof(int), 'i'};
    int value = 42;
    container_set(&int_container, &value);
    generic_print(int_container.data, int_container.type);

    free(int_container.data);

    return 0;
}

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

示例4:const与多级指针

#include <stdio.h>

int main() {
    int a = 10, b = 20;

    // const int* p:指向const int的指针
    const int *p1 = &a;
    printf("*p1 = %d\n", *p1);
    // *p1 = 20;    // 错误:不能通过p1修改值
    p1 = &b;       // 正确:可以修改指针本身
    printf("*p1 = %d (修改后)\n", *p1);

    // int* const p:const指针
    int *const p2 = &a;
    *p2 = 30;      // 正确:可以通过p2修改值
    printf("a = %d\n", a);
    // p2 = &b;    // 错误:不能修改指针本身

    // const int* const p:既不能修改值也不能修改指针
    const int *const p3 = &a;
    printf("*p3 = %d\n", *p3);
    // *p3 = 40;  // 错误:不能修改值
    // p3 = &b;   // 错误:不能修改指针

    // 多级指针与const
    int c = 50;
    const int *p4 = &c;
    int **pp = (int**)&p4;  // 需要强制转换
    **pp = 60;  // 危险!绕过了const保护
    printf("c = %d (通过const指针修改)\n", c);

    return 0;
}

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

示例5:实际应用 - 链表节点管理

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

struct Node {
    int data;
    struct Node *next;
};

// 创建节点
struct Node* create_node(int data) {
    struct Node *new_node = (struct Node*)malloc(sizeof(struct Node));
    if (new_node != NULL) {
        new_node->data = data;
        new_node->next = NULL;
    }
    return new_node;
}

// 错误:无法修改头指针
void wrong_insert(struct Node *head, int data) {
    struct Node *new_node = create_node(data);
    if (new_node != NULL) {
        new_node->next = head;
        head = new_node;  // 只修改了局部副本
    }
}

// 正确:使用二级指针
void correct_insert(struct Node **head, int data) {
    struct Node *new_node = create_node(data);
    if (new_node != NULL) {
        new_node->next = *head;
        *head = new_node;  // 修改了外部头指针
    }
}

// 打印链表
void print_list(struct Node *head) {
    struct Node *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

// 释放链表
void free_list(struct Node **head) {
    struct Node *current = *head;
    while (current != NULL) {
        struct Node *temp = current;
        current = current->next;
        free(temp);
    }
    *head = NULL;
}

int main() {
    struct Node *head = NULL;

    // 使用正确的插入函数
    correct_insert(&head, 1);
    correct_insert(&head, 2);
    correct_insert(&head, 3);

    printf("链表: ");
    print_list(head);

    // 释放链表
    free_list(&head);
    printf("释放后: ");
    print_list(head);

    return 0;
}

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

10.4 注意事项与易错点

二级指针误用

  • 不必要的二级指针会增加代码复杂度
  • 只有当需要修改指针本身时才使用二级指针

void*类型安全

  • void*失去了类型信息,必须手动转换
  • 类型转换错误可能导致未定义行为
  • 尽量减少void*的使用,优先使用具体类型

const绕过

  • 通过强制类型转换可以绕过const保护
  • 这是未定义行为,应避免
  • const应该作为设计约束,而不是强制限制

内存管理

  • void*指针需要正确计算内存大小
  • 释放void*指针前必须转换为正确类型(虽然free接受void*)

指针长度计算

  • void*不能直接进行指针算术
  • 必须先转换为具体类型才能进行加减运算

多级指针嵌套

  • 超过三级的指针很少使用,会大大降低代码可读性
  • 优先考虑重构设计,减少指针嵌套层级

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

Q1: 什么时候需要使用二级指针? A: 三种情况:1) 函数需要修改外部指针变量本身;2) 动态分配内存给指针;3) 数据结构操作(如链表插入需要修改头指针)。

Q2: void指针有哪些限制? A: 1) 不能直接解引用,必须先转换为具体类型;2) 不能进行指针算术运算;3) 不能直接使用sizeof获取大小;4) 类型转换需要程序员保证正确性。

Q3: 如何正确使用const与指针? A: 根据需求选择:const int *p(不能修改值)、int *const p(不能修改指针)、const int *const p(都不能修改)。const是设计约束,应作为接口契约使用。

*Q4: 为什么内核链表使用void*?* A: 为了实现通用容器,可以存储任意类型的数据。通过void*可以避免为每种类型实现单独的链表,减少代码重复。使用时需要通过容器元数据进行类型转换。

Q5: 多级指针有什么实际应用场景? A: 1) 二级指针:函数修改外部指针、动态内存分配、链表操作;2) 三级指针:很少使用,通常出现在复杂数据结构中。应尽量减少指针嵌套层级,提高代码可读性。

10.6 完整示例程序

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

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

void generic_sort(void *base, size_t nmemb, size_t size, compare_func cmp) {
    // 简化的冒泡排序
    char *arr = (char*)base;
    for (size_t i = 0; i < nmemb - 1; i++) {
        for (size_t j = 0; j < nmemb - i - 1; j++) {
            void *elem1 = arr + j * size;
            void *elem2 = arr + (j + 1) * size;
            if (cmp(elem1, elem2) > 0) {
                // 交换
                char *temp = (char*)malloc(size);
                memcpy(temp, elem1, size);
                memcpy(elem1, elem2, size);
                memcpy(elem2, temp, size);
                free(temp);
            }
        }
    }
}

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

int compare_float(const void *a, const void *b) {
    float fa = *(float*)a;
    float fb = *(float*)b;
    return (fa > fb) - (fa < fb);
}

int main() {
    // 二级指针演示
    int a = 10;
    int *p = &a;
    int **pp = &p;

    printf("二级指针: **pp = %d\n", **pp);

    // 通过二级指针修改值
    **pp = 20;
    printf("修改后: a = %d\n", a);

    // void*通用容器
    int int_arr[] = {5, 2, 8, 1, 9};
    int int_size = 5;

    printf("排序前: ");
    for (int i = 0; i < int_size; i++) {
        printf("%d ", int_arr[i]);
    }
    printf("\n");

    generic_sort(int_arr, int_size, sizeof(int), compare_int);

    printf("排序后: ");
    for (int i = 0; i < int_size; i++) {
        printf("%d ", int_arr[i]);
    }
    printf("\n");

    return 0;
}

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