4. 结构体指针:定义、访问成员与作为函数参数

结构体指针,说白了就是指向结构体变量的指针。很多初学者觉得它绕,其实它就是个普通指针,只不过它指向的对象是一个结构体。我个人习惯把结构体指针看作是「遥控器」——你不需要把整个结构体搬来搬去,拿着遥控器就能操作它。

4.1 结构体指针的定义

定义结构体指针的语法很简单,和定义其他类型指针一样:

struct 结构体类型名 *指针变量名;

举个例子,假设我们有一个学生结构体:

struct Student {
    char name[32];
    int age;
    float score;
};

// 定义结构体变量和指针
struct Student stu1 = {"张三", 20, 89.5};
struct Student *pStu;

// 让指针指向结构体变量
pStu = &stu1;

嗯,这里要注意:&stu1 取的是整个结构体的首地址,不是某个成员的地址。我在项目中见过有人写成 pStu = stu1,编译直接报错——结构体变量名不是地址,别跟数组搞混了。

4.2 通过指针访问成员

通过指针访问结构体成员有两种方式,我个人强烈推荐第二种:

方式 语法 说明
解引用 + 点操作符 (*pStu).age 先解引用得到结构体,再访问成员。括号不能省
箭头操作符 pStu->age 直接通过指针访问成员,更直观

看个完整例子:

#include <stdio.h>
#include <string.h>

struct Student {
    char name[32];
    int age;
    float score;
};

int main() {
    struct Student stu1;
    struct Student *p = &stu1;

    // 通过指针给成员赋值
    strcpy(p->name, "李四");
    p->age = 22;
    p->score = 95.0;

    // 两种方式访问成员
    printf("姓名:%s\n", p->name);
    printf("年龄:%d\n", (*p).age);
    printf("成绩:%.1f\n", p->score);

    return 0;
}
⚠ 避坑指南
我曾经在代码里写过 *pStu.age,以为先解引用再取成员。结果编译器告诉我 . 的优先级比 * 高,实际被解析成了 *(pStu.age),直接报错。记住:(*p).age 括号不能丢,或者直接用 ->

4.3 指针作为函数参数

为什么要把结构体指针传给函数?说白了就两个原因:

  • 效率:传指针只复制4或8字节(地址),传整个结构体可能复制几十上百字节
  • 修改:传指针可以在函数内部修改结构体的内容,传值的话改的是副本

来看一个实际例子:

#include <stdio.h>
#include <string.h>

struct Student {
    char name[32];
    int age;
    float score;
};

// 通过指针修改结构体内容
void updateScore(struct Student *p, float newScore) {
    if (p != NULL) {
        p->score = newScore;
    }
}

// 通过指针读取结构体内容(加const保护)
void printStudent(const struct Student *p) {
    if (p == NULL) return;
    printf("姓名:%s,年龄:%d,成绩:%.1f\n",
           p->name, p->age, p->score);
}

int main() {
    struct Student stu = {"王五", 21, 78.5};

    printStudent(&stu);
    updateScore(&stu, 88.0);
    printStudent(&stu);

    return 0;
}
💡 我的习惯
写函数参数时,如果只读不写,我习惯加 const 修饰。这样既避免了意外修改,又让调用者一眼就知道这个函数不会动数据。你想想看,这算不算一种「文档即代码」?

4.4 结构体指针与动态内存分配

实际项目中,结构体指针最常用的场景是配合 malloc 动态分配内存。我记得有一次做嵌入式数据采集系统,传感器数量不确定,用静态数组根本没法搞——这时候动态分配就派上用场了。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Sensor {
    int id;
    float value;
    char unit[8];
};

int main() {
    // 动态分配一个结构体
    struct Sensor *pSensor = (struct Sensor *)malloc(sizeof(struct Sensor));
    if (pSensor == NULL) {
        printf("内存分配失败\n");
        return -1;
    }

    // 使用指针操作
    pSensor->id = 1;
    pSensor->value = 25.6;
    strcpy(pSensor->unit, "°C");

    printf("传感器%d:%.1f %s\n", pSensor->id, pSensor->value, pSensor->unit);

    // 用完释放
    free(pSensor);
    pSensor = NULL;  // 避免野指针

    return 0;
}
⚠ 重要提醒
动态分配的结构体,用完后一定要 free,并且把指针置为 NULL。我曾经在一个项目中漏了 free,跑了三天后系统内存耗尽直接死机——排查了一整天才找到原因。嵌入式环境内存本来就紧张,千万别犯这种错。

4.5 结构体指针数组

有时候我们需要管理多个结构体,用指针数组是个好办法。说白了就是数组里每个元素都是一个指针,指向一个结构体。

#include <stdio.h>
#include <stdlib.h>

struct Point {
    int x;
    int y;
};

int main() {
    struct Point *points[3];  // 指针数组,每个元素指向一个Point结构体

    // 动态分配并初始化
    for (int i = 0; i < 3; i++) {
        points[i] = (struct Point *)malloc(sizeof(struct Point));
        if (points[i] != NULL) {
            points[i]->x = i * 10;
            points[i]->y = i * 10 + 5;
        }
    }

    // 遍历输出
    for (int i = 0; i < 3; i++) {
        printf("Point%d:(%d, %d)\n", i, points[i]->x, points[i]->y);
    }

    // 释放内存
    for (int i = 0; i < 3; i++) {
        free(points[i]);
        points[i] = NULL;
    }

    return 0;
}
📌 核心要点总结
  • 结构体指针用 -> 访问成员,比 (*p). 更直观
  • 函数参数传指针:省内存、能修改原数据
  • 只读参数加 const 保护,这是好习惯
  • 动态分配的结构体必须配对 free,防止内存泄漏
  • 指针数组适合管理多个结构体,灵活且高效
结构体指针知识体系 结构体指针 定义:struct 类型 *指针名 访问成员:p->成员 或 (*p).成员 函数参数:传指针(高效、可修改原数据) 动态分配:malloc / free 配对使用 核心操作 访问方式 应用场景 内存管理

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