C语言结构体与联合体详解
目录
- 结构体(Structure)
- 基本概念
- 结构体定义与声明
- 结构体嵌套
- 结构体指针
- 结构体数组
- 结构体的内存布局
- 结构体作为函数参数
- 联合体(Union)
- 基本概念
- 联合体定义与声明
- 联合体成员访问
- 联合体的内存布局
- 联合体嵌套
- 联合体的应用场景
- 结构体与联合体的对比
- 实际应用示例
- 结构体应用示例:学生信息管理
- 联合体应用示例:变体数据类型
- 注意事项
- 结构体注意事项
- 联合体注意事项
- 总结
结构体(Structure)和联合体(Union)是C语言中两种重要的自定义数据类型,它们允许程序员将不同类型的数据组合在一起,形成更复杂的数据结构。虽然它们在语法上相似,但在内存布局和使用场景上有本质区别。
结构体(Structure)
基本概念
结构体是一种用户自定义的数据类型,用于将多个不同类型的数据项组合在一起。每个数据项称为结构体的成员(Member),它们在内存中依次排列。
结构体定义与声明
// 结构体定义
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);
结构体嵌套
结构体可以包含其他结构体作为成员:
// 日期结构体
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);
结构体指针
通过结构体指针可以间接访问结构体成员:
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;
}
结构体数组
结构体数组是一组具有相同结构体类型的变量:
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):
struct Example {char c; // 1字节int i; // 4字节char d; // 1字节
};// 在32位系统上,sizeof(struct Example)通常为12字节,而非6字节
// 原因是内存对齐:char后填充3字节,使int从4字节边界开始
结构体作为函数参数
结构体可以作为函数参数传递,有两种方式:
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)
基本概念
联合体是一种特殊的数据类型,允许在相同的内存位置存储不同类型的数据。联合体的所有成员共享同一块内存空间,因此联合体的大小等于其最大成员的大小。
联合体定义与声明
// 联合体定义
union Data {int i; // 4字节float f; // 4字节char str[20]; // 20字节
}; // 注意分号不能省略// 声明联合体变量
union Data data; // 声明一个Data类型的变量// 联合体大小
printf("联合体大小: %lu 字节\n", sizeof(data)); // 输出20(最大成员大小)
联合体成员访问
联合体在同一时间只能存储一个成员的值:
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;
}
联合体的内存布局
联合体的所有成员共享同一块内存空间:
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]: 0x12printf("m.c[0]: 0x%x\n", m.c[0]); // 输出0x78(小端序)return 0;
}
联合体嵌套
联合体可以嵌套在结构体中,反之亦然:
// 嵌套联合体的结构体
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) |
---|---|---|
内存布局 | 成员依次排列,可能有内存对齐 | 所有成员共享同一块内存空间 |
大小计算 | 所有成员大小之和(考虑对齐) | 最大成员的大小 |
成员访问 | 所有成员可同时访问 | 同一时间只能访问一个成员 |
数据存储 | 每个成员存储独立的数据 | 所有成员共享相同的数据存储空间 |
内存占用 | 通常较大 | 通常较小 |
适用场景 | 组合不同类型的数据 | 节省内存,处理变体数据 |
实际应用示例
结构体应用示例:学生信息管理
#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;
}
联合体应用示例:变体数据类型
#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语言中两种重要的自定义数据类型,它们各自有独特的内存布局和应用场景:
- 结构体用于组合不同类型的数据,每个成员有独立的内存空间
- 联合体用于节省内存,所有成员共享同一块内存空间
- 结构体适合需要同时存储多个相关数据的场景
- 联合体适合处理变体数据或需要在不同类型间共享位模式的场景
理解结构体和联合体的区别和联系,能够帮助程序员设计更高效、更灵活的数据结构,满足不同的编程需求。