为什么需要 struct 指针?
我们来理解为什么需要用指针来操作结构体,主要有以下几个原因:

(图片来源网络,侵删)
-
避免大结构体的拷贝(性能优化):
- 结构体可以包含很多成员,占用较大的内存空间。
- 当你将一个结构体变量作为参数传递给函数时,C 语言会进行值传递,即创建一个完整的副本,如果结构体很大,这个拷贝过程会非常耗时,消耗大量内存。
- 使用指针传递,只需要传递一个内存地址(通常是一个 4 字节或 8 字节的指针),效率极高。
-
修改函数外部的结构体(实现引用传递):
- C 语言函数参数默认是值传递,这意味着在函数内部修改参数,不会影响到函数外部的原始变量。
- 如果你想让函数能够修改调用者提供的结构体,你必须传递该结构体的指针,这样,函数内部通过指针操作的就是原始结构体本身。
-
动态内存分配:
- 在很多情况下,结构体的大小在编译时可能不确定,或者你需要根据运行时的情况来决定创建多少个结构体实例。
- 这时,你会使用
malloc或calloc在堆上动态分配内存,它们返回的是一个指向新分配内存的指针,这个指针通常就是指向一个结构体的指针。
-
构建复杂数据结构:
(图片来源网络,侵删)指针是构建链表、树、图等动态数据结构的基石,一个结构体可以包含一个指向同类型结构体的指针,从而实现节点之间的连接。
如何定义和使用 struct 指针
让我们通过一个具体的例子来学习。
步骤 1: 定义结构体
我们定义一个简单的 Student 结构体。
#include <stdio.h>
#include <string.h>
// 定义一个结构体类型 Student
struct Student {
int id; // 学号
char name[50]; // 姓名
float score; // 分数
};
步骤 2: 声明结构体变量和指针
int main() {
// 声明一个结构体变量 s1
struct Student s1;
struct Student s2;
// 声明一个指向 Student 结构体的指针 pStudent
struct Student *pStudent;
// 让指针 pStudent 指向结构体变量 s1 的内存地址
pStudent = &s1;
// 也可以在声明时直接初始化
// struct Student *pStudent = &s1;
return 0;
}
步骤 3: 通过指针访问结构体成员
这是最关键的一步,有两种主要的语法:
使用 运算符(需要解引用)
你必须先使用 解引用指针,得到它指向的结构体变量,然后再用 来访问成员。
// 解引用指针,然后访问成员 (*pStudent).id = 1001; strcpy((*pStudent).name, "Zhang San"); (*pStudent).score = 95.5;
注意:(*pStudent).name 的括号是必须的,因为 运算符的优先级高于 。
使用 -> 运算符(推荐)
C 语言专门为这种情况提供了一种更简洁、更易读的语法:->(箭头操作符),它等价于 。
// 使用箭头操作符 -> 访问成员 (推荐) pStudent->id = 1001; strcpy(pStudent->name, "Zhang San"); pStudent->score = 95.5;
pStudent->name 完全等同于 (*pStudent).name,但前者显然更清晰。
完整示例
下面是一个完整的例子,展示了如何初始化、打印以及通过指针修改结构体。
#include <stdio.h>
#include <string.h>
// 定义结构体
struct Student {
int id;
char name[50];
float score;
};
// 函数声明:通过指针修改结构体
void updateStudentScore(struct Student *ptr, float newScore);
int main() {
// 1. 声明并初始化一个结构体变量
struct Student s1 = {1001, "Li Si", 88.5f};
// 2. 声明一个指针并指向它
struct Student *pStudent = &s1;
// 3. 使用指针访问和打印成员
printf("--- Before Update ---\n");
printf("ID: %d\n", pStudent->id);
printf("Name: %s\n", pStudent->name);
printf("Score: %.2f\n", pStudent->score);
// 4. 调用函数,通过指针修改结构体
updateStudentScore(pStudent, 92.0f);
// 5. 再次打印,验证修改是否成功
printf("\n--- After Update ---\n");
printf("ID: %d\n", pStudent->id);
printf("Name: %s\n", pStudent->name);
printf("Score: %.2f\n", pStudent->score);
return 0;
}
// 函数定义:接收一个结构体指针和一个新分数
void updateStudentScore(struct Student *ptr, float newScore) {
// 通过指针直接修改原始结构体 s1 的 score 成员
ptr->score = newScore;
printf("\n[Inside function] Score updated to: %.2f\n", ptr->score);
}
输出结果:
--- Before Update ---
ID: 1001
Name: Li Si
Score: 88.50
[Inside function] Score updated to: 92.00
--- After Update ---
ID: 1001
Name: Li Si
Score: 92.00
从输出可以看到,updateStudentScore 函数成功地修改了 main 函数中的 s1 变量,这正是指针传递的魅力所在。
动态分配 struct 指针
这是 struct 指针最常见的应用场景之一。
#include <stdio.h>
#include <stdlib.h> // 需要 malloc 和 free
struct Student {
int id;
char name[50];
float score;
};
int main() {
// 1. 在堆上为结构体分配内存
// sizeof(struct Student) 计算整个结构体的大小
struct Student *pDynamicStudent = (struct Student *)malloc(sizeof(struct Student));
// 2. 检查内存是否分配成功
if (pDynamicStudent == NULL) {
printf("Memory allocation failed!\n");
return 1; // 异常退出
}
// 3. 使用指针访问和初始化成员
pDynamicStudent->id = 1002;
strcpy(pDynamicStudent->name, "Wang Wu");
pDynamicStudent->score = 76.5f;
printf("Dynamic Student:\n");
printf("ID: %d, Name: %s, Score: %.2f\n",
pDynamicStudent->id,
pDynamicStudent->name,
pDynamicStudent->score);
// 4. 释放分配的内存(非常重要!)
// 防止内存泄漏
free(pDynamicStudent);
pDynamicStudent = NULL; // 好习惯,防止悬垂指针
return 0;
}
常见陷阱与注意事项
-
未初始化的指针:
- 错误:
struct Student *p;p->id = 10; - 问题:
p是一个野指针,它指向一个随机的、不可预测的内存地址,向该地址写入数据会导致未定义行为,通常是程序崩溃(段错误)。 - 解决:始终在指针使用前让它指向一个有效的内存地址(如
&s或malloc的返回值)。
- 错误:
-
忘记释放动态内存:
- 使用
malloc分配的内存在程序结束前不会被自动释放,如果忘记调用free,就会造成内存泄漏。 - 解决:对于每一个
malloc,都要确保在对应的代码路径中有free。
- 使用
-
悬垂指针:
- 错误:
struct Student *p; { struct Student s_local; p = &s_local; } // s_local 的生命周期在此结束,内存被回收 p->id = 10; // 错误!p 现在是一个悬垂指针,指向了已释放的内存。 - 问题:指针指向的内存已经被释放,再次访问会导致未定义行为。
- 解决:不要返回或保存指向局部变量的地址。
- 错误:
-
结构体指针的指针(`struct Student `)**:
- 当你需要在一个函数中修改一个指针本身(而不是它指向的内容)时,就需要传递指针的指针。
- 让一个函数为你分配内存并返回新分配的地址。
// 函数原型:分配内存,并将新地址存入 ppStudent void createStudent(struct Student **ppStudent); int main() { struct Student *p = NULL; createStudent(&p); // 传递 p 的地址 if (p) { p->id = 1003; printf("Created student with ID: %d\n", p->id); free(p); } return 0; } void createStudent(struct Student **ppStudent) { *ppStudent = (struct Student *)malloc(sizeof(struct Student)); // (*ppStudent) 现在指向了新分配的内存 }
| 特性 | 描述 |
|---|---|
| 定义 | struct Student *p; 声明一个指向 Student 结构体的指针。 |
| 赋值 | p = &s1; 让指针指向一个已存在的结构体变量。 |
| 访问成员 | p->member 是最常用的方式,等同于 (*p).member。 |
| 核心优势 | 高效(避免大拷贝)、灵活(能修改外部变量)、动态(支持动态内存分配)。 |
| 动态分配 | p = malloc(sizeof(struct Student)); 在堆上创建结构体。 |
| 释放内存 | free(p); 释放动态分配的内存,防止内存泄漏。 |
| 关键陷阱 | 野指针、悬垂指针、忘记 free。 |
掌握 struct 指针是迈向高级 C 语言编程的必经之路,它让你能够编写出更高效、更强大、更灵活的程序。
