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