一、void指针1. 概念void *是无类型指针泛型指针可以存放任意类型指针的值。cvoid *p; // 无类型指针 char *cp; int *ip; double *dp; p cp; // ✅ 任意指针都能赋给void * p ip; p dp;2. 使用场景1存放任意内存地址cvoid *p (void *)0x12345678; // 存放一个绝对地址2作为函数参数传递任意类型指针cvoid swap(void *a, void *b, int size) { // 按字节交换内存块 }void *类型的参数可以接收任意类型的指针配合size参数可以实现通用交换函数。void指针不能直接解引用必须先转换为具体类型cint num 10; void *p num; printf(%d\n, *p); // ❌ void *不能解引用 printf(%d\n, *(int *)p); // ✅ 先转为int *再解引用二、二级指针1. 概念二级指针是指向指针的指针存的是指针变量的地址。cint a 100; int *p a; // p指向a int **q p; // q指向p逐级解引用cprintf(%d\n, **q); // 输出100理解过程q存的是p的地址*q取到p的内容即a的地址**q取到a的内容即1002. 二级指针的用途1修改指针变量的指向函数内想修改函数外指针变量的值需要传指针变量的地址即二级指针。cvoid allocate(int **pp, int size) { *pp (int *)malloc(size * sizeof(int)); } int main(void) { int *p NULL; allocate(p, 10); // 传入p的地址 // p现在指向动态分配的空间 return 0; }2 指针数组传参cvoid print_strings(char **str, int len) { for (int i 0; i len; i) { printf(%s\n, str[i]); } } int main(void) { char *str[3] {hello, world, C}; print_strings(str, 3); // 指针数组名→char** return 0; }三、指针数组和数组指针1. 指针数组本质是数组数组的每个元素都是指针。cint *a[5]; // 有5个元素每个都是 int* 类型占40字节64位系统使用场景操作字符串数组比二维字符数组更高效。cchar *str[3] {hello, world, C}; printf(%s\n, str[0]); // 输出hello指针数组只存地址字符串在常量区二维字符数组存完整副本字符串在数组内。指针数组更省空间但指向的内容通常是只读的。2. 数组指针本质是指针指向数组的指针。cint (*p)[5]; // p指向一个有5个int元素的数组p占8字节与一维数组的关系cint a[5] {1, 2, 3, 4, 5}; int (*p)[5] a; // 指向整个数组之前学过一维数组名a通常理解为int *指向首元素但有两种情况例外sizeof(a)计算整个数组大小a取整个数组的地址类型为int (*)[5]cint a[5]; printf(%p\n, a); // 指向第一个元素 printf(%p\n, a); // 指向整个数组值相同但类型不同数组指针取*值不变类型降级cint (*p)[5] a; printf(%p\n, *p); // 与a的值相同类型变为 int *3. 指针数组 vs 数组指针指针数组数组指针写法int *a[5]int (*p)[5]本质数组指针运算符优先级[]优先括号优先大小5×840字节8字节四、二维数组与指针的关系1. 二维数组的地址结构cint a[2][3] {{1,2,3}, {4,5,6}};表达式含义类型a指向第一行的数组指针int (*)[3]a[0]第一行第一个元素的指针int *a[1]第二行第一个元素的指针int *a[0][0]第一个元素的指针int *二维数组的数组名a和一维数组的数组名a含义不同一维数组名指向首元素二维数组名指向第一行整个行数组。2. 访问第m行第n列元素的等价写法ca[m][n] // 最直观 *(a[m] n) // 先取第m行指针再偏移 *(*(a m) n) // 先偏移到第m行再解引用取行指针再偏移 *(*(a m) n) // 通用公式如果已知首元素指针p a[0][0]总列数为Nc*(p m * N n) // 按一维方式偏移如果已知数组指针q ac*(*(q m) n) // 与上面第3种等价 *(q[m] n) // q[m]等价于*(qm)3. 二维数组传参二维数组传参需要传递数组指针和行数。cint fun(int (*parray)[3], int row); // 方式1数组指针 int fun(int parray[][3], int row); // 方式2二维数组形式等价cvoid print_array(int (*arr)[3], int row) { for (int i 0; i row; i) { for (int j 0; j 3; j) { printf(%d , arr[i][j]); } printf(\n); } } int main(void) { int a[2][3] {{1,2,3}, {4,5,6}}; print_array(a, 2); // 传数组名和行数 return 0; }二维数组传参时列数不能省略但行数可以省略。五、今日总结今天的核心要点如下void指针无类型指针可以存放任意类型指针不能直接解引用必须强转类型常用于通用函数设计如qsort、memcpy二级指针指向指针的指针存的是指针变量的地址用途修改函数外指针变量的指向、指针数组传参解引用**q逐级取值指针数组 vs 数组指针int *a[5]指针数组数组存指针int (*p)[5]数组指针指针指向数组区分关键看运算符优先级和括号二维数组与指针a是int (*)[N]类型指向行的数组指针a[i]是int *类型指向第i行首元素访问元素a[m][n]、*(a[m]n)、*(*(am)n)传参列数不能省略行数可省今日感悟指针是c语言学习的一大难点。void *是实现泛型编程的基础二级指针若没有理解画图后思路就清晰了指针数组和数组指针的区分关键在于运算符优先级二维数组和指针的关系虽然复杂但掌握a、a[i]、a三者的类型区别就能应对大多数场景。