C语言——布局体,位段,罗列和连合

[复制链接]
发表于 2025-11-12 04:04:37 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

×
目次
媒介
布局体
1寄义
2语法
3匿名布局体
4布局体自引用
5布局体的界说与初始化
6内存对齐
7修改对齐数
8布局体传参
位段
1寄义
2位段的内存分配
​编辑3位段的标题 
4位段的应用
罗列
1寄义
2界说
3罗列长处
4罗列使用
连合
1寄义
2界说
3特点
4盘算
通讯录

媒介

C语言的布局体知识学好了的话对背面学习C++类对象就简单许多了
布局体

1寄义

布局体是一些值的聚集,这些值称为成员变量;每个成员变量可以是差异范例的变量。
2语法

  1. struct tag//类型名
  2. {
  3.     member-list;//成员变量
  4. }variable-list;//可在此时定义出结构体类型的变量 它是全局变量
复制代码
如果我们用布局体来形貌一个门生:
  1. struct Stu
  2. {
  3.     char name[20];//名字
  4.     int age;//年龄
  5.     char sex[5];//性别
  6.     char id[20];//学号
  7. }S1;//分号不能丢
复制代码
3匿名布局体

也就是布局体不完全声明
  1. //匿名结构体类型
  2. struct
  3. {
  4.         int a;
  5.         char b;
  6.         float c;
  7. }x;
复制代码
匿名布局体省略了tag(布局体范例名),而且它只能用一次(声明后立即界说,不能声明后界说)
能否用指针来吸收匿名布局体变量?
不能,编译器会以为这个两个范例(vs2019编都编不外,vs2022能编过!)

4布局体自引用

  1. struct Node
  2. {
  3.         int data;
  4.         struct Node next;
  5. };
  6. int main()
  7. {
  8.         printf("%d", sizeof(struct Node));
  9.         return 0;
  10. }
复制代码
上面这种引用行吗?

   编译器以为:你在内部界说了同一个布局体成员,那这个成员变量里又包罗了同一个布局体成员...这个巨细是
无穷大的
  以是正确的布局体自引用
  1. struct Node
  2. {
  3.     int data;
  4.     struct Node* next;
  5. };
复制代码
这种在链表中用的最多
别的的自引用界说:
  1. typedef struct Node
  2. {
  3.         int data;
  4.         N* next;
  5. }N;
复制代码
这种写法是错误的:布局体typedef之前内部并不知道
  1. //正确写法
  2. typedef struct Node
  3. {
  4.         int data;
  5.         struct Node* next;
  6. }N;
复制代码
5布局体的界说与初始化

  1. struct Poin
  2. {
  3.         int x;
  4.         int y;
  5. }p1 = {1,2};//可以在这里初始化
  6. struct stu
  7. {
  8.         int date;
  9.         struct Poin p;
  10. }s1;//全局变量
  11. int main()
  12. {
  13.         struct stu s2 = { 100,{20,30} };//局部变量 按顺序初始化
  14.         //struct stu s2 = { .p={20,30},.date=100 };//指定成员初始化
  15.         printf("%d %d %d\n", s2.date, s2.p.x, s2.p.y);
  16.         return 0;
  17. }
复制代码
6内存对齐

盘算布局体的巨细:这个非常紧张的一个内容!!!
来看下面的一段代码: 
  1. struct s1
  2. {
  3.         char a;
  4.         int b;
  5.         char c;
  6. };
  7. struct s2
  8. {
  9.         char a;
  10.         char c;
  11.         int b;
  12. };
  13. int main()
  14. {
  15.         printf("s1的大小:%d\n", sizeof(struct s1));
  16.         printf("s2的大小:%d", sizeof(struct s2));
  17.         return 0;
  18. }
复制代码

   int是4字节,char是1字节,两个int和一个char加起来不是6字节吗?
  差异的布局体放着类似的变量,只是次序差异,为什么巨细不一样?
  先容一个宏:offsetof,用来查察布局体变量相对于内存的偏移位置

按照打印出来的内存偏移位置举行安排成员变量的放置

一共9个字节就能竣事了,偏偏还要在浪费3个字节,前面也浪费3个字节,为什么?                    这时我们来看看内存对齐的规则:
   1. 第一个成员在与布局体变量偏移量为0的地点处。
2. 其他成员变量要对齐到对齐数整数倍的地点处。

