C语言结构体详解
什么是结构体?
在C语言中,我们有了基本的数据类型(如 int, char, float)和数组(存储相同类型的数据),但现实世界中的很多实体,是由多种不同类型的数据组合而成的,一个“学生”信息,可能包含学号(int)、姓名(char数组)、年龄(int)、成绩(float)等。

为了能够将这些不同类型的数据组合成一个有机的整体,C语言提供了 结构体(Struct) 这种自定义的数据类型。
一句话总结:结构体是一个可以包含不同类型数据成员的集合。
如何定义和声明结构体?
结构体的使用主要分为两步:定义结构体类型 和 声明结构体变量。
定义结构体类型
定义结构体类型只是创建了一个“模板”或“蓝图”,它本身不占用内存空间,语法格式如下:

struct 结构体名 {
数据类型 成员1;
数据类型 成员2;
...
数据类型 成员n;
};
示例:定义一个学生结构体
// 关键字 struct + 结构体名 Student + {成员列表} + 分号
struct Student {
int id; // 学号
char name[50]; // 姓名
int age; // 年龄
float score; // 成绩
};
注意: 定义结构体类型时,最后的分号 绝对不能少!这是初学者最容易犯的错误之一。
声明结构体变量
定义好结构体类型后,我们就可以像使用 int, char 一样来声明该类型的变量了,系统才会为变量分配内存空间。
先定义类型,后声明变量(推荐)

