2. 结构体成员访问:点运算符(.)、箭头运算符(->)、结构体成员的赋值与读取
结构体定义好了,数据装进去了,怎么拿出来用?这是每个嵌入式开发者天天都要面对的问题。我个人习惯把结构体成员访问比作「开抽屉」——你得知道钥匙在哪,还得知道怎么拧。
今天咱们就把这两种访问方式彻底讲透。说白了,就是点(.)和箭头(->)的区别。很多新手在这上面栽过跟头,我当年也不例外。
2.1 点运算符(.):直接访问
当你手里拿着的就是一个结构体变量本身,用点运算符。这是最直观的方式。
#include <stdio.h>
#include <string.h>
struct Sensor {
char name[20];
int id;
float value;
};
int main() {
struct Sensor s1;
// 赋值:用点运算符
strcpy(s1.name, "温度传感器");
s1.id = 101;
s1.value = 25.6;
// 读取:也是点运算符
printf("传感器名称: %s\n", s1.name);
printf("传感器ID: %d\n", s1.id);
printf("当前值: %.1f\n", s1.value);
return 0;
}
你看,s1.name、s1.id、s1.value,都是「变量名.成员名」的格式。我在项目中遇到过不少新手把点写成箭头,编译直接报错——其实记住一条就行:左边是变量,就用点。
核心要点:点运算符的左边必须是结构体变量,不能是指针。这是语法硬性规定。
2.2 箭头运算符(->):间接访问
当你手里拿的是一个指向结构体的指针,就得用箭头。为什么?因为指针存的是地址,不是数据本身。你得先解引用,再访问成员。箭头就是「解引用+成员访问」的合体写法。
#include <stdio.h>
#include <stdlib.h>
struct Sensor {
char name[20];
int id;
float value;
};
int main() {
// 动态分配一个结构体
struct Sensor *p = (struct Sensor *)malloc(sizeof(struct Sensor));
if (p == NULL) {
return -1;
}
// 赋值:用箭头运算符
strcpy(p->name, "压力传感器");
p->id = 202;
p->value = 1.05;
// 读取:也是箭头
printf("传感器名称: %s\n", p->name);
printf("传感器ID: %d\n", p->id);
printf("当前值: %.2f MPa\n", p->value);
free(p);
return 0;
}
p->name 等价于 (*p).name。箭头就是语法糖,让你少打几个字符。但别小看这几个字符——我见过有人把 (*p).name 写成 *p.name,结果编译器报错半天找不到原因。嗯,这里要注意:点运算符的优先级比解引用高,所以必须加括号。
我的习惯:只要是指针,一律用箭头。代码更清晰,也不容易出错。别混着用,保持风格统一。
2.3 两种方式的本质区别
为什么会有点和箭头的区别?你想想看,C语言的设计哲学是「信任程序员,同时给足灵活性」。结构体可以放在栈上,也可以放在堆上。访问方式自然要区分。
| 特性 | 点运算符 (.) | 箭头运算符 (->) |
|---|---|---|
| 左侧类型 | 结构体变量 | 结构体指针 |
| 本质操作 | 直接访问内存 | 解引用后访问 |
| 性能开销 | 无额外开销 | 一次间接寻址 |
| 典型场景 | 局部变量、传值 | 动态分配、传参、链表 |
| 可读性 | 直观 | 暗示指针操作 |
这张表我建议你记一下。面试的时候经常被问到:「点运算符和箭头运算符有什么区别?」标准答案就是上面这些。
2.4 结构体成员的赋值与读取
赋值和读取看起来简单,但坑不少。我分几种情况说。
2.4.1 基本类型成员
int、float、char 这些,直接赋值就行。没什么好说的。
struct Point {
int x;
int y;
};
struct Point p1;
p1.x = 10; // 赋值
p1.y = 20;
int a = p1.x; // 读取
2.4.2 数组成员
数组不能直接赋值。你得用 strcpy 或者 memcpy。这是C语言的老大难问题,很多新手在这里翻车。
struct Student {
char name[32];
int scores[5];
};
struct Student stu;
// 错误!数组不能直接赋值
// stu.name = "张三"; // 编译错误
// 正确做法
strcpy(stu.name, "张三");
// 数组元素可以逐个赋值
stu.scores[0] = 85;
stu.scores[1] = 90;
我曾经踩过的坑:用等号给字符数组成员赋值,编译器报错后我愣是看了十分钟才反应过来。记住:数组名是常量指针,不能作为左值。
2.4.3 结构体嵌套
结构体里面套结构体,访问的时候一层层点下去就行。别怕长,编译器认识。
struct Date {
int year;
int month;
int day;
};
struct Employee {
char name[32];
struct Date birth;
float salary;
};
struct Employee emp;
// 嵌套访问
strcpy(emp.name, "李四");
emp.birth.year = 1990;
emp.birth.month = 5;
emp.birth.day = 15;
emp.salary = 15000.0;
// 读取
printf("出生年份: %d\n", emp.birth.year);
如果是指针嵌套指针呢?那就箭头和点混着用。比如 p->birth.year,先箭头解引用指针,再点访问成员。这种写法在链表和树结构中非常常见。
2.5 知识体系图
下面这张图把结构体成员访问的核心逻辑串起来了。我建议你多看几遍,理解透。
2.6 常见错误与避坑指南
最后,我把自己这些年踩过的坑总结一下。你遇到了,直接对照着查。
- 混用点和箭头:左边是变量却用箭头,左边是指针却用点。编译器会报错,但错误信息有时候不太直观。记住:看左边类型,不是看心情。
- 数组赋值用等号:结构体里的数组成员,不能直接 = 赋值。用 strcpy、memcpy 或者逐个元素赋值。
- 指针未初始化就访问:p->name 之前,确保 p 指向了有效的内存。否则就是段错误,程序直接崩。
- 嵌套过深可读性差:a.b.c.d.e 这种写法,看着就头疼。建议用临时变量拆开,或者用指针简化。
我的建议:写代码的时候,多想想「我手里拿的是变量还是指针」。养成这个习惯,点和箭头就不会搞混。另外,IDE 的自动补全也能帮你——输入点之后,IDE 会列出成员列表;如果 IDE 没反应,八成是你用错了运算符。
好了,结构体成员访问就讲到这里。记住核心原则:变量用点,指针用箭头。下一节咱们聊结构体的内存对齐,那是个更有意思的话题。
公众号:蓝海资料掘金营,微信deep3321