对齐数 = 编译器默认的一个对齐数 与 该成员巨细的较小值
在VS中默认的值为8 而gcc是没有默认对齐数的
3. 布局体总巨细为最大对齐数(每个成员变量盘算出来的对齐数的最大值)的整数倍。
4. 如果嵌套告终构体的环境,嵌套的布局体对齐到自己的最大对齐数的整数倍处

  

布局体嵌套的布局体巨细:
  1. #include<stdio.h>
  2. struct S3//总大小:16
  3. {
  4.         double d;//8 8 -> 8
  5.         char c;  //1 8 -> 1
  6.         int i;   //4 8 -> 4
  7. };
  8. struct S4
  9. {
  10.         char c1;     //1  8 -> 1
  11.         struct S3 s3;//16 8 -> 8
  12.         double d;    //8  8 -> 8
  13. };
  14. int main()
  15. {
  16.         printf("%d\n", sizeof(struct S4));
  17.         return 0;
  18. }
复制代码

那为什么存在内存对齐? 
   1. 平台缘故原由(移植缘故原由):
大概硬件平台只能在某些地点处取某些特定范例的数据(否则硬件停止);
2. 性能缘故原由:
为了访问未对齐的内存,处置处罚器须要作两次内存访问;而对齐的内存访问仅须要一次访问
  这本质上就是以空间换时间来进步服从!
  

那在筹划布局体时:既要省空间,又要进步服从:
只管让字节小的成员变量放在一起
  1. #include<stdio.h>
  2. struct s1
  3. {
  4.         char c1;
  5.         int i;
  6.         char c2;
  7. };
  8. struct s2
  9. {
  10.         char c1;
  11.         char c2;
  12.         int i;
  13. };
  14. int main()
  15. {
  16.         printf("%d %d", sizeof(struct s1), sizeof(struct s2));
  17.         return 0;
  18. }
复制代码

7修改对齐数

如果对应VS的默认对齐数不满意(最好不修改!),可以自己设定对齐数
  1. #pragma pack(1)
  2. //在#pragma的范围内的对齐数是1
  3. struct s3
  4. {
  5.         char c1;
  6.         int i;
  7.         char c2;
  8. };
  9. #pragma()
  10. struct s4
  11. {
  12.         char c1;
  13.         int i;
  14.         char c2;
  15. };
  16. int main()
  17. {
  18.         printf("s3=%d s4=%d", sizeof(struct s2), sizeof(struct s3));
  19.         return 0;
  20. }
复制代码

8布局体传参

  1. struct S
  2. {
  3.         int data[1000];
  4.         int num;
  5. };
  6. struct S s = { {1,2,3,4}, 1000 };
  7. void print1(struct S s)
  8. {
  9.         printf("%d\n", s.num);
  10. }
  11. void print2(struct S* ps)
  12. {
  13.         printf("%d\n", ps->num);
  14. }
  15. int main()
  16. {
  17.         print1(s); //传结构体
  18.         print2(&s); //传地址
  19.         //那种方式更优点?
  20.         return 0;
  21. }
复制代码
  关于布局体传参,只管使用传址传参:
  由于一个布局体巨细大概很大,如果传值传参会加大体系资源的开销,导致性能的降落;而使用传址传参时数据不渴望举行修改,要把const加上!
  位段

1寄义

   位段是一种特别的自界说范例;
  对空间的使用精打细算
  a 位段的成员必须是 int、char(整形眷属);
b 位段的成员名后边有一个冒号和一个数字

  1. struct A
  2. {
  3.         int _a : 2;
  4.         int _b : 5;
  5.         int _c : 10;
  6.         int _d : 30;
  7. };
复制代码
与布局体相比巨细方面有什么差异吗?
  1. struct A//如果是结构体
  2. {
  3.         int _a : 2;
  4.         int _b : 5;
  5.         int _c : 10;
  6.         int _d : 30;
  7. };
  8. struct B
  9. {
  10.         int _a;
  11.         int _b;
  12.         int _c;
  13.         int _d;
  14. };
  15. int main()
  16. {
  17.         printf("A=%d B=%d", sizeof(struct A),sizeof(struct B));
  18.         return 0;
  19. }
复制代码

