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 程序,为后续学习数据结构与算法打下坚实基础。