为什么需要结构体?
想象一下,你要描述一个学生,这个学生有姓名、年龄、学号和成绩这些信息,如果用基本的数据类型来表示,你可能需要这样定义:

char name[50]; int age; int id; float score;
虽然这样可以,但这些变量是独立的,没有内在的联系,当你需要传递一个学生的所有信息时,你需要传递这四个变量,非常麻烦。
结构体就是为了解决这个问题而生的。 它可以把这些相关的数据项打包成一个整体,就像一个“超级变量”。
结构体的基本定义
结构体的定义使用 struct 关键字,后跟一个结构体名(标签),然后用花括号 包含成员列表。
语法:
struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
数据类型 成员3;
// ...
};
示例:定义一个学生结构体
// 定义一个名为 Student 的结构体
struct Student {
char name[50]; // 姓名
int age; // 年龄
int id; // 学号
float score; // 成绩
};
注意:

struct Student是一个完整的类型,就像int或float一样。- 定义结构体时,系统并不会为它分配内存,它只是一个“蓝图”或“模板”,只有当你创建该类型的变量时,才会分配内存。
结构体变量的声明与初始化
定义好结构体类型后,你就可以像使用基本类型一样声明变量了。
1 声明变量
先定义结构体,再声明变量
struct Student {
char name[50];
int age;
};
int main() {
struct Student student1; // 声明一个 Student 类型的变量 student1
struct Student student2; // 声明另一个变量 student2
return 0;
}
在定义结构体时直接声明变量(不推荐,因为会使结构体定义和变量声明混在一起)
struct Student {
char name[50];
int age;
} student1, student2; // 在这里直接声明了两个变量
2 初始化变量
可以在声明时直接初始化,使用花括号 将初始值列表括起来,顺序必须与结构体成员的顺序一致。

