# 10 多级指针与void指针 ## 10.1 完整概念讲解 ### 二级指针 int**pp 二级指针是指向指针的指针,用于存储指针变量的地址。 ```c 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 ``` ### 指针做函数参数修改外部指针 当需要在函数内部修改外部指针变量本身时,必须传递指针的地址(二级指针)。 ```c 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. 回调函数的上下文参数 ```c void *generic_func(void *data) { int *int_ptr = (int*)data; return int_ptr; } ``` ### void*不能直接算术运算 C标准规定不能对void*进行算术运算,因为void没有大小信息。 ```c void *vp = malloc(100); // vp++; // 错误:不能对void*算术运算 // 必须先转换为具体类型 char *cp = (char*)vp; cp++; // 正确:移动1字节 ``` ### const与多级指针组合 const与指针的组合有多种方式,需要注意const的位置。 ```c const int *p; // 指向const int的指针(不能通过p修改值) int *const p; // const指针(不能修改p本身) const int *const p; // 既不能修改值也不能修改指针 ``` ## 10.2 核心API/语法 ### 多级指针声明 ```c int a = 10; int *p = &a; // 一级指针 int **pp = &p; // 二级指针 int ***ppp = &pp; // 三级指针(很少用) ``` ### 二级指针使用 ```c int a = 10; int *p = &a; int **pp = &p; printf("**pp = %d\n", **pp); // 10 *pp = malloc(sizeof(int)); // 修改p指向新内存 **pp = 20; // 通过二级指针修改值 ``` ### void*使用 ```c 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指针 ```c 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:二级指针基础 ```c #include 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:二级指针做函数参数 ```c #include #include // 错误:无法修改外部指针 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*万能指针 ```c #include #include // 通用交换函数 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与多级指针 ```c #include 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:实际应用 - 链表节点管理 ```c #include #include 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 完整示例程序 ```c #include #include #include // 通用排序函数 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`