C: 结构体
结构体就像一张定制表格:把不同类型的信息(姓名、年龄、成绩)打包在一起,变成一个整体来管理。
1. struct 定义与声明
(1) 定义结构体类型
C
struct Student {
char name[20];
int age;
float score;
};
这定义了一个类型 struct Student,注意末尾的分号不能省。
(2) 声明结构体变量
C
struct Student s1;
struct Student s2, s3;
也可以在定义类型的同时声明变量:
C
struct Point {
int x;
int y;
} p1, p2;
(3) 匿名结构体
如果只用一次,可以不写类型名:
C
struct {
int width;
int height;
} box;
⚠️ 匿名结构体无法再声明新变量,通常只用于一次性场景。
2. 结构体变量初始化
(1) 顺序初始化
C
struct Student s1 = {"张三", 20, 89.5};
(2) 指定成员初始化(C99)
C
struct Student s2 = {.name = "李四", .score = 92.0, .age = 21};
💡 指定成员初始化可以不按顺序,未指定的成员自动清零。
(3) 部分初始化
C
struct Student s3 = {"王五"};
name 被初始化,其余成员自动为 0。
3. 成员访问
(1) 点运算符 .
通过结构体变量访问成员:
C
struct Student s = {"赵六", 19, 78.5};
printf("姓名: %s\n", s.name);
printf("年龄: %d\n", s.age);
s.score = 85.0;
(2) 箭头运算符 ->
通过结构体指针访问成员:
C
struct Student s = {"赵六", 19, 78.5};
struct Student *ps = &s;
printf("姓名: %s\n", ps->name);
ps->age = 20;
ps->name 等价于 (*ps).name。-> 是语法糖,用指针时更简洁。
💡 口诀:变量用点,指针用箭头。
▶ 示例
C
#include <stdio.h>
struct Book {
char title[50];
char author[30];
float price;
};
int main(void) {
struct Book b1 = {.title = "C程序设计语言", .author = "K&R", .price = 45.0};
struct Book *pb = &b1;
printf("书名: %s\n", pb->title);
printf("作者: %s\n", pb->author);
printf("价格: %.1f\n", pb->price);
pb->price = 55.0;
printf("折扣后: %.1f\n", b1.price);
return 0;
}
TEXT
📖 仅展示
书名: C程序设计语言
作者: K&R
价格: 45.0
折扣后: 55.0
4. 结构体数组
把多个结构体放在数组里统一管理。
C
struct Student class[3] = {
{"张三", 20, 89.5},
{"李四", 21, 92.0},
{"王五", 19, 78.0}
};
for (int i = 0; i < 3; i++) {
printf("%s %d %.1f\n", class[i].name, class[i].age, class[i].score);
}
TEXT
📖 仅展示
张三 20 89.5
李四 21 92.0
王五 19 78.0
用指针遍历结构体数组:
C
struct Student *p = class;
for (int i = 0; i < 3; i++, p++) {
printf("%s %.1f\n", p->name, p->score);
}
5. 结构体指针
结构体指针最常见的用法:在函数间传递结构体时避免拷贝整块数据。
C
void print_student(struct Student *ps) {
printf("%s, %d岁, %.1f分\n", ps->name, ps->age, ps->score);
}
int main(void) {
struct Student s = {"张三", 20, 89.5};
print_student(&s);
return 0;
}
TEXT
📖 仅展示
张三, 20岁, 89.5分
⚠️ 传值会拷贝整个结构体,如果成员多、含大数组,开销很大。传指针只拷贝一个地址。
6. 结构体作为函数参数
(1) 传值
C
void add_score(struct Student s, float bonus) {
s.score += bonus;
}
⚠️ 传值时函数内修改的是副本,不会影响原变量。
(2) 传指针
C
void add_score(struct Student *s, float bonus) {
s->score += bonus;
}
函数内通过指针修改,直接影响原变量。
▶ 示例
C
#include <stdio.h>
struct Score {
char name[20];
int chinese;
int math;
int english;
};
int total(struct Score *s) {
return s->chinese + s->math + s->english;
}
void boost(struct Score *s, int bonus) {
s->chinese += bonus;
s->math += bonus;
s->english += bonus;
}
int main(void) {
struct Score stu = {.name = "周七", .chinese = 80, .math = 75, .english = 88};
printf("原始总分: %d\n", total(&stu));
boost(&stu, 5);
printf("加分后总分: %d\n", total(&stu));
return 0;
}
TEXT
📖 仅展示
原始总分: 243
加分后总分: 258
7. 结构体赋值与比较
(1) 赋值
同类型结构体可以直接赋值,成员逐个拷贝:
C
struct Student s1 = {"张三", 20, 89.5};
struct Student s2;
s2 = s1;
(2) 比较
⚠️ 结构体不能用
== 或 != 比较,必须逐个成员比较:
C
int equal(struct Student *a, struct Student *b) {
return a->age == b->age && a->score == b->score;
}
8. 结构体嵌套
结构体的成员可以是另一个结构体:
C
struct Date {
int year;
int month;
int day;
};
struct Employee {
char name[20];
struct Date birthday;
float salary;
};
struct Employee e = {"陈八", {1995, 6, 15}, 8000.0};
printf("生日: %d-%02d-%02d\n", e.birthday.year, e.birthday.month, e.birthday.day);
TEXT
📖 仅展示
生日: 1995-06-15
❓ 常见问题
Q
. 和 -> 有什么区别?A
. 用于结构体变量访问成员,-> 用于结构体指针访问成员。p->name 等价于 (*p).name。Q 结构体可以用
== 比较吗?A 不能。C 语言不支持结构体的
== 比较,需要逐成员比较或用 memcmp(注意填充字节问题)。Q 结构体传值和传指针怎么选?
A 一般传指针——效率高、可修改原数据。只读场景加 const 保护:
const struct Student *s。Q 结构体定义后面为什么必须加分号?
A 因为定义末尾可以紧跟变量声明列表,分号告诉编译器列表结束,不加会报语法错误。
📖 小节
struct将不同类型的成员打包成自定义类型- 初始化可用顺序赋值或 C99 指定成员
.name = ... - 变量用
.访问成员,指针用->访问成员 - 结构体数组可批量管理同类型数据
- 函数参数优先传指针,避免拷贝开销
- 结构体可嵌套,成员本身也可以是结构体
📝 作业
- 定义
struct Rectangle含 width 和 height,编写函数float area(struct Rectangle *r)和float perimeter(struct Rectangle *r),在 main 中计算输出 - 定义
struct Date,编写函数int date_compare(struct Date *a, struct Date *b),返回 -1/0/1 表示 a 早于/等于/晚于 b - 定义学生结构体数组(5人),按成绩从高到低排序并输出