指针与基本数据类型:int指针、char指针、float指针、void指针
指针这东西,说白了就是地址的搬运工。但不同的数据类型,搬运的“货物”大小不一样。我刚开始学的时候,总觉得指针就是指针,管它什么类型呢——结果踩了不少坑。今天咱们就把 int 指针、char 指针、float 指针和 void 指针挨个捋一遍。
1. int 指针:最基础的指针类型
int 指针,指向的是一个整型变量的地址。它的大小在 32 位系统上是 4 字节,64 位系统上是 8 字节——嗯,这是指针本身的大小,不是它指向的数据大小。
int a = 42;
int *p = &a;
printf("a 的值:%d\n", a); // 42
printf("p 指向的值:%d\n", *p); // 42
printf("p 的地址:%p\n", p); // 比如 0x7ffd1234
这里有个关键点:p 存的是地址,*p 才是那个地址里存的值。我在项目中遇到过有人把 p 和 *p 搞混,结果赋值赋到了地址本身,程序直接崩了。
2. char 指针:字节级别的操作
char 指针指向一个字符变量。它最常用的场景是处理字符串。你想想看,字符串本质上就是字符数组,而数组名就是首地址——所以 char 指针天然适合操作字符串。
char str[] = "Hello";
char *p = str;
printf("%c\n", *p); // H
printf("%c\n", *(p+1)); // e
printf("%s\n", p); // Hello
char 指针的步长是 1 字节。这意味着 p+1 只往后挪一个字节。我在做嵌入式串口协议解析时,经常用 char 指针逐字节地拆包、组包——特别顺手。
3. float 指针:浮点数的地址操作
float 指针指向一个 float 变量。它的步长是 sizeof(float),通常是 4 字节。但要注意,float 的内存布局和 int 完全不同——int 是整数补码,float 是指数+尾数。
float f = 3.14f;
float *p = &f;
printf("%f\n", *p); // 3.140000
printf("%p\n", p); // 地址
printf("%f\n", *(p+1)); // 危险!可能读到垃圾值
这里我要特别提醒:不要用 float 指针去访问 int 变量的内存。虽然它们大小都是 4 字节,但解释方式完全不同。我见过有人想用 float 指针“快速转换”数据类型,结果算出来的值完全不对——嗯,这其实是一种未定义行为。
4. void 指针:万能指针
void 指针,也叫无类型指针。它可以指向任何类型的数据,但不能直接解引用。为什么?因为编译器不知道它指向的数据有多大。
int a = 10;
float b = 2.5f;
char c = 'X';
void *vp;
vp = &a; // 合法
vp = &b; // 合法
vp = &c; // 合法
// printf("%d\n", *vp); // 错误!不能直接解引用
printf("%d\n", *(int*)vp); // 正确:先转成 int 指针
void 指针最常见的用途是写通用函数。比如标准库里的 memcpy、memset,参数都是 void 指针。我在写嵌入式驱动时,也经常用 void 指针来设计“通用寄存器读写接口”——不管寄存器是 8 位、16 位还是 32 位,传个地址进去就行。
5. 四种指针的对比
| 指针类型 | 指向的数据类型 | 步长(32位系统) | 典型用途 |
|---|---|---|---|
| int * | int | 4 字节 | 整数数组、状态寄存器 |
| char * | char | 1 字节 | 字符串、字节流、协议解析 |
| float * | float | 4 字节 | 浮点数组、传感器数据 |
| void * | 任意类型 | 不可直接运算 | 通用接口、内存操作 |
这张表我建议你记在心里。尤其是步长——指针加减运算时,步长决定了你能跳到哪个位置。搞错了,就是越界访问。
6. 知识体系图
下面这张图展示了四种指针的核心关系,以及它们各自的特点。我习惯用这种图来帮自己理清思路。
7. 避坑总结
- 类型不匹配:不要用 int 指针去读 float 变量,反之亦然。虽然能编译过,但结果完全不可控。
- void 指针乱运算:void 指针不能直接做加减。我见过有人写
vp++,结果不同编译器行为不一样——有的报错,有的当 char 指针处理。 - char 指针越界:用 char 指针遍历字符串时,一定要确保有 '\0' 结尾。否则
strlen会一直读到崩溃。 - float 指针的陷阱:float 的二进制表示不是直观的数值。你打印
*(int*)&f得到的整数,和 f 的值没有任何简单关系。
好了,这四种指针的基本用法和注意事项就这些。你想想看,其实核心就两点:步长决定偏移,类型决定解释方式。搞懂了这两条,指针就没什么神秘的了。