C语言自定义类型详解:结构体、位段、枚举与联合
C语言的自定义类型是构建复杂数据结构的基础,主要包括结构体、位段、枚举和联合。掌握这些类型的定义、初始化及内存布局(如内存对齐),对于编写高效、兼容的C/C++代码至关重要。
一、结构体
结构体(struct)是一种将不同类型变量聚合在一起的自定义数据类型,允许开发者将相关数据打包处理。
第1步:结构体的声明与变量定义
使用 struct 关键字声明结构体类型,需包含类型标签和成员列表。定义时必须保留末尾的分号。
struct Book
{
char name[20]; //名字
int Price; //价格
char Writer[5];//作者
char Time[20]; //日期
}; //注意分号不能丢基于声明的类型,可以在局部或全局作用域中定义变量:
- 局部变量:在函数内部定义,仅在该函数作用域内有效。
- 全局变量:在结构体定义后直接附加变量名,或在文件作用域声明,可被多个函数访问。
// 全局定义
struct Book
{
char name[20];
int Price;
char Writer[5];
char Time[20];
}B3,B4,B5;
struct Book B2;
int main()
{
struct Book B1; // B1为局部结构体变量
return 0;
}第2步:特殊声明与自引用
不完全声明(匿名结构体):没有结构体标签的结构体,必须紧跟着定义变量,且后续无法复用该类型。
// 匿名结构体类型--没有结构体标签
struct
{
int a;
char b;
float c;
}x;
// ❌ 错误写法:编译器视其为不同类型,不兼容
struct
{
int a;
char b;
float c;
}* ps;
int main()
{
ps=&x;
return 0;
}
因此,不完全声明很少使用。结构体的自引用常用于实现链表等动态数据结构,即指针成员指向同类型的下一个节点:
struct Node
{
int data;
struct Node* next;
};
第3步:结构体变量的初始化
初始化时使用花括号 {} 按顺序赋值。若存在嵌套结构体,内层结构体也需用花括号包裹。
// 普通初始化
int main()
{
struct Book B1={"三脚猫", 50, "阿里", "20081001"};
return 0;
}
// 嵌套结构体初始化
struct Data
{
int a;
char b[6];
};
struct Book
{
struct Data D; // 嵌套成员
char name[20];
int Price;
char Writer[5];
char Time[20];
};
int main()
{
struct Book B1={{4,"haha"},"三脚猫", 50, "阿里", "20081001"};
return 0;
}第4步:结构体内存对齐
内存对齐旨在提升CPU读取数据的效率,以空间换取时间。计算结构体大小时遵循以下规则:
- 起始位置:首成员偏移量为0。
- 成员对齐:其他成员地址必须是其自身大小与编译器默认对齐数(VS中为8)的较小值的整数倍。
- 总大小:结构体总大小必须是最大对齐数的整数倍。
- 嵌套结构体:嵌套结构体按其内部最大对齐数进行对齐,整体大小取所有最大对齐数的公约数倍数。
以下示例展示了成员排列对大小影响:
#include <stdio.h>
struct S1
{
char c1; // 1字节 + 3字节 padding
int i; // 4字节
char c2; // 1字节 + 3字节 padding
};
// sizeof(S1) = 1+3+4+1+3 = 12
struct S2
{
char c1;
char c2; // 2字节连续
int i; // 4字节 (满足对齐)
};
// sizeof(S2) = 1+1+2(padding)+4 = 8
int main()
{
printf("%d\n", sizeof(struct S1));
printf("%d\n", sizeof(struct S2));
return 0;
}
对于嵌套情况,S2包含S1:
struct S1 { char c1; int i; char c2; };
struct S2
{
char c1;
char c2;
struct S1 s1; // S1最大对齐数为4,需从4的倍数处开始
int i;
};
int main(){
// S2分析:c1(1)+c2(1)+padding(2)=4; s1占12字节; i占4字节+4填充=8; total=4+12+8=24
printf("%d\n", sizeof(struct S2));
}
第5步:修改默认对齐数
可通过 #pragma pack(n) 指令修改编译器的默认对齐数。设为1时相当于取消对齐。
#include <stdio.h>
#pragma pack(1)
struct S1
{
char c1;
int i;
char c2;
};
int main()
{
// 无padding,总大小 1+4+1=6
printf("%d\n", sizeof(struct S1));
return 0;
}
第6步:结构体传参优化
当结构体体积较大时,推荐使用传址调用。传值调用会复制整个结构体到栈上,造成时间和空间开销;传地址仅传递指针,性能更高。
struct S { int data[1000]; int num; };
void print1(struct S s) { ... } // 压栈开销大
void print2(struct S* ps) { ... } // 推荐,效率高二、位段
位段(Bit Field)允许在结构体成员后指定位数,主要用于节省存储空间。
struct S
{
int _a:2; // 占用2位
int _b:5; // 占用5位
int _c:20;
int _d:25;
};位段的大小同样遵循内存对齐原则。示例中S的大小为8字节。
若初始值超出指定位数,会发生截断。跨平台问题:位段的符号性(有符号/无符号)、位分配方向(从左到右/从右到左)以及剩余位的处理方式,均取决于具体编译器实现,因此在需要高兼容性时应避免使用位段。
三、枚举
枚举(enum)用于定义一组命名的整型常量,默认为从0开始的递增序列。
enum Day
{
Mon, Tues, Wed, Thur, Fri, Sat, Sun
};
int main()
{
enum Day d=Mon;
return 0;
}
枚举相比宏的优势在于:类型检查更严格(C++环境下严禁隐式转换,如图片所示的报错差异),能有效防止命名污染并提高可读性。
四、联合
联合(union)的所有成员共用同一块内存空间,其大小至少等于最大成员的大小,且需对齐到最大对齐数的整数倍。
union Un
{
int i;
char c;
};
// 验证内存重叠
int main()
{
printf("%d\n", &(un.i));
printf("%d\n", &(un.c));
return 0;
}
联合大小的计算案例:
union Un1 { char c[5]; int i; }; // 最大成员5,对齐数4 -> 大小为8
union Un2 { short c[7]; int i; }; // 最大成员14,对齐数2 -> 大小为14
int main() {
printf("%d\n", sizeof(union Un1));
printf("%d\n", sizeof(union Un2));
}
以上就是关于C语言自定义类型(结构体、位段、枚举、联合)的全面理解与操作指南,更多关于C语言底层内存管理的数据结构资料请关注本站其它相关文章!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