答案:位段比布局体巨细要小,更节流空间了!那这是怎么实现的呢?
2位段的内存分配

   位段背面的数字代表要分配多少个bit空间给该成员;
  位段的空间是按照须要以4个字节( int )大概1个字节( char )的方式来开发的
  1. #include<stdio.h>
  2. struct S
  3. {
  4.         char a : 3;
  5.         char b : 4;
  6.         char c : 5;
  7.         char d : 4;
  8. };
  9. int main()
  10. {
  11.         struct S s = { 0 };
  12.         s.a = 10;
  13.         s.b = 12;
  14.         s.c = 3;
  15.         s.d = 4;
  16.         return 0;
  17. }
复制代码
3位段的标题 


   1. int 位段被当成有符号数还是无符号数?
2. 位段中最大位的数量?
  (16位呆板最大16,32位呆板最大32,如果写成27,在16位呆板会出标题)
3. 位段中的成员在内存中从左向右分配,还是从右向左分配?
  (在VS时从右向左)
4. 开发的空间无法容纳下个位段时,是舍弃后别的开发空间还是使用?
  (在VS时舍弃剩余的位)
  4位段的应用

应用于各种协议(信息传到下一层时须要用到)

罗列

1寄义

罗列就是逐一枚举;在实际生存中把一个星期举行罗列:星期一,星期二,星期三...                    那么罗列怎么用代码来表现呢?
2界说

  1. enum Day//星期
  2. {
  3.         Mon = 1,
  4.         Tues,
  5.         Wed,
  6.         Thur,
  7.         Fri,
  8.         Sat,
  9.         Sun
  10. };
复制代码
  enum Day是一个罗列范例;
  罗列括号内的内容都是常量;
  罗列默认从0开始(与数组一样);但你也可以自己修改(Mon = 1)
  3罗列长处

