二级指针是指向指针的指针,用于存储指针变量的地址。
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 *generic_func(void *data) {
int *int_ptr = (int*)data;
return int_ptr;
}
C标准规定不能对void*进行算术运算,因为void没有大小信息。
void *vp = malloc(100);
// vp++; // 错误:不能对void*算术运算
// 必须先转换为具体类型
char *cp = (char*)vp;
cp++; // 正确:移动1字节
const与指针的组合有多种方式,需要注意const的位置。
const int *p; // 指向const int的指针(不能通过p修改值)
int *const p; // const指针(不能修改p本身)
const int *const p; // 既不能修改值也不能修改指针
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 *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);
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; // 既不能修改值也不能修改指针
#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
#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
#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
#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
#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
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) 三级指针:很少使用,通常出现在复杂数据结构中。应尽量减少指针嵌套层级,提高代码可读性。
#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