• C进阶-自定义类型:结构体、枚举、联合


    本章重点:

    结构体:

    结构体类型的声明
    结构的自引用
    结构体变量的定义和初始化
    结构体内存对齐
    结构体传参
    结构体实现位段(位段的填充&可移植性)

     1 结构体的声明

     1.1 结构的基础知识

    结构是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量。

    数组:一组相同类型元素的集合

    1.2 结构的声明
    1. // struct tag
    2. // {
    3. // member-list;
    4. // }variable-list; //变量列表
    5. struct Stu
    6. {
    7. int age;
    8. char name[20];
    9. char id[20];
    10. };
    11. struct Stu s1,s2,s3;
    1.3 特殊的声明 

    在声明结构的时候吗可以不完全声明

    1. //特殊声明
    2. struct
    3. {
    4. int a;
    5. char c;
    6. float f;
    7. } x;
    8. struct
    9. {
    10. int a;
    11. char c;
    12. float f;
    13. }* p;
    14. int main()
    15. {
    16. //p = &x; //err
    17. return 0;
    18. }
    1.4 结构体的自引用

     数据结构:描述的是数据在内存中的组织结构

    线性数据结构: 假设要存储:1,2,3,4,5      顺序表

     

    1. // struct Node
    2. // {
    3. // int data;
    4. // struct Node* next;
    5. // };
    6. // typedef struct //err
    7. // {
    8. // int data;
    9. // Node* next;
    10. // } Node;
    11. typedef struct Node
    12. {
    13. int data;
    14. struct Node* next;
    15. } Node;
    16. int main()
    17. {
    18. Node n = {0};
    19. return 0;
    20. }
    1.5 结构体变量的定义和初始化 
    1. struct SN
    2. {
    3. //char c[20];
    4. char c;
    5. int i;
    6. }sn1 = {'q',100},sn2 = {.i=50,.c='w'}; //全局变量
    7. struct S
    8. {
    9. double d;
    10. struct SN sn;
    11. int arr[10];
    12. };
    13. int main()
    14. {
    15. //struct SN sn3,sn4; //局部变量
    16. //printf("%c %d\n",sn2.c,sn2.i);
    17. //printf("%c %d\n",sn1.c,sn1.i);
    18. struct S s = {3.14,{'a',99},{1,2,3,4}};
    19. printf("%lf %c %d\n",s.d,s.sn.c,s.sn.i);
    20. int i = 0;
    21. for (i = 0; i< 10;i++)
    22. {
    23. printf("%d ",s.arr[i]);
    24. }
    25. return 0;
    26. }
     1.6 结构体内存对齐

    结构体内存对齐的规则:

    1.结构体的第一个成员永远放在相较于结构体变量起始位置的偏移量为0的位置

    2.从第二个成员开始,往后的每个成员都要对齐到某个对齐数的整数倍处。

    对齐数:结构体成员自身的大小和默认对齐数的较小值

    VS上默认对齐数是8

    gcc 没有默认对齐数,对齐数就是结构体成员的自身大小。

    3.结构体的总大小,必须上最大对齐数的整数倍

    最大对齐数是:是所有成员的对齐数中的最大值

    1. // struct S1
    2. // {
    3. // char c1;
    4. // int i;
    5. // char c2;
    6. // };
    7. // struct S2
    8. // {
    9. // int i;
    10. // char c1;
    11. // char c2;
    12. // };
    13. // int main()
    14. // {
    15. // printf("%d\n",sizeof(struct S1)); //12
    16. // printf("%d\n",sizeof(struct S2));// 8
    17. // printf("%d\n",offsetof(struct S1,c1));//可以计算结构体成员相较于结构体起始位置的偏移量 //0
    18. // printf("%d\n",offsetof(struct S1,i));//4
    19. // printf("%d\n",offsetof(struct S1,c2));//8
    20. // printf("%d\n",offsetof(struct S2,i));//可以计算结构体成员相较于结构体起始位置的偏移量 //0
    21. // printf("%d\n",offsetof(struct S2,c1));//4
    22. // printf("%d\n",offsetof(struct S2,c2));//8
    23. // //通过上面的现象分析,我们发现结构成员不是按照顺序在内存中连续存放的,而是有一定的对齐规则的
    24. // return 0;
    25. // }
    26. struct S3
    27. {
    28. double d;
    29. char c;
    30. int i;
    31. };
    32. struct S4
    33. {
    34. char c1;
    35. struct S3 s3;
    36. double d;
    37. };
    38. int main()
    39. {
    40. printf("%d\n",sizeof(struct S4)); //32
    41. return 0;
    42. }

     为什么存在内存对齐?

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

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

    总结来说:结构体的内存对齐是拿空间来换取时间的做法  

     1.7 修改默认对齐数
    1. #pragma pack(1)
    2. struct S
    3. {
    4. char c1;// 1 1 1
    5. int a;// 4 1 1
    6. char c2;// 1 1 1
    7. };
    8. #pragma pack(1)
    9. int main()
    10. {
    11. printf("%d\n",sizeof(struct S));
    12. return 0;
    13. }
     1.8 结构体传参
    1. struct S
    2. {
    3. int data[100];
    4. int num;
    5. };
    6. void print1(struct S tmp)
    7. {
    8. printf("%d\n",tmp.num);
    9. }
    10. //优先使用结构体传参
    11. void print2(struct S* ps)
    12. {
    13. printf("%d\n",ps->num);
    14. }
    15. int main()
    16. {
    17. struct S s = {{1,2,3},100};
    18. print1(s);
    19. print2(&s);
    20. return 0;
    21. }

    2. 位段 

    2.1 什么是位段

    位段的声明和结构是类似的,有两个不同:

    1.    位段的成员必须是 int、unsigned int 或signed int。

    2.    位段的成员名后边有一个冒号和一个数字。

    1. struct A
    2. {
    3. int _a : 2; //二进制位
    4. int _b : 5;
    5. int _c : 10;
    6. int _d : 30;
    7. };
    8. int main()
    9. {
    10. printf("%d\n",sizeof(struct A));
    11. return 0;
    12. }
     2.2 位段的内存分配

    1.    位段的成员可以是 int unsigned int signed int或者是 char(属于整形家族)类型

    2.    位段的空间上是按照需要以4个字节( int)或者1个字节( char)的方式来开辟的。

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

    1. struct S
    2. {
    3. char a:3;
    4. char b:4;
    5. char c:5;
    6. char d:4;
    7. };
    8. int main()
    9. {
    10. struct S s = {0};
    11. s.a = 10;
    12. s.b = 12;
    13. s.c = 3;
    14. s.d = 4;
    15. printf("%d\n",sizeof(struct S));
    16. return 0;
    17. }
     2.3 位段的跨平台问题

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

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

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

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

    枚举:

    枚举顾名思义就是一一列举。 把可能的取值一一列举

    枚举类型的定义

    枚举的优点

    枚举的使用

    3.1 枚举类型的定义 
    1. enum Color
    2. {
    3. RED,
    4. GREEN = 8,
    5. BLUE
    6. };
    7. int main()
    8. {
    9. enum Color c = GREEN;
    10. printf("%d\n",RED);
    11. printf("%d\n",GREEN);
    12. printf("%d\n",BLUE);
    13. return 0;
    14. }
     3.2 枚举的优点

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

    2.    和#define定义的标识符比较枚举有类型检查,更加严谨。

    3.    防止了命名污染(封装)

    4.    便于调试

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

    3.3 枚举的使用 
    1. enum Color
    2. {
    3. RED = 1,
    4. GREEN = 8,
    5. BLUE = 4
    6. };
    7. int main()
    8. {
    9. // enum Color c = GREEN;
    10. // printf("%d\n",RED);
    11. // printf("%d\n",GREEN);
    12. // printf("%d\n",BLUE);
    13. enum Color clr = GREEN;
    14. clr = 5;
    15. return 0;
    16. }

    联合:

    联合类型的定义

    联合的特点

    联合大小的计算

     4.1 联合类型的定义

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

     

    1. union Un
    2. {
    3. char c;
    4. int i;
    5. };
    6. int main()
    7. {
    8. printf("%d\n",sizeof(union Un));
    9. union Un un = {0};
    10. printf("%p\n",&un);
    11. printf("%p\n",&un.i);
    12. printf("%p\n",&un.c);
    13. return 0;
    14. }
     4.2 联合的特点

    联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。 

    1. union Un
    2. {
    3. char c;
    4. int i;
    5. };
    6. int main()
    7. {
    8. printf("%d\n",sizeof(union Un));
    9. union Un un = {0};
    10. un.i = 0x11223344;
    11. un.c = 0x55;
    12. printf("%x\n", un.i);
    13. // printf("%p\n",&un);
    14. // printf("%p\n",&un.i);
    15. // printf("%p\n",&un.c);
    16. return 0;
    17. }
     4.3 联合的计算
    1. // union Un
    2. // {
    3. // int i;
    4. // char c;
    5. // };
    6. // int main()
    7. // {
    8. // printf("%d\n",sizeof(union Un)); //4
    9. // return 0;
    10. // }
    11. // int check_sys()
    12. // {
    13. // union
    14. // {
    15. // int i;
    16. // char c;
    17. // } un = {.i = 1};
    18. // return un.c;
    19. // }
    20. // int main()
    21. // {
    22. // int ret = check_sys();
    23. // if(ret ==1)
    24. // {
    25. // printf("小端\n");
    26. // }
    27. // else
    28. // {
    29. // printf("大端\n");
    30. // }
    31. // return 0;
    32. // }
    33. union Un1
    34. {
    35. char c[5]; //5 1 8 1
    36. int i; //4 8 4
    37. };
    38. union Un2
    39. {
    40. short c[7]; //14 2 8 2
    41. int i; //4 4 8 4
    42. };
    43. int main()
    44. {
    45. printf("%d\n",sizeof(union Un1)); //5 + 3 = 8
    46. printf("%d\n",sizeof(union Un2)); //14 + 2 = 16
    47. return 0;
    48. }

  • 相关阅读:
    网易面试总结——面试案例5~面试案例8
    C#如何批量创建类
    单节点Hadoop安装(详细操作和问题处理)
    如何做好需求收集?方法和步骤
    栈、栈帧、AAPCS的一些粗浅理解(通俗易懂)
    【云原生进阶之PaaS中间件】第二章Zookeeper-1-综述
    [力扣] 剑指 Offer 第三天 - 替换空格
    全网最牛的pytest从0到1全套教程-pytest(2)-pytest-html测试报告
    用遍历和边界的思考来总结final关键字
    中小企业如何选择进销存软件?
  • 原文地址:https://blog.csdn.net/qq_61658398/article/details/133768814