以上对于星期的罗列:我们也可以用#define Mon 1,#define Tues 2来表现,那是不是就说罗列就可以被更换了?
   a 使用罗列方便调试(而#define在调试前就被更换成常量);
  b 罗列有范例查抄,更严谨写;
  b 使用罗列能增长代码的可读性与可维护性;
  c 罗列内界说变量不存在定名辩说(而#define大概有定名污染标题)
  4罗列使用

  1. enum Color//颜色
  2. {
  3.     RED=1,
  4.     GREEN=2,
  5.     BLUE=4
  6. };
  7. int main()
  8. {
  9.     enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异
  10.     return 0;
  11. }
复制代码
连合

1寄义

连合也是一种特别的自界说范例;
这种范例界说的变量也包罗一系列的成员,这些成员公用同一块空间(以是连合也叫共用体)。
2界说

  1. #include<stdio.h>
  2. //联合类型的声明
  3. union Un
  4. {
  5.         char c;
  6.         int i;
  7. };
  8. int main()
  9. {
  10.         union Un un;
  11.         printf("%d\n", sizeof(un));
  12.         return 0;
  13. }
复制代码

3特点

   a 由于连合范例共有内存空间,以是比别的的布局体而言节流空间;
  b 成员共有内存空间,以是界说变量时成员不能同时出现(后一个会改变前一个的数据)
  使用场景1:判断巨细端(之前通过地点方式访问判断)
  1. #include<stdio.h>
  2. int sys_call()
  3. {
  4.         //匿名联合
  5.         union
  6.         {
  7.                 char c;
  8.                 int i;
  9.         }Un;
  10.         Un.i = 1;
  11.         // 01 00 00 00 还是 00 00 00 01
  12.         //(c与i公用1字节的内存空间)
  13.         return Un.c;
  14. }
  15. int main()
  16. {
  17.         if (sys_call())
  18.         {
  19.                 printf("小端\n");
  20.         }
  21.         else
  22.         {
  23.                 printf("大端\n");
  24.         }
  25.         return 0;
  26. }
复制代码
使用场景2:筹划一个礼品清单 
  1. struct git_list
  2. {
  3.         double price;
  4.         char name[20];
  5.         char author[20];
  6.         int page;
  7.         char form[20];
  8.         double size;
  9.         char flavour[20];
  10.         char materials[20];
  11. };
  12. //利用联合体设计清晰且省空间
  13. struct git_list
  14. {
  15.         double price;
  16.         char name[20];
  17.         union
  18.         {
  19.                 char author[20];
  20.                 int page;
  21.         }book;
  22.         union
  23.         {
  24.                 char form[20];
  25.                 double size;
  26.         }mug;
  27.         union
  28.         {
  29.                 char flavour[20];
  30.                 char materials[20];
  31.         }cooike;
  32. };
复制代码
4盘算

   a 连合的巨细至少是最大成员的巨细;
b 当最大成员巨细不是最大对齐数的整数倍的时间,就要对齐到最大对齐数的整数倍;
  以是:连合巨细 != 最大成员巨细
  1. #include<stdio.h>
  2. union Un1
  3. {
  4.         char c[5];
  5.         int i;
  6. };
  7. int main()
  8. {
  9.         printf("%d\n", sizeof(union Un1));
  10.         return 0;
  11. }
复制代码
你大概会由于巨细是5(char c[5]一共是5字节,比i 4字节大),但实际上:

   最大是5没错,但它不是最大对齐数(max(char,int) = 4)的整数倍,举行要举行对齐
  通讯录

   实现一个通讯录,具备以下功能:
  1增长接洽人(名字,年事,性别,电话,地点);
  2删除接洽人的信息;
  3查找接洽人的信息;
  4更改接洽人的信息;
  5打印通讯录;
  6对通讯录举行排序;
  1. //Contact.h
  2. #define _CRT_SECURE_NO_WARNINGS 1
  3. #include<stdio.h>
  4. #include<stdlib.h>
  5. #include<string.h>
  6. #include<assert.h>
  7. #define Max_Size 100
  8. #define Max_NameSize 20
  9. #define Max_SexSize 5
  10. #define Max_CallSize 20
  11. #define Max_AddrSize 30
  12. enum
  13. {
  14.         ext,
  15.         add,
  16.         del,
  17.         check,
  18.         modify,
  19.         print,
  20.         sort,
  21. };
  22. typedef struct Mess
  23. {
  24.         char name[Max_NameSize];
  25.         int age;
  26.         char sex[Max_SexSize];
  27.         char call[Max_CallSize];
  28.         char addr[Max_AddrSize];
  29. }Mess;
  30. typedef struct Contact
  31. {
  32.         int sz;
  33.         Mess message[Max_Size];
  34. }Contact;
  35. void Add(Contact* con);
  36. void Del(Contact* con);
  37. void Check(const Contact* con);
  38. void Modify(Contact* con);
  39. void Print(const Contact* con);
  40. void Sort(Contact* con);
  41. //Contact.c
  42. #include"contact.h"
  43. void Add(Contact* con)
  44. {
  45.         assert(con);
  46.         if (con->sz == Max_Size)
  47.         {
  48.                 printf("Contact Is Full\n");
  49.                 return;
  50.         }
  51.         printf("Input Name:");
  52.         gets(con->message[con->sz].name);
  53.         //scanf("%s", &con->message[con->sz].name);
  54.     printf("Input Age:");
  55.         scanf("%d", &con->message[con->sz].age);//如果输入的是非法字符串?
  56.         getchar();//清空缓冲区的'\n'
  57.         printf("Input Sex:");
  58.         gets(con->message[con->sz].sex);
  59.         //scanf("%s", &con->message[con->sz].sex);
  60.         printf("Input Call:");
  61.         gets(con->message[con->sz].call);
  62.         //scanf("%s", &con->message[con->sz].call);
  63.         printf("Input Addr:");
  64.         gets(con->message[con->sz].addr);
  65.         //scanf("%s", &con->message[con->sz].addr);
  66.         con->sz++;
  67.         printf("Add Finish\n");
  68. }
  69. void Print(const Contact* con)
  70. {
  71.         assert(con);
  72.         if (con->sz == 0)
  73.         {
  74.                 printf("Contact Is Empty\n");
  75.                 return;
  76.         }
  77.         printf("%-20s %-5s %-5s %-11s %-30s\n", "name", "age", "sex", "call", "addr");
  78.         for (int i = 0; i < con->sz; i++)
  79.         {
  80.                 printf("%-20s %-5d %-5s %-11s %-30s\n",
  81.                         con->message[i].name, con->message[i].age,
  82.                         con->message[i].sex,con->message[i].call,
  83.                         con->message[i].addr);
  84.         }
  85. }
  86. static int Find(const Contact* con,char Name[])
  87. {
  88.         for (int i = 0; i < con->sz; i++)
  89.         {
  90.                 if (strcmp(con->message[i].name,Name)==0)//用strcmp比
  91.                 {
  92.                         printf("Find Sucess\n");
  93.                         return i;
  94.                 }
  95.         }
  96.         printf("Find Error\n");
  97.         return -1;
  98. }
  99. void Del(Contact* con)
  100. {
  101.         assert(con);
  102.         char InputName[Max_NameSize];
  103.         printf("Input Del Name:");
  104.         gets(InputName);
  105.         int DelPos = Find(con, InputName);
  106.         if (DelPos == -1) return;
  107.         for (int i = DelPos; i < con->sz - 1; i++)
  108.         {
  109.                 con->message[i] = con->message[i + 1];//后一个往前一个覆盖
  110.         }
  111.         con->sz--;
  112.         printf("Del Sucess\n");
  113. }
  114. void Check(const Contact* con)
  115. {
  116.         assert(con);
  117.         printf("Input Check Name:");
  118.         char Check_Name[Max_NameSize];
  119.         gets(Check_Name);
  120.         int pos = Find(con, Check_Name);
  121.         if (pos == -1) return;
  122.         printf("Check Result:\n");
  123.         printf("%-20s %-5s %-5s %-11s %-30s\n", "name", "age", "sex", "call", "addr");
  124.         printf("%-20s %-5d %-5s %-11s %-30s\n",
  125.                 con->message[pos].name, con->message[pos].age,
  126.                 con->message[pos].sex, con->message[pos].call,
  127.                 con->message[pos].addr);
  128. }
  129. void Modify(Contact* con)
  130. {
  131.         assert(con);
  132.         char Modify_Name[Max_NameSize];
  133.         printf("Input Modify Name:");
  134.         gets(Modify_Name);
  135.         int pos = Find(con, Modify_Name);
  136.         if (pos == -1) return;
  137.         printf("Modify Name:");
  138.         gets(con->message[pos].name);
  139.         printf("Input Modify Age:");
  140.         scanf("%d", &con->message[pos].age);
  141.         getchar();
  142.         printf("Input Modify Sex:");
  143.         gets(con->message[pos].sex);
  144.         printf("Input Modify call:");
  145.         gets(con->message[pos].call);
  146.         printf("Input Modify addr:");
  147.         gets(con->message[pos].addr);
  148.         printf("Modify Sucess\n");
  149. }
  150. int cmp(const void* a, const void* b)
  151. {
  152.         return ((*((Contact*)a)).message->age - (*((Contact*)b)).message->age);
  153. }
  154. void Sort(Contact* con)
  155. {
  156.         qsort(con, con->sz, sizeof(con->message[0]), cmp);
  157.         printf("排序完成\n");
  158. }
  159. //test.c
  160. #include"contact.h"
  161. void memu()
  162. {
  163.         printf("********************************\n");
  164.         printf("*****   0.exit    1.add    *****\n");
  165.         printf("*****   2.del     3.check  *****\n");
  166.         printf("*****   4.modify  5.print  *****\n");
  167.         printf("*****         6sort        *****\n");
  168. }
  169. int main()
  170. {
  171.         Contact con;
  172.         //对con空间进行清理
  173.         memset(&con, 0, sizeof(con));
  174.         int input;
  175.         do
  176.         {
  177.                 memu();
  178.                 printf("Please Select:");
  179.                 //防止读到字符串时发生死循环
  180.                 input = 123456;//防止下一次输入的是字符后input还是上一次的值
  181.                 scanf("%d", &input);
  182.                 getchar();//输入字符串时:清空缓冲区的'\n'
  183.                 switch (input)
  184.                 {
  185.                 case ext:
  186.                         printf("Process Exit");
  187.                         break;
  188.                 case add:
  189.                         Add(&con);
  190.                         break;
  191.                 case del:
  192.                         Del(&con);
  193.                         break;
  194.                 case check:
  195.                         Check(&con);
  196.                         break;
  197.                 case modify:
  198.                         Modify(&con);
  199.                         break;
  200.                 case print:
  201.                         Print(&con);
  202.                         break;
  203.                 case sort:
  204.                         Sort(&con);
  205.                         break;
  206.                 default:
  207.                         printf("Select Error,Please Try Again\n");
  208.                         break;
  209.                 }
  210.         }while(input);
  211.         return 0;
  212. }
复制代码
  但上面的通讯录无法做到:
  通讯录满了无法再存信息(学习动态内存管理后办理);
  关闭步调时通讯录信息还存在(学习完文件利用后办理);
    以上便是全部内容,有标题接待在批评区指正,感谢观看!
回复

使用道具 举报

登录后关闭弹窗

登录参与点评抽奖  加入IT实名职场社区
去登录
快速回复 返回顶部 返回列表