C语言中如何高效运用结构体、枚举和联合体实现复杂数据管理?

2026-04-12 09:241阅读0评论SEO问题
  • 内容介绍
  • 文章标签
  • 相关推荐

本文共计2677个文字,预计阅读时间需要11分钟。

C语言中如何高效运用结构体、枚举和联合体实现复杂数据管理?

目录

一、匿名结构体

二、结构体的自引用

1.声明时不要自引用

2.重命名时不能使用重命名

三、结构体的内存对齐规则

1.结构体内存计算

2.结构体嵌套

3.通过调整结构体

目录
  • 一、匿名结构体
  • 二、结构体的自引用
    • 1、声明时不要自己引用自己
    • 2、结构体重命名时不能使用重命名
  • 三、结构体内存对齐规则
    • 1、结构体内存计算
    • 2、结构体嵌套
    • 3、通过调整结构体成员顺序,压缩内存
  • 四、存在内存对齐的原因
    • 五、修改默认对齐数
      • 六、结构体传参
        • 七、位段
          • 1、位段在内存中的存储
          • 2、位段的跨平台问题
        • 八、枚举
          • 1、枚举的定义
          • 2、枚举的优点
        • 九、联合体(共用体)
          • 1、联合体大小的计算
          • 2、使用联合体判断计算机的大小端字节序

        一、匿名结构体

        struct { char name[20]; int age; }s1;

        匿名结构体对象s1过了这一行即销毁。

        二、结构体的自引用

        1、声明时不要自己引用自己

        struct Node { int data; struct Node next;//错误的,严禁自己引用自己 }; struct Node { int data; struct Node* next;//正确的引用方式 };

        2、结构体重命名时不能使用重命名

        typedef struct { int data; Node* next;//错误的,不要再重命名中使用重命名 }Node; typedef struct Node { int data; struct Node* next;//正确的 }Node;

        博主在学数据结构的时候踩过这个坑,在结构体重命名的时候成员变量的类型就使用了重命名,导致整个程序不认识这个成员变量的类型(但是vs在typedef这里不报错,而是在每个使用这个类型的地方报错!!!)。后来把这个成员变量的类型修改为重命名之前的类型,整个程序就可以运行了。(如上图的正确写法)

        三、结构体内存对齐规则

        第一个成员在与结构体变量偏移量为0的地址处。

        后续成员变量要放到各自的对齐数的倍数上。对齐数 = 编译器默认对齐数与该成员类型大小的较小值。(vs中默认对齐数是8,gcc没有默认对齐数)

        结构体最终大小为最大对齐数的整数倍。

        如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

        1、结构体内存计算

        struct S1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct S1)); return 0; }

        C语言中如何高效运用结构体、枚举和联合体实现复杂数据管理?

        char c1在结构体变量的零偏移量处分配内存

        int i的对齐数为4,所以跳过3个字节,在4的整数倍地址处分配内存

        char c2的对齐数为1,使用下一个字节空间即可

        目前已使用9字节

        由于该结构体中所有成员变量中最大的成员类型大小为4字节,所以最大内存对齐数为4字节,结构体总大小为最大对齐数的整数倍。所以该结构体内存为12字节。

        可以使用宏offsetof来观察结构体成员在内存中的偏移量:

        2、结构体嵌套

        struct S3//16 { double d; char c; int i; }; struct S4//32 { char c1; struct S3 s3; double d; };

        char c1在结构体变量的零偏移量处分配内存

        struct S3 s3按照其最大内存对齐数(此处为8)进行对齐

        double d按照其最大内存对齐数(8)进行对齐

        S4的最大内存对齐数为8,所以结构体的最终大小为32

        3、通过调整结构体成员顺序,压缩内存

        通过上述例子可以发现,结构体成员之间有很大的空间浪费,哪怕是拥有相同结构体成员的两个结构体类型,其在内存中所占据的空间也不相同,所以为了空间的节省,在不影响数据结构的情况下,有目的的把字节占用小的成员变量放在一起,达到节省空间的目的。

        四、存在内存对齐的原因

        1. 平台原因(移植原因)

        不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

        2. 性能原因(空间换时间)

        数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

        五、修改默认对齐数

        #pragma pack(2)//把默认对齐数改成2 struct S { char c1; int i; short c2; }; #pragma pack()//恢复默认对齐数为8 int main() { printf("%d\n", sizeof(struct S)); return 0; } #pragma pack(num)修改默认对齐数,该结构体的内存大小由12字节降低为8字节。

        默认对齐数尽量为2的次方。

        六、结构体传参

        结构体传参要传地址。

        传址调用优于传值调用的原因是地址占4/8个字节。

        但是传值调用参数需要压栈,当结构体过大时,参数压栈的系统开销较大。

        七、位段

        位段是在结构体中实现的。

        位段的成员可以是 int、unsigned int、signed int或者是char类型

        位段的空间增长方式为每次增长4个字节(int)或1个字节(char)

        位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

        1、位段在内存中的存储

        1.1位段中char类型的存储方式(vs中舍弃剩余空间)

        struct S//占用3个字节 { char _a : 3; char _b : 4; char _c : 5; char _d : 4; }; int main() { printf("%d\n", sizeof(struct S)); struct S s= { 0 }; s._a = 10;//1010,截断为010 s._b = 12;//1100 s._c = 3;//0011 s._d = 4;//0100 return 0; }

        通过调用内存发现,&s中存储的16进制数字为62 03 04,那么可以发现s在内存中的存储方式如下图:

        在vs环境中,char成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将剩余的比特位舍弃。

        1.2位段中int类型的存储方式(vs中利用剩余空间)

        struct A//占4个字节 { int a : 2; int b : 3; int c : 4; }; int main() { struct A s = { 0 }; s.a = 12;//1100,截断为00 s.b = 13;//1101,截断为101 s.c = 14;//1110 return 0; }

        通过调用内存发现,&s中存储的16进制数字为d4 01 00 00,那么可以发现s在内存中的存储方式如下图:

        在vs环境中,int成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将继续存储,存不下的二进制位将存放至下一个字节的右侧。

        注意:位段冒号后面的数字只能小于等于类型大小(例如char a:9是错误的)

        2、位段的跨平台问题

        1.int 位段被当成有符号数还是无符号数是不确定的。

        2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

        3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

        4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

        八、枚举

        1、枚举的定义

        enum color { RED,//枚举常量 BLUE, YELLOW };

        不赋值那么默认从0开始,后续枚举成员的值递增1

        enum color { RED=1, BLUE, YELLOW };

        只需要对第一个成员进行赋值,后续枚举成员的值递增1

        在写枚举成员的时候建议全大写,博主在写通讯录枚举了exit,使用时vs提示该命名和exit函数冲突。

        2、枚举的优点

        增加代码的可读性和可维护性

        枚举使用时有类型检查,#define定义的标识符没有

        防止了命名污染(封装)

        便于调试(#define定义宏在预处理时是直接替换)

        使用方便,一次可以定义多个常量

        九、联合体(共用体)

        联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。

        1、联合体大小的计算

        #include <stdio.h> union un { char arr[5]; int a; }u; int main() { printf("%d", sizeof(u));//8 return 0; }

        联合体的大小至少是最大成员的大小。

        最大内存对齐数的整数倍要大于等于最大成员的大小。

        (这里最大成员是arr,占5个字节,最大内存对齐数是4,所以需要为祖国联合体开辟8个字节空间)

        2、使用联合体判断计算机的大小端字节序

        #include <stdio.h> union un { int m; char n; }u; int check_sys() { u.m = 1; return u.n; } int main() { int a = check_sys(); if (a == 1) printf("小端存储\n"); else printf("大端存储\n"); return 0; }

        以上就是详解C语言结构体,枚举,联合体的使用的详细内容,更多关于C语言 结构体 枚举 联合体的资料请关注自由互联其它相关文章!

        标签:使用目录

        本文共计2677个文字,预计阅读时间需要11分钟。

        C语言中如何高效运用结构体、枚举和联合体实现复杂数据管理?

        目录

        一、匿名结构体

        二、结构体的自引用

        1.声明时不要自引用

        2.重命名时不能使用重命名

        三、结构体的内存对齐规则

        1.结构体内存计算

        2.结构体嵌套

        3.通过调整结构体

        目录
        • 一、匿名结构体
        • 二、结构体的自引用
          • 1、声明时不要自己引用自己
          • 2、结构体重命名时不能使用重命名
        • 三、结构体内存对齐规则
          • 1、结构体内存计算
          • 2、结构体嵌套
          • 3、通过调整结构体成员顺序,压缩内存
        • 四、存在内存对齐的原因
          • 五、修改默认对齐数
            • 六、结构体传参
              • 七、位段
                • 1、位段在内存中的存储
                • 2、位段的跨平台问题
              • 八、枚举
                • 1、枚举的定义
                • 2、枚举的优点
              • 九、联合体(共用体)
                • 1、联合体大小的计算
                • 2、使用联合体判断计算机的大小端字节序

              一、匿名结构体

              struct { char name[20]; int age; }s1;

              匿名结构体对象s1过了这一行即销毁。

              二、结构体的自引用

              1、声明时不要自己引用自己

              struct Node { int data; struct Node next;//错误的,严禁自己引用自己 }; struct Node { int data; struct Node* next;//正确的引用方式 };

              2、结构体重命名时不能使用重命名

              typedef struct { int data; Node* next;//错误的,不要再重命名中使用重命名 }Node; typedef struct Node { int data; struct Node* next;//正确的 }Node;

              博主在学数据结构的时候踩过这个坑,在结构体重命名的时候成员变量的类型就使用了重命名,导致整个程序不认识这个成员变量的类型(但是vs在typedef这里不报错,而是在每个使用这个类型的地方报错!!!)。后来把这个成员变量的类型修改为重命名之前的类型,整个程序就可以运行了。(如上图的正确写法)

              三、结构体内存对齐规则

              第一个成员在与结构体变量偏移量为0的地址处。

              后续成员变量要放到各自的对齐数的倍数上。对齐数 = 编译器默认对齐数与该成员类型大小的较小值。(vs中默认对齐数是8,gcc没有默认对齐数)

              结构体最终大小为最大对齐数的整数倍。

              如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

              1、结构体内存计算

              struct S1 { char c1; int i; char c2; }; int main() { printf("%d\n", sizeof(struct S1)); return 0; }

              C语言中如何高效运用结构体、枚举和联合体实现复杂数据管理?

              char c1在结构体变量的零偏移量处分配内存

              int i的对齐数为4,所以跳过3个字节,在4的整数倍地址处分配内存

              char c2的对齐数为1,使用下一个字节空间即可

              目前已使用9字节

              由于该结构体中所有成员变量中最大的成员类型大小为4字节,所以最大内存对齐数为4字节,结构体总大小为最大对齐数的整数倍。所以该结构体内存为12字节。

              可以使用宏offsetof来观察结构体成员在内存中的偏移量:

              2、结构体嵌套

              struct S3//16 { double d; char c; int i; }; struct S4//32 { char c1; struct S3 s3; double d; };

              char c1在结构体变量的零偏移量处分配内存

              struct S3 s3按照其最大内存对齐数(此处为8)进行对齐

              double d按照其最大内存对齐数(8)进行对齐

              S4的最大内存对齐数为8,所以结构体的最终大小为32

              3、通过调整结构体成员顺序,压缩内存

              通过上述例子可以发现,结构体成员之间有很大的空间浪费,哪怕是拥有相同结构体成员的两个结构体类型,其在内存中所占据的空间也不相同,所以为了空间的节省,在不影响数据结构的情况下,有目的的把字节占用小的成员变量放在一起,达到节省空间的目的。

              四、存在内存对齐的原因

              1. 平台原因(移植原因)

              不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。

              2. 性能原因(空间换时间)

              数据结构(尤其是栈)应该尽可能地在自然边界上对齐。 原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

              五、修改默认对齐数

              #pragma pack(2)//把默认对齐数改成2 struct S { char c1; int i; short c2; }; #pragma pack()//恢复默认对齐数为8 int main() { printf("%d\n", sizeof(struct S)); return 0; } #pragma pack(num)修改默认对齐数,该结构体的内存大小由12字节降低为8字节。

              默认对齐数尽量为2的次方。

              六、结构体传参

              结构体传参要传地址。

              传址调用优于传值调用的原因是地址占4/8个字节。

              但是传值调用参数需要压栈,当结构体过大时,参数压栈的系统开销较大。

              七、位段

              位段是在结构体中实现的。

              位段的成员可以是 int、unsigned int、signed int或者是char类型

              位段的空间增长方式为每次增长4个字节(int)或1个字节(char)

              位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

              1、位段在内存中的存储

              1.1位段中char类型的存储方式(vs中舍弃剩余空间)

              struct S//占用3个字节 { char _a : 3; char _b : 4; char _c : 5; char _d : 4; }; int main() { printf("%d\n", sizeof(struct S)); struct S s= { 0 }; s._a = 10;//1010,截断为010 s._b = 12;//1100 s._c = 3;//0011 s._d = 4;//0100 return 0; }

              通过调用内存发现,&s中存储的16进制数字为62 03 04,那么可以发现s在内存中的存储方式如下图:

              在vs环境中,char成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将剩余的比特位舍弃。

              1.2位段中int类型的存储方式(vs中利用剩余空间)

              struct A//占4个字节 { int a : 2; int b : 3; int c : 4; }; int main() { struct A s = { 0 }; s.a = 12;//1100,截断为00 s.b = 13;//1101,截断为101 s.c = 14;//1110 return 0; }

              通过调用内存发现,&s中存储的16进制数字为d4 01 00 00,那么可以发现s在内存中的存储方式如下图:

              在vs环境中,int成员变量在单个字节中是倒着存储的(有截断先发生截断),当该字节中剩余的比特位不足以存放下一个完整的成员变量时,会将继续存储,存不下的二进制位将存放至下一个字节的右侧。

              注意:位段冒号后面的数字只能小于等于类型大小(例如char a:9是错误的)

              2、位段的跨平台问题

              1.int 位段被当成有符号数还是无符号数是不确定的。

              2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机器会出问题。

              3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。

              4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是舍弃剩余的位还是利用,这是不确定的。

              八、枚举

              1、枚举的定义

              enum color { RED,//枚举常量 BLUE, YELLOW };

              不赋值那么默认从0开始,后续枚举成员的值递增1

              enum color { RED=1, BLUE, YELLOW };

              只需要对第一个成员进行赋值,后续枚举成员的值递增1

              在写枚举成员的时候建议全大写,博主在写通讯录枚举了exit,使用时vs提示该命名和exit函数冲突。

              2、枚举的优点

              增加代码的可读性和可维护性

              枚举使用时有类型检查,#define定义的标识符没有

              防止了命名污染(封装)

              便于调试(#define定义宏在预处理时是直接替换)

              使用方便,一次可以定义多个常量

              九、联合体(共用体)

              联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。

              1、联合体大小的计算

              #include <stdio.h> union un { char arr[5]; int a; }u; int main() { printf("%d", sizeof(u));//8 return 0; }

              联合体的大小至少是最大成员的大小。

              最大内存对齐数的整数倍要大于等于最大成员的大小。

              (这里最大成员是arr,占5个字节,最大内存对齐数是4,所以需要为祖国联合体开辟8个字节空间)

              2、使用联合体判断计算机的大小端字节序

              #include <stdio.h> union un { int m; char n; }u; int check_sys() { u.m = 1; return u.n; } int main() { int a = check_sys(); if (a == 1) printf("小端存储\n"); else printf("大端存储\n"); return 0; }

              以上就是详解C语言结构体,枚举,联合体的使用的详细内容,更多关于C语言 结构体 枚举 联合体的资料请关注自由互联其它相关文章!

              标签:使用目录