指针与基本数据类型: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 搞混,结果赋值赋到了地址本身,程序直接崩了。

核心理解:int 指针做加减运算时,步长是 sizeof(int) 字节。比如 p+1,地址会往后跳 4 个字节(32 位系统)。

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 指针逐字节地拆包、组包——特别顺手。

小技巧:用 char 指针遍历字符串时,记得判断结束符 '\0'。我曾经忘了这个,结果读到了内存里的垃圾数据。

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 指针“快速转换”数据类型,结果算出来的值完全不对——嗯,这其实是一种未定义行为。

避坑指南:我曾经在 DSP 上做浮点运算优化时,试图用 int 指针去读 float 的二进制位。结果因为字节序问题,高低字节搞反了,调试了一整天。后来老老实实用 memcpy 或者联合体。

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 指针最常见的用途是写通用函数。比如标准库里的 memcpymemset,参数都是 void 指针。我在写嵌入式驱动时,也经常用 void 指针来设计“通用寄存器读写接口”——不管寄存器是 8 位、16 位还是 32 位,传个地址进去就行。

关键规则:void 指针必须先转换成具体类型的指针,才能解引用或做算术运算。而且 void 指针不能直接做加减——有些编译器允许,但这是扩展行为,不是标准 C。

5. 四种指针的对比

指针类型 指向的数据类型 步长(32位系统) 典型用途
int * int 4 字节 整数数组、状态寄存器
char * char 1 字节 字符串、字节流、协议解析
float * float 4 字节 浮点数组、传感器数据
void * 任意类型 不可直接运算 通用接口、内存操作

这张表我建议你记在心里。尤其是步长——指针加减运算时,步长决定了你能跳到哪个位置。搞错了,就是越界访问。

6. 知识体系图

下面这张图展示了四种指针的核心关系,以及它们各自的特点。我习惯用这种图来帮自己理清思路。

指针与基本数据类型 int * 步长 4 字节 char * 步长 1 字节 float * 步长 4 字节 void * 万能指针 特性 • 指向整型变量 • 常用于数组遍历 • 注意字节序问题 特性 • 指向字符/字节 • 字符串操作利器 • 注意 '\0' 结束符 特性 • 指向浮点变量 • 内存布局特殊 • 避免类型混用 特性 • 无类型指针 • 需强制转换后使用 • 通用接口设计 核心要点 指针类型决定步长 → 步长决定地址偏移 → 偏移错误导致越界

7. 避坑总结

  • 类型不匹配:不要用 int 指针去读 float 变量,反之亦然。虽然能编译过,但结果完全不可控。
  • void 指针乱运算:void 指针不能直接做加减。我见过有人写 vp++,结果不同编译器行为不一样——有的报错,有的当 char 指针处理。
  • char 指针越界:用 char 指针遍历字符串时,一定要确保有 '\0' 结尾。否则 strlen 会一直读到崩溃。
  • float 指针的陷阱:float 的二进制表示不是直观的数值。你打印 *(int*)&f 得到的整数,和 f 的值没有任何简单关系。
我的习惯:在嵌入式项目中,我一般用 char 指针做底层字节操作,用 void 指针设计通用接口,用 int 指针处理寄存器地址。float 指针用得最少——因为浮点运算在 MCU 上比较慢,能用定点数就用定点数。

好了,这四种指针的基本用法和注意事项就这些。你想想看,其实核心就两点:步长决定偏移,类型决定解释方式。搞懂了这两条,指针就没什么神秘的了。

公众号:蓝海资料掘金营,微信 deep3321