目录
结构体(Structure)和联合体(Union)是C语言中两种重要的自定义数据类型,它们允许程序员将不同类型的数据组合在一起,形成更复杂的数据结构。虽然它们在语法上相似,但在内存布局和使用场景上有本质区别。
结构体(Structure)
基本概念
结构体是一种用户自定义的数据类型,用于将多个不同类型的数据项组合在一起。每个数据项称为结构体的成员(Member),它们在内存中依次排列。
结构体定义与声明
c
// 结构体定义
struct Person {
char name[50]; // 姓名
int age; // 年龄
float height; // 身高
}; // 注意分号不能省略
// 声明结构体变量
struct Person p1; // 声明一个Person类型的变量
// 定义并初始化
struct Person p2 = {
"张三",
25,
175.5
};
// 访问结构体成员
printf("姓名: %s\n", p2.name);
printf("年龄: %d\n", p2.age);
结构体嵌套
结构体可以包含其他结构体作为成员:
c
// 日期结构体
struct Date {
int year;
int month;
int day;
};
// 包含Date结构体的Person结构体
struct Person {
char name[50];
int age;
struct Date birthday; // 嵌套结构体
};
// 初始化嵌套结构体
struct Person p = {
"李四",
30,
{1990, 5, 15} // 初始化birthday成员
};
// 访问嵌套结构体成员
printf("出生日期: %d-%d-%d\n",
p.birthday.year,
p.birthday.month,
p.birthday.day);
结构体指针
通过结构体指针可以间接访问结构体成员:
c
struct Person {
char name[50];
int age;
};
int main() {
struct Person p = {"王五", 28};
struct Person *ptr = &p; // 指向结构体的指针
// 通过指针访问成员的两种方式
printf("姓名: %s\n", (*ptr).name); // 使用解引用操作符
printf("年龄: %d\n", ptr->age); // 使用箭头操作符
return 0;
}
结构体数组
结构体数组是一组具有相同结构体类型的变量:
c
struct Point {
int x;
int y;
};
int main() {
// 结构体数组定义与初始化
struct Point points[3] = {
{1, 2},
{3, 4},
{5, 6}
};
// 访问结构体数组元素
for (int i = 0; i < 3; i++) {
printf("点 %d: (%d, %d)\n", i+1, points[i].x, points[i].y);
}
return 0;
}
结构体的内存布局
结构体的成员在内存中依次排列,但可能存在内存对齐(Alignment):
c
struct Example {
char c; // 1字节
int i; // 4字节
char d; // 1字节
};
// 在32位系统上,sizeof(struct Example)通常为12字节,而非6字节
// 原因是内存对齐:char后填充3字节,使int从4字节边界开始
结构体作为函数参数
结构体可以作为函数参数传递,有两种方式:
c
struct Rectangle {
int width;
int height;
};
// 1. 值传递(复制整个结构体)
int areaByValue(struct Rectangle r) {
return r.width * r.height;
}
// 2. 指针传递(传递结构体地址)
int areaByPointer(struct Rectangle *r) {
return r->width * r->height;
}
int main() {
struct Rectangle rect = {10, 5};
printf("面积(值传递): %d\n", areaByValue(rect));
printf("面积(指针传递): %d\n", areaByPointer(&rect));
return 0;
}
联合体(Union)
基本概念
联合体是一种特殊的数据类型,允许在相同的内存位置存储不同类型的数据。联合体的所有成员共享同一块内存空间,因此联合体的大小等于其最大成员的大小。
联合体定义与声明
c
// 联合体定义
union Data {
int i; // 4字节
float f; // 4字节
char str[20]; // 20字节
}; // 注意分号不能省略
// 声明联合体变量
union Data data; // 声明一个Data类型的变量
// 联合体大小
printf("联合体大小: %lu 字节\n", sizeof(data)); // 输出20(最大成员大小)
联合体成员访问
联合体在同一时间只能存储一个成员的值:
c
union Data {
int i;
float f;
char str[20];
};
int main() {
union Data data;
// 存储整数
data.i = 10;
printf("data.i: %d\n", data.i);
// 存储浮点数(覆盖之前的整数)
data.f = 22.5;
printf("data.f: %f\n", data.f);
// 存储字符串(覆盖之前的浮点数)
strcpy(data.str, "Hello");
printf("data.str: %s\n", data.str);
return 0;
}
联合体的内存布局
联合体的所有成员共享同一块内存空间:
c
union Mixed {
int i; // 4字节
char c[4]; // 4字节
};
int main() {
union Mixed m;
m.i = 0x12345678;
// 在小端序系统上,内存布局为:
// m.c[0]: 0x78
// m.c[1]: 0x56
// m.c[2]: 0x34
// m.c[3]: 0x12
printf("m.c[0]: 0x%x\n", m.c[0]); // 输出0x78(小端序)
return 0;
}
联合体嵌套
联合体可以嵌套在结构体中,反之亦然:
c
// 嵌套联合体的结构体
struct Record {
int id;
char type; // 'I'表示整数,'F'表示浮点数
union {
int iValue;
float fValue;
} value; // 匿名联合体
};
int main() {
struct Record r1 = {1, 'I', {.iValue = 100}};
struct Record r2 = {2, 'F', {.fValue = 3.14f}};
if (r1.type == 'I') {
printf("r1的整数值: %d\n", r1.value.iValue);
}
if (r2.type == 'F') {
printf("r2的浮点值: %f\n", r2.value.fValue);
}
return 0;
}
联合体的应用场景
- 节省内存:当一个变量可能有多种类型,但同一时间只使用一种类型时
- 类型转换:在不同数据类型之间进行位模式转换
- 访问数据的不同表示:例如访问整数的各个字节
- 变体数据结构:实现可以存储不同类型数据的通用容器
结构体与联合体的对比
特性 | 结构体(Structure) | 联合体(Union) |
---|---|---|
内存布局 | 成员依次排列,可能有内存对齐 | 所有成员共享同一块内存空间 |
大小计算 | 所有成员大小之和(考虑对齐) | 最大成员的大小 |
成员访问 | 所有成员可同时访问 | 同一时间只能访问一个成员 |
数据存储 | 每个成员存储独立的数据 | 所有成员共享相同的数据存储空间 |
内存占用 | 通常较大 | 通常较小 |
适用场景 | 组合不同类型的数据 | 节省内存,处理变体数据 |
实际应用示例
结构体应用示例:学生信息管理
c
#include <stdio.h>
#include <string.h>
// 学生结构体定义
struct Student {
char name[50];
int id;
float scores[3]; // 三门课程的成绩
};
// 计算平均分
float calculateAverage(struct Student *s) {
float sum = 0;
for (int i = 0; i < 3; i++) {
sum += s->scores[i];
}
return sum / 3;
}
int main() {
// 初始化学生数组
struct Student students[2] = {
{"张三", 101, {85.5, 90.0, 78.5}},
{"李四", 102, {92.0, 87.5, 95.0}}
};
// 显示学生信息
for (int i = 0; i < 2; i++) {
printf("学生: %s (ID: %d)\n", students[i].name, students[i].id);
printf("成绩: %.1f, %.1f, %.1f\n",
students[i].scores[0],
students[i].scores[1],
students[i].scores[2]);
printf("平均分: %.2f\n\n", calculateAverage(&students[i]));
}
return 0;
}
联合体应用示例:变体数据类型
c
#include <stdio.h>
// 变体数据类型
union Variant {
int i;
float f;
char str[20];
};
// 数据类型枚举
enum Type {INT, FLOAT, STRING};
// 数据结构
struct Data {
enum Type type;
union Variant value;
};
// 打印变体数据
void printData(struct Data d) {
switch (d.type) {
case INT:
printf("整数值: %d\n", d.value.i);
break;
case FLOAT:
printf("浮点值: %f\n", d.value.f);
break;
case STRING:
printf("字符串值: %s\n", d.value.str);
break;
}
}
int main() {
// 创建不同类型的数据
struct Data d1 = {INT, {.i = 42}};
struct Data d2 = {FLOAT, {.f = 3.14f}};
struct Data d3 = {STRING, {.str = "Hello"}};
// 打印数据
printData(d1);
printData(d2);
printData(d3);
return 0;
}
注意事项
结构体注意事项
- 内存对齐:结构体大小可能大于成员大小之和,编译器会插入填充字节以满足对齐要求
- 结构体复制:结构体赋值会复制整个结构体,对于大型结构体可能影响性能
- 结构体指针传递:对于大型结构体,使用指针传递可提高性能
- 结构体嵌套:避免过度嵌套导致代码复杂度过高
联合体注意事项
- 成员覆盖:写入一个成员会覆盖其他成员的值
- 类型安全:必须跟踪当前存储的成员类型,否则访问错误类型会导致未定义行为
- 对齐要求:联合体的对齐方式由其最大成员决定
- 应用限制:联合体主要用于节省内存,不适合需要同时存储多个值的场景
总结
结构体和联合体是C语言中两种重要的自定义数据类型,它们各自有独特的内存布局和应用场景:
- 结构体用于组合不同类型的数据,每个成员有独立的内存空间
- 联合体用于节省内存,所有成员共享同一块内存空间
- 结构体适合需要同时存储多个相关数据的场景
- 联合体适合处理变体数据或需要在不同类型间共享位模式的场景
理解结构体和联合体的区别和联系,能够帮助程序员设计更高效、更灵活的数据结构,满足不同的编程需求。