struct Student {
int id;
char name[50];
int age;
float score;
};
// 声明两个 struct Student 类型的变量 s1 和 s2
struct Student s1, s2;
在定义类型的同时声明变量
// 定义 struct Student 类型,并直接声明变量 s3
struct Student {
int id;
char name[50];
int age;
float score;
} s3, s4;
// struct Student 类型已定义,s3 和 s4 是变量
// 你还可以继续声明其他变量
struct Student s5;
使用 typedef 关键字(最常用、最方便)
typedef 可以为一个已有的数据类型创建一个新的名字(别名),使用它可以使结构体变量的声明更简洁。
// 定义 struct Student 类型,并为其创建一个别名 "STU"
typedef struct {
int id;
char name[50];
int age;
float score;
} STU;
// 现在可以直接使用 STU 来声明变量,无需再写 struct
STU s1, s2, s3;
这种方式代码更简洁,可读性也更好,在实际开发中被广泛使用。
如何访问结构体成员?
我们使用 成员访问运算符() 来访问结构体变量中的具体成员。
语法:结构体变量名.成员名
示例:
#include <stdio.h>
#include <string.h>
// 使用 typedef 定义结构体
typedef struct {
int id;
char name[50];
int age;
float score;
} Student;
int main() {
// 声明一个结构体变量
Student s1;
// 为成员赋值
s1.id = 1001;
s1.age = 18;
s1.score = 95.5f;
// 字符串不能直接用 = 赋值,需要使用 strcpy
strcpy(s1.name, "张三");
// 打印成员的值
printf("学号: %d\n", s1.id);
printf("姓名: %s\n", s1.name);
printf("年龄: %d\n", s1.age);
printf("成绩: %.2f\n", s1.score);
return 0;
}
结构体变量的初始化
在声明变量的同时,可以为其赋初值。
示例:
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[50];
int age;
float score;
} Student;
int main() {
// 初始化方式一:按成员顺序
Student s1 = {1002, "李四", 19, 88.0f};
// 初始化方式二:指定成员名(C99标准支持,更清晰)
Student s2 = {
.id = 1003,
.name = "王五",
.age = 20,
.score = 92.5f
};
printf("s1的信息: 学号=%d, 姓名=%s, 年龄=%d, 成绩=%.2f\n",
s1.id, s1.name, s1.age, s1.score);
printf("s2的信息: 学号=%d, 姓名=%s, 年龄=%d, 成绩=%.2f\n",
s2.id, s2.name, s2.age, s2.score);
return 0;
}
结构体数组
如果我们要存储多个学生的信息,可以使用结构体数组。
示例:
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[50];
float score;
} Student;
int main() {
// 定义一个包含3个元素的 Student 结构体数组
Student students[3] = {
{1001, "张三", 90.5},
{1002, "李四", 85.0},
{1003, "王五", 92.5}
};
// 遍历数组并打印每个学生的信息
for (int i = 0; i < 3; i++) {
printf("学生 %d: ID=%d, 姓名=%s, 成绩=%.2f\n",
i + 1, students[i].id, students[i].name, students[i].score);
}
return 0;
}
结构体指针
指向结构体变量的指针,称为结构体指针,它通常用于在函数间传递大型结构体数据,以提高效率(避免整个结构体的拷贝)。
关键点:
- 定义指针:
Student *ptr; - 指向变量:
ptr = &s1; - 通过指针访问成员:
(*ptr).id(先解引用,再用 . 访问成员)ptr->id(使用->运算符,这是更推荐、更简洁的方式)
-> 称为“指向成员运算符”或“箭头运算符”。
示例:
#include <stdio.h>
#include <string.h>
typedef struct {
int id;
char name[50];
float score;
} Student;
int main() {
Student s1 = {1001, "赵六", 96.0f};
Student *ptr; // 声明一个结构体指针
ptr = &s1; // 让指针 ptr 指向 s1 的地址
// 使用 . 运算符访问
printf("使用 . 访问: ID=%d, 姓名=%s\n", s1.id, s1.name);
// 使用 -> 运算符访问
printf("使用 -> 访问: ID=%d, 姓名=%s\n", ptr->id, ptr->name);
// 也可以使用 (*ptr).id 的形式
printf("使用 (*ptr). 访问: ID=%d\n", (*ptr).id);
return 0;
}
结构体作为函数参数
结构体可以作为函数的参数进行传递,主要有两种方式:
- 值传递: 将结构体变量的副本传递给函数,函数内部修改不会影响原始变量。
- 地址传递(指针传递): 将结构体变量的地址传递给函数,函数可以通过指针修改原始变量,且效率更高(特别是结构体很大时)。
示例:
#include <stdio.h>
typedef struct {
int x;
int y;
} Point;
// 值传递:修改的是副本,不影响主函数中的 p1
void modifyByValue(Point p) {
p.x = 100;
p.y = 200;
printf("函数内部 (值传递): x=%d, y=%d\n", p.x, p.y);
}
// 地址传递:修改的是原始变量
void modifyByPointer(Point *p) {
p->x = 300;
p->y = 400;
printf("函数内部 (指针传递): x=%d, y=%d\n", p->x, p->y);
}
int main() {
Point p1 = {10, 20};
printf("调用函数前: x=%d, y=%d\n", p1.x, p1.y);
modifyByValue(p1);
printf("值传递后: x=%d, y=%d\n", p1.x, p1.y); // 值未改变
modifyByPointer(&p1);
printf("指针传递后: x=%d, y=%d\n", p1.x, p1.y); // 值已改变
return 0;
}
结构体嵌套
结构体的成员也可以是另一个结构体类型,这就是结构体的嵌套。
示例:
#include <stdio.h>
#include <string.h>
// 定义日期结构体
typedef struct {
int year;
int month;
int day;
} Date;
// 定义学生结构体,其中包含一个 Date 类型的成员
typedef struct {
int id;
char name[50];
Date birthday; // 嵌套结构体
} Student;
int main() {
Student s1;
s1.id = 1001;
strcpy(s1.name, "孙七");
// 访问嵌套结构体的成员
s1.birthday.year = 2005;
s1.birthday.month = 8;
s1.birthday.day = 10;
printf("学生ID: %d\n", s1.id);
printf("学生姓名: %s\n", s1.name);
printf("出生日期: %d-%02d-%02d\n",
s1.birthday.year, s1.birthday.month, s1.birthday.day);
return 0;
}
结构体的大小与内存对齐
在C语言中,结构体的大小并不等于其所有成员大小之和,为了提高CPU的访问效率,编译器会进行内存对齐。
内存对齐规则(简化版):
- 第一个成员永远放在偏移量为0的位置。
- 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
- 对齐数 =
编译器默认的对齐数与该成员自身大小中较小的那个。 - 在VS/VC中,默认对齐数是8;在Linux/GCC中,默认是1(不进行对齐)。
- 对齐数 =
- 结构体的总大小,必须是所有成员中最大对齐数的整数倍。
- 如果嵌套了结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的)的整数倍。
示例:
#include <stdio.h>
typedef struct {
char c1; // 1字节
int i; // 4字节
char c2; // 1字节
} Test;
int main() {
printf("sizeof(Test) = %zu\n", sizeof(Test)); // 输出可能是 12
// 分析:
// c1 放在偏移量 0,占 1 字节。
// i 需要对齐到 4 字节边界,所以从偏移量 4 开始,占 4 字节。
// c2 放在偏移量 8,占 1 字节。
// 总大小是 9,但需要是最大对齐数 (4) 的倍数,所以补 3 个字节,最终大小为 12。
return 0;
}
如何修改结构体以节省空间? 将小的成员放在一起,大的成员放在一起,可以减少内存填充,节省空间。
typedef struct {
char c1; // 1字节
char c2; // 1字节
int i; // 4字节
} Test2;
printf("sizeof(Test2) = %zu\n", sizeof(Test2)); // 输出 8
结构体与 const
当使用结构体指针作为函数参数,并且不希望函数内部修改结构体的内容时,可以使用 const 关键字来修饰指针。
void printStudentInfo(const Student *s) {
// s->id = 999; // 错误!不能修改 const 指针指向的内容
printf("ID: %d\n", s->id); // 读取是允许的
}
这既保证了数据的安全性,又避免了值传递带来的性能开销。
| 特性 | 描述 | 示例 |
|---|---|---|
| 定义 | 自定义数据类型,组合不同类型成员。 | struct Student { ... }; |
| 声明变量 | 使用定义好的类型创建变量。 | struct Student s1; 或 STU s1; (使用typedef) |
| 访问成员 | 使用 运算符。 | s1.id = 100; |
| 指针访问 | 使用 -> 运算符。 |
ptr->id = 100; |
| 初始化 | 声明时赋初值。 | Student s = {1001, "Tom"}; |
| 数组 | 存储多个结构体。 | Student class[30]; |
| 函数参数 | 值传递(安全,低效)或指针传递(可修改,高效)。 | void func(Student s) 或 void func(Student *p) |
| 嵌套 | 结构体成员可以是另一个结构体。 | Date birthday; |
| 内存对齐 | 编译器为提高效率进行的内存填充。 | sizeof 结果可能大于成员之和。 |
掌握结构体是学习C语言面向对象思想(C++中的类)和复杂数据结构(如链表、树)的基础,希望这份详细的教程能对你有所帮助!
