指针与结构体:C 语言进阶核心详解

1. 引言

在 C 语言的学习过程中,指针与结构体是两个绕不开的核心概念。指针让我们能够直接操作内存地址,而结构体则允许我们将不同类型的数据组合成一个整体。当这两者结合时,便诞生了 C 语言中最强大、也最让初学者头疼的用法------结构体指针。

本文将带你从基础出发,逐步深入理解指针与结构体的关系,掌握结构体指针的定义、初始化、访问方式,以及它在链表、动态内存分配等场景中的典型应用。

2. 结构体基础回顾

在讨论指针之前,我们先快速回顾一下结构体的基本用法。

2.1 结构体的定义

c 复制代码
struct Student {
    char name[50];
    int age;
    float score;
};

2.2 结构体变量的声明与初始化

c 复制代码
struct Student stu1 = {"张三", 20, 89.5};
struct Student stu2;

2.3 通过点运算符访问成员

c 复制代码
printf("姓名:%s\n", stu1.name);
printf("年龄:%d\n", stu1.age);
printf("成绩:%.1f\n", stu1.score);

3. 指针基础回顾

指针是存储变量地址的变量。理解指针,首先要理解地址与取地址运算符。

3.1 指针的声明与赋值

c 复制代码
int a = 10;
int *p = &a;   // p 存储变量 a 的地址

3.2 解引用运算符

c 复制代码
printf("%d\n", *p);   // 输出 10,*p 表示 p 指向的变量的值

4. 结构体指针

结构体指针,就是指向结构体变量的指针。它存储的是结构体变量在内存中的起始地址。

4.1 结构体指针的声明

c 复制代码
struct Student *pStu;

4.2 结构体指针的初始化

c 复制代码
struct Student stu1 = {"张三", 20, 89.5};
struct Student *pStu = &stu1;

4.3 通过指针访问结构体成员

访问结构体指针所指向的成员,有两种方式:

方式一:先解引用,再使用点运算符

c 复制代码
(*pStu).age = 21;
printf("年龄:%d\n", (*pStu).age);

方式二:使用箭头运算符(推荐)

c 复制代码
pStu->age = 21;
printf("年龄:%d\n", pStu->age);

注意:pStu->age 等价于 (*pStu).age。箭头运算符 -> 是 C 语言专门为结构体指针提供的语法糖,书写更简洁,也更不容易出错。

5. 结构体指针作为函数参数

将结构体指针作为函数参数传递,可以避免结构体整体拷贝带来的性能开销,同时允许函数直接修改原结构体的内容。

5.1 传递结构体变量(值传递)

c 复制代码
void printStudent(struct Student stu) {
    printf("姓名:%s\n", stu.name);
}

这种方式会拷贝整个结构体,当结构体很大时效率较低。

5.2 传递结构体指针(引用传递)

c 复制代码
void updateStudent(struct Student *pStu) {
    pStu->age += 1;   // 直接修改原结构体
}

int main() {
    struct Student stu1 = {"张三", 20, 89.5};
    updateStudent(&stu1);
    printf("年龄:%d\n", stu1.age);   // 输出 21
    return 0;
}

5.3 传递 const 指针保护数据

如果函数只需要读取结构体内容而不需要修改,可以加上 const 修饰,防止意外修改:

c 复制代码
void printStudent(const struct Student *pStu) {
    printf("姓名:%s\n", pStu->name);
}

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

结构体指针最常见的应用之一,就是配合 malloc 在堆上动态分配结构体内存。

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Student {
    char name[50];
    int age;
};

int main() {
    struct Student *pStu = (struct Student *)malloc(sizeof(struct Student));
    if (pStu == NULL) {
        printf("内存分配失败\n");
        return 1;
    }

    strcpy(pStu->name, "李四");
    pStu->age = 22;

    printf("姓名:%s,年龄:%d\n", pStu->name, pStu->age);

    free(pStu);   // 释放内存
    return 0;
}

注意:使用 malloc 分配的内存必须用 free 释放,否则会造成内存泄漏。

7. 结构体指针数组

