一、void * 万能空类型指针
1. 核心概念
普通指针(int*/char*/float*)都绑定了固定数据类型,编译器会根据类型确定指针±1时偏移的字节数;而void指针是无类型万能指针,不绑定任何数据类型,仅单纯存储内存地址,不自带解析内存长度的规则。
void *p; // 合法定义,可存放任意类型变量的地址
int a = 10;
char ch = 'A';
double d = 3.14;
p = &a; // int* 自动隐式转为 void*
p = &ch; // char* 自动隐式转为 void*
p = &d; // double* 自动隐式转为 void*
2. void指针的限制与强制转换规则
void指针不能直接解引用(*p)、不能直接做自增自减运算,编译器无法知道要读取几个字节的数据,必须强制转换成对应类型指针后才能操作内存:
void *addr = (void *)0x12345678;
// 将0x12345678地址强制转为int*,修改4字节内存值
*((int *)addr) = 0x11223344;
转换逻辑:
3. void指针典型应用场景
二、二级指针(**):指针的指针
1. 基础原理
普通指针int *p存放普通变量地址;二级指针int **q存放一级指针变量的地址,相当于指针变量的容器。
内存层级关系:普通变量a ←一级指针p=&a ←二级指针q=&p
int a = 100;
int *p = &a; // p存a的地址
int **q = &p; // q存一级指针p的地址
// 取值分层解析
printf("a的值:%d\\n", a);
printf("一级指针p指向的值:%d\\n", *p);
printf("二级指针q解一层:p的地址 %p\\n", *q);
printf("二级指针q解两层:a的值 %d\\n", **q);
2. 二级指针核心作用
三、指针数组 vs 数组指针(极易混淆核心考点)
3.1 指针数组:数组,元素全是指针
语法:数据类型 *数组名[长度],[]优先级高于*,先结合数组,再修饰指针。
int *a[5];
含义:定义数组a,共5个元素,每个元素类型是int *(int型一级指针);64位系统下每个指针占8字节,整个数组总大小5*8=40字节。
典型使用场景:存储字符串数组
C语言批量存储多段字符串优先使用字符指针数组,比二维字符数组更省内存:
// 每个字符串单独存常量区,数组仅存字符串首地址
char *month[] = {"Jan","Feb","Mar","Apr","May"};
printf("%s", month[0]); // 打印Jan
3.2 数组指针:指针,专门指向一整个数组
语法:数据类型 (*指针名)[长度],括号强制*先结合变量,本质是指针,指向固定列长的完整数组。
int arr[2][3] = {1,2,3,4,5,6};
int (*p)[3]; // 数组指针,指向“包含3个int元素的一维数组”
p = arr; // 二维数组名arr本身就是数组指针
特性:
3.3 快速区分口诀
3.4 二维数组与数组指针的映射关系
int a[2][3] = {{1,2,3},{4,5,6}};
int (*q)[3] = a;
// 多种等价访问元素方式
a[0][1];
*(a[0]+1);
*(*(a+0)+1);
q[0][1];
*(*(q+0)+1);
层级拆解:
- a / q:指向整行的数组指针;
- a[m] / *(a+m):等价行首一维指针,指向当前行第一个元素;
- a[m][n]:先取第m行首地址,偏移n个int字节,读取对应数值。
四、const修饰指针的4种组合(只读权限细分)
const代表只读,修饰位置不同,限制对象完全不同,分四类场景:
1. const int *p / int const *p:指向内容只读,指针可改
const修饰*p,不能通过指针修改内存值,但指针p可以更换指向:
int x=1,y=2;
const int *p = &x;
// *p = 10; 编译报错,禁止修改指向内容
p = &y; // 合法,指针指向可以切换
2. int *const p:指针本身只读,内容可改
const修饰指针变量p,定义时必须初始化,指针指向永久固定,但能修改内存数据:
int x=1;
int *const p = &x;
*p = 100; // 合法,修改内容
// p = &y; 编译报错,不能修改指针指向
3. const int *const p / int const *const p:双向只读
指针不可更换指向,同时不能修改指向内存的值,完全只读。
五、指针函数 vs 函数指针(概念完全割裂)
5.1 指针函数:是函数,返回值为指针
语法:数据类型 *函数名(形参列表),核心是函数,执行结束返回一个内存地址(指针)。
// 返回char*字符串地址
char *get_msg(void);
⚠️ 致命禁忌:绝对不能返回局部变量的地址,局部变量存储在栈区,函数执行结束栈内存自动回收,返回野指针导致程序崩溃;仅能返回全局变量、静态局部变量、堆内存地址。
5.2 函数指针:是指针,指向一段函数代码
语法:返回值类型 (*指针名)(形参类型),存储函数在文本区的代码首地址,可间接调用函数、用于回调函数。
int add(int a,int b){return a+b;}
int (*func_p)(int,int) = add;
// 两种等价调用方式
int res = func_p(1,2);
int res = (*func_p)(1,2);
六、补充:野指针与空指针(指针安全基础)
宏定义#define NULL ((void *)0),指向地址0,该内存为操作系统保护只读区域,禁止解引用*NULL,否则程序段错误;用于指针初始化,标记指针无有效指向。
int *p = NULL; // 规范初始化,避免野指针
两类来源:① 未初始化的局部指针;② 指向已释放/回收的内存。野指针指向随机非法地址,解引用会随机崩溃,开发规范:所有指针定义时初始化为NULL。



