目录
[通过指针访问成员(-> 操作符的本质)](#通过指针访问成员(-> 操作符的本质))
[-> 运算符的本质:基址 + 偏移](#-> 运算符的本质:基址 + 偏移)
[修改 name(字符串数组)](#修改 name(字符串数组))
[修改 sex 和 score](#修改 sex 和 score)
[-> 和 . 的区别](#-> 和 . 的区别)
[const 与结构体指针](#const 与结构体指针)
结构体定义
struct 结构体名{ 数据类型 成员1; 数据类型 成员2; // ... };
-
定义结构体变量时,系统会在内存中分配一块连续的空间,大小至少为各成员大小之和(实际因内存对齐可能更大)。
-
成员按照声明顺序依次排列:成员1在最低地址,成员2最高地址 。
初始化结构体
(1)定义时直接初始化
struct Student stu = {1001, "张三", 'M', 89.5};
- 赋值顺序必须和结构体成员定义顺序一致。
(2)先定义后赋值
struct Student stu;
stu.id = 1001;
// stu.name = "张三"; 错误!字符串不能直接用=赋值
strcpy(stu.name, "张三");
stu.sex = 'M';
stu.score = 89.5;
- 字符数组成员必须用
strcpy赋值。
利用结构体录入学生信息
#include <stdio.h>
struct Student {
int id;
char name[20];
char sex;
float score;
};
int main() {
struct Student stu;
printf("请输入学号: ");
scanf("%d", &stu.id);
printf("请输入姓名: ");
scanf("%s", stu.name);
printf("请输入性别: ");
scanf("%c", stu.sex);
printf("请输入成绩: ");
scanf("%f", &stu.score);
printf("\n===== 学生信息 =====\n");
printf("学号: %d\n", stu.id);
printf("姓名: %s\n", stu.name);
printf("性别: %c\n", stu.sex);
printf("成绩: %.1f\n", stu.score);
return 0;
}

显示
sex地址为0x7ffecd7f7078,score为0x7ffecd7f707c,中间有 3 字节填充。
结构体指针
指针存储的是地址
struct Student *p = &stu;
-
指针变量
p本身也占用内存(通常 8 字节),它里面存放的是stu的起始地址 (即&stu)。 -
p的值就是stu的首地址,&p是指针变量自己的地址。#include <stdio.h>
struct Student {
int id; // 4 字节
char name[20]; // 20 字节
char sex; // 1 字节
float score; // 4 字节
}; // 结构体总大小可能因对齐而大于 4+20+1+4 = 29 字节int main() {
struct Student stu = {
1001,
"张三",
'M',
89.5
};struct Student *p = &stu; printf("结构体变量 stu 的起始地址: %p\n", &stu); printf("p 的内容: %p\n", p); printf("&p 的地址: %p\n", &p); printf("\n======== 成员信息 ========\n"); printf("成员 id : 值 = %d, 地址 = %p\n", p->id, &(p->id)); printf("成员 name : 值 = %s, 地址 = %p\n", p->name, p->name); // 数组名即地址 printf("成员 sex : 值 = %c, 地址 = %p\n", p->sex, &(p->sex)); printf("成员 score : 值 = %.1f, 地址 = %p\n", p->score, &(p->score)); printf("\n结构体 Student 的大小: %zu 字节\n", sizeof(struct Student)); return 0;}


用指针改变结构体的变量
通过指针访问成员(-> 操作符的本质)
p->id = 1002;
-
编译器知道结构体成员的类型和偏移量:
-
id偏移 0 字节 -
name偏移 4 字节(假设对齐后) -
sex偏移 24 字节(4 + 20) -
score偏移28 字节(对齐后)
-
-
p->id等价于*(p + 偏移量)。因为p是结构体指针,p+0还是p,所以p->id就是从p指向的地址开始,取前 4 字节作为 int。 -
同理
p->score:从p的地址加上score的偏移量,取出 4 字节作为 float。#include <stdio.h>
#include <string.h>struct Student {
int id;
char name[20];
char sex;
float score;
};int main() {
struct Student stu = {1001, "张三", 'M', 85.5};
struct Student *p = &stu; // 指针指向 stuprintf("修改前:\n"); printf("学号: %d, 姓名: %s, 性别: %c, 成绩: %.1f\n", stu.id, stu.name, stu.sex, stu.score); p->id = 1002; strcpy(p->name, "李四"); // 字符串不能用 = 直接赋值,用 strcpy p->sex = 'F'; p->score = 92.0; printf("\n修改后:\n"); printf("学号: %d, 姓名: %s, 性别: %c, 成绩: %.1f\n", stu.id, stu.name, stu.sex, stu.score); return 0;}
从内存角度解释如何用指针改变结构体的变量
指针存储的是首地址
-
p这个变量里保存的就是0x7ffecd7f7060。 -
编译器知道
struct Student的每个成员的类型 和偏移量。
-> 运算符的本质:基址 + 偏移
例如:
p->id = 1002;
编译器将其转换为:
*(int*)((char*)p + 0) = 1002;
-
(char*)p将 p 转为字节指针,方便做字节偏移。 -
加 0 得到 id 的地址。
-
再转成
int*然后解引用写入。
修改 name(字符串数组)
strcpy(p->name, "李四");
-
p->name的类型是char[20],在表达式中退化为char*,其值就是(char*)p + 4。 -
strcpy将"李四"的每个字节(包括结尾的\0)从地址0x7ffecd7f7064开始逐个写入,覆盖原来的"张三"。
修改 sex 和 score
p->sex = 'F'; // 写入地址 p + 24 p->score = 92.0; // 写入地址 p + 28
-
p->sex对应地址0x7ffecd7f7078,一个字节,写入'F'(ASCII 0x46)。 -
p->score对应地址0x7ffecd7f707c,四个字节,写入92.0的 IEEE 754 表示。

注意
-> 和 . 的区别
-
结构体变量用
.:stu.id -
结构体指针用
->:p->id -
等价写法:
(*p).id(括号不能少,因为.优先级高于*) -
常见错误:
*p.id→ 实际被解析为*(p.id),但p不是结构体,编译报错。
结构体指针作为函数参数
void modify(struct Student *p) { p->score = 100.0; // 可以改变外部 stu } void wrong(struct Student stu) { stu.score = 100.0; // 只是修改副本,外部不变 }
内存原因:传指针只复制 8 字节地址,仍指向原结构体;传值会复制整个 32 字节,修改的是副本。
动态分配的结构体指针
struct Student *p = malloc(sizeof(struct Student)); if (p == NULL) { /* 处理失败 */ } p->id = 1003; // 安全 strcpy(p->name, "王五"); free(p); // 必须释放,否则内存泄漏 p = NULL; // 避免野指针
注意 :动态分配的内存位于堆区,使用完必须 free,且释放后不能再通过 p 访问成员。
指向结构体数组的指针
struct Student arr[3]; struct Student *q = arr; // 指向第一个元素 q->id = 1001; // 等价于 arr[0].id (q+1)->id = 1002; // 等价于 arr[1].id
注意 :q+1 会向前移动 sizeof(struct Student) 字节(32 字节),而不是 1 字节。
避免返回局部结构体变量的指针
struct Student* bad() { struct Student stu = {1001, "张三", 'M', 89.5}; return &stu; // 危险!stu 是局部变量,函数结束即销毁 }
内存原因:局部变量在栈上,函数返回后栈帧被回收,指针指向的内存可能被覆盖。
const 与结构体指针
const struct Student *p = &stu; // 指向常量的指针,不能通过 p 修改成员 p->id = 1002; // 编译错误 struct Student *const p = &stu; // 常量指针,不能指向别的变量 p = &stu2; // 错误