结构体指针数组,即数组的每个元素都是一个指向结构体的指针。这在管理多个动态分配的结构体时非常有用。

c 复制代码
struct Student *students[3];

students[0] = (struct Student *)malloc(sizeof(struct Student));
students[1] = (struct Student *)malloc(sizeof(struct Student));
students[2] = (struct Student *)malloc(sizeof(struct Student));

// 使用
students[0]->age = 20;
students[1]->age = 21;
students[2]->age = 22;

// 释放
for (int i = 0; i < 3; i++) {
    free(students[i]);
}

8. 结构体指针与链表

链表是结构体指针最经典的应用场景。每个节点是一个结构体,其中包含一个指向下一个节点的指针。

c 复制代码
struct Node {
    int data;
    struct Node *next;
};

8.1 创建链表节点

c 复制代码
struct Node *createNode(int data) {
    struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
    if (newNode == NULL) {
        return NULL;
    }
    newNode->data = data;
    newNode->next = NULL;
    return newNode;
}

8.2 在链表头部插入节点

c 复制代码
void insertAtHead(struct Node **head, int data) {
    struct Node *newNode = createNode(data);
    if (newNode == NULL) return;
    newNode->next = *head;
    *head = newNode;
}

注意:这里使用了二级指针 struct Node **head,因为我们需要修改头指针本身的值。

8.3 遍历链表

c 复制代码
void printList(struct Node *head) {
    struct Node *current = head;
    while (current != NULL) {
        printf("%d -> ", current->data);
        current = current->next;
    }
    printf("NULL\n");
}

9. 常见错误与注意事项

9.1 未初始化指针就使用

c 复制代码
struct Student *pStu;   // 未初始化,指向未知地址
pStu->age = 20;         // 错误!野指针

正确做法 :要么指向一个已存在的结构体变量,要么用 malloc 分配内存。

9.2 忘记释放内存

c 复制代码
struct Student *pStu = (struct Student *)malloc(sizeof(struct Student));
// 使用完后忘记 free(pStu);

这会导致内存泄漏。务必在不再使用时调用 free。

9.3 释放后继续使用(悬空指针)

c 复制代码
free(pStu);
pStu->age = 20;   // 错误!pStu 已成为悬空指针

释放后应将指针置为 NULL:

c 复制代码
free(pStu);
pStu = NULL;

9.4 混淆 -> 与 .

  • 结构体变量用 . 访问成员:stu.age
  • 结构体指针用 -> 访问成员:pStu->age

10. 总结

指针与结构体的结合是 C 语言进阶的重要里程碑。通过本文的学习,我们掌握了:

  • 结构体指针的声明、初始化与成员访问(-> 运算符)
  • 结构体指针作为函数参数的高效传参方式
  • 结构体指针配合 malloc 进行动态内存分配
  • 结构体指针数组与链表的典型应用
  • 常见错误与规避方法

掌握这些内容后,你就能更自如地编写高效、灵活的 C 程序,为后续学习数据结构与算法打下坚实基础。

相关推荐
乌暮1 小时前
JVM 原理与实践:从运行时数据区到线上故障排查
java·开发语言·jvm·后端·学习·面试
en.en..1 小时前
IO 多路复用:select、poll、epoll 核心函数解析
网络
我就是不信1 小时前
TCP/IP 网络编程:从入门到实战
网络·网络协议·tcp/ip
Logic1011 小时前
关于汉诺塔问题的分析
c语言·算法·递归·函数·解题思路·递归函数·汉诺塔问题
维克兜率天1 小时前
【维克】配对交易的季节性:哪些品种适合长拿?
android·开发语言·笔记·python·算法·kotlin·量化
沫璃染墨1 小时前
《Qt从零入门系列(十二):Qt文件操作详解——从QFile读写到QFileInfo与记事本实战》
开发语言·网络·c++·qt·交互·信号处理·文件
时间的拾荒人2 小时前
Qt 界面布局与容器控件详解:从分组框到布局管理器
开发语言·qt·面试
水饺编程2 小时前
第1章:在 Windows 中安装 GCC 套件
c语言·汇编