struct Student {
char name[50];
int age;
float score;
};
int main() {
// 初始化 student1
struct Student student1 = {"张三", 20, 95.5f};
// 初始化 student2,部分初始化(后面的成员自动初始化为0)
struct Student student2 = {"李四", 21};
return 0;
}
访问结构体成员
使用成员访问运算符(,英文句点)来访问和修改结构体变量的成员。
语法:
结构体变量名.成员名;
示例:
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
float score;
};
int main() {
struct Student student1 = {"张三", 20, 95.5f};
// 访问成员并打印
printf("姓名: %s\n", student1.name);
printf("年龄: %d\n", student1.age);
printf("成绩: %.2f\n", student1.score);
// 修改成员的值
student1.age = 21;
student1.score = 98.0f;
printf("\n更新后的信息:\n");
printf("姓名: %s\n", student1.name);
printf("年龄: %d\n", student1.age);
printf("成绩: %.2f\n", student1.score);
// 给字符串成员赋值,不能直接用 =,要使用 strcpy
// student1.name = "王五"; // 错误!
strcpy(student1.name, "王五"); // 正确!
printf("\n再次更新后的姓名: %s\n", student1.name);
return 0;
}
结构体数组
你可以创建一个结构体数组,来存储多个相同类型的结构体。
示例:
#include <stdio.h>
struct Student {
char name[50];
float score;
};
int main() {
// 定义一个可以容纳3个 Student 结构体的数组
struct Student students[3] = {
{"张三", 95.5},
{"李四", 88.0},
{"王五", 92.5}
};
// 遍历并打印数组中的每个元素
for (int i = 0; i < 3; i++) {
printf("学生 %d: 姓名: %s, 成绩: %.2f\n", i + 1, students[i].name, students[i].score);
}
return 0;
}
结构体指针
你可以声明一个指向结构体的指针,通过指针访问成员有两种方式:
(*指针变量名).成员名:先解引用指针,再访问成员。指针变量名->成员名:使用箭头运算符->(由 和>组成),这是更常用、更简洁的方式。
示例:
#include <stdio.h>
#include <string.h>
struct Student {
char name[50];
int age;
};
int main() {
struct Student student1 = {"赵六", 22};
struct Student *ptr; // 声明一个指向 Student 结构体的指针
ptr = &student1; // 让 ptr 指向 student1 的地址
// 使用 . 运算符访问(需要先解引用)
printf("通过指针和 . 运算符访问: 姓名: %s\n", (*ptr).name);
// 使用 -> 运算符访问(推荐)
printf("通过指针和 -> 运算符访问: 年龄: %d\n", ptr->age);
// 通过指针修改成员
ptr->age = 23;
printf("修改后的年龄: %d\n", ptr->age);
return 0;
}
结构体作为函数参数
结构体可以作为函数的参数传递,主要有两种方式:
1 传递结构体本身(值传递)
这种方式会将整个结构体的内容复制一份传给函数,优点是函数内部不会修改原始数据,保证了安全性,缺点是如果结构体很大,复制会消耗较多时间和内存。
#include <stdio.h>
struct Point {
int x;
int y;
};
// 函数接收一个 Point 结构体
void printPoint(struct Point p) {
printf("Point: (%d, %d)\n", p.x, p.y);
}
int main() {
struct Point p1 = {10, 20};
printPoint(p1); // 传递 p1 的一个副本
return 0;
}
2 传递结构体指针(地址传递)
这种方式只传递结构体的地址,函数通过指针来操作原始结构体,优点是效率高,不涉及数据复制,缺点是函数可能会意外地修改原始数据。
#include <stdio.h>
struct Point {
int x;
int y;
};
// 函数接收一个 Point 结构体的指针
void movePoint(struct Point *p, int dx, int dy) {
p->x += dx; // 通过指针修改原始数据
p->y += dy;
}
int main() {
struct Point p1 = {10, 20};
printf("移动前: (%d, %d)\n", p1.x, p1.y);
movePoint(&p1, 5, -5); // 传递 p1 的地址
printf("移动后: (%d, %d)\n", p1.x, p1.y);
return 0;
}
结构体嵌套
结构体的成员可以是另一个结构体类型,这称为结构体嵌套。
示例:
#include <stdio.h>
// 定义日期结构体
struct Date {
int year;
int month;
int day;
};
// 定义学生结构体,其中包含一个 Date 类型的成员
struct Student {
char name[50];
struct Date birthday; // 嵌套结构体
float score;
};
int main() {
struct Student student1 = {"钱七", 2000, 5, 20, 91.0f};
// 访问嵌套结构体的成员
printf("姓名: %s\n", student1.name);
printf("生日: %d-%d-%d\n", student1.birthday.year, student1.birthday.month, student1.birthday.day);
printf("成绩: %.2f\n", student1.score);
return 0;
}
结构体的大小与内存对齐
sizeof 操作符可以获取结构体的大小,需要注意的是,结构体的大小不一定等于其所有成员大小之和,这涉及到内存对齐问题。
内存对齐的规则(简化版):
- 第一个成员放在偏移量为0的地址处。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处,对齐数 =
编译器默认的对齐数与该成员自身大小中较小的那个,在VS中,默认对齐数是8;在Linux中,默认是4。 - 结构体的总大小,必须是所有成员中最大对齐数的整数倍。
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的)的整数倍。
示例:
#include <stdio.h>
struct S1 {
char c1; // 1字节
int i; // 4字节
char c2; // 1字节
}; // 大小可能是 12 字节 (1 + 3(填充) + 4 + 1 + 3(填充) = 12)
struct S2 {
int i; // 4字节
char c1; // 1字节
char c2; // 1字节
}; // 大小可能是 8 字节 (4 + 1 + 1 + 2(填充) = 8)
int main() {
printf("sizeof(struct S1) = %zu\n", sizeof(struct S1));
printf("sizeof(struct S2) = %zu\n", sizeof(struct S2));
return 0;
}
输出(在默认对齐数为8的编译器中):
sizeof(struct S1) = 12
sizeof(struct S2) = 8
如何优化结构体大小? 将占用空间小的成员尽量放在一起,可以减少填充字节,从而节省内存。
typedef 与结构体
使用 typedef 可以为结构体类型定义一个别名,让代码更简洁易读。
先定义,再取别名
struct Student {
char name[50];
int age;
};
typedef struct Student Student; // 为 struct Student 定义一个别名 Student
int main() {
// 现在可以直接使用 Student 来声明变量,而不用写 struct Student
Student s1 = {"孙八", 25};
printf("姓名: %s\n", s1.name);
return 0;
}
在定义时直接取别名(更常用)
// 定义结构体的同时,用 typedef 为其取别名
typedef struct {
char name[50];
int age;
} Student;
int main() {
// 直接使用 Student
Student s1 = {"孙八", 25};
printf("姓名: %s\n", s1.name);
return 0;
}
注意: 如果使用方式二,在定义结构体时不能写结构体名(标签),因为 typedef 已经为你创建了一个新类型 Student。
| 功能 | 语法/示例 | 描述 |
|---|---|---|
| 定义 | struct Student { ... }; |
创建一个结构体类型作为“蓝图”。 |
| 声明 | struct Student s1; |
创建一个结构体变量,分配内存。 |
| 初始化 | struct Student s = {"Tom", 20}; |
在声明时赋初值。 |
| 访问成员 | s.name 或 ptr->name |
使用 或 -> 运算符访问/修改成员。 |
| 结构体数组 | struct Student arr[10]; |
存储多个结构体。 |
| 结构体指针 | struct Student *ptr = &s; |
指向结构体变量,通常用于函数传参以提高效率。 |
| 函数参数 | void func(struct Student s); (值) void func(struct Student *p); (地址) |
决定是复制数据还是传递地址。 |
| 嵌套 | struct A { struct B b; }; |
结构体的成员可以是另一个结构体。 |
typedef |
typedef struct { ... } Student; |
为结构体类型创建一个简洁的别名。 |
| 内存对齐 | sizeof(struct S) |
结构体大小可能大于成员之和,受对齐规则影响。 |
掌握结构体是C语言编程的基石,它让你能够更好地组织和操作复杂的数据,是构建更高级数据结构(如链表、树、图)的基础。
