自定义类型讲解

简介: 自定义类型讲解

💕痛苦难道是白忍受的吗?💕

作者:Mylvzi

文章主要内容:自定义类型讲解

一.结构体

定义:

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

结构体:多组不同类型元素的集合-->管理多组不同类型数据的集合体,结构体中的数据也叫做结构体成员。

例如:管理学生的基本信息,需要的数据有学生的年龄,性别,身高等等

结构体关键字:struct

结构体的声明:

1. struct Stu//创建了一个结构体类型-->struct Stu-->整体是一种数据类型
2. {
3.  int age;
4.  float height;
5.  char name[20];
6. }s1,s2;//可直接在末尾添加你所需要的变量名
7. 
8. struct Stu s1, s2;//使用类型创建变量  类型+变量名  int a;

一种特殊的声明:匿名声明(忽略掉tag标签)

1. //特殊的声明-->匿名声明-->不告诉你具体名字
2. struct {
3.  int a;
4.  float b;
5. }x;//匿名定义了一个结构体变量x
6. //缺点:只能使用一次,无法对其修改
7. //优点:安全性高
8. 
9. //注意:未知tag,保证了其使用的唯一性
10. struct
11. {
12.   int a;
13.   char c;
14.   float f;
15. }x;
16. 
17. struct
18. {
19.   int a;
20.   char c;
21.   float f;
22. }* p;
23. 
24. int main()
25. {
26.   p = &x;//err
27.   //尽管成员列表相同,但都是匿名结构体变量,未知类型,会发生类型转换报错
28.   return 0;
29. }

结构体的自引用:

//结构体的成员列表不能存在一个类型和该结构体一样的结构体
//套娃是非法的;无法计算具体的大小

但可以有和原结构体类型相同的结构体指针变量,指向下一个结构体;(链表中常使用)

1. 
2. //通过结构体访问下一个结构体
3. struct Node
4. {
5.  int data;
6.  struct Node next;//err
7.  //sizeof(struct Node)是多少?无法计算
8. };
9. 
10. //改进
11. struct Node
12. {
13.   int data;
14.   struct Node* next;//存放下一个结构体的地址
15. };
16. 
17. int main()
18. {
19.   printf("%zd\n", sizeof(struct Node));
20.   return 0;
21. }
22. 
23. //错误的命名方式
24. typedef struct
25. {
26.   int data;
27.   Node* next;
28. }Node;
29. //先typedef为Node后才能使用Node,不能直接在成员列表内使用
30. 
31. typedef struct Node
32. {
33. int data;
34. struct Node* next;
35. }Node;

尽量不要使用匿名的方式声明结构体,可能声明错误;

结构体定义和初始化:

注意:使用.操作符初始化结构体时,可以不按照顺序初始化;否则,一定要严格按照结构体成员顺序进行初始化

1. struct SN
2. {
3.  char c;
4.  int i;
5. }sn1 = { 'q', 100 }, sn2 = {.i=200, .c='w'};//全局变量

结构体的内存对齐(重要):

先来计算两个结构体的大小:

再来看成员相较于结构体初始地址的偏移量(利用到offsetof宏)

 

通过以上两个现象,我们知道,结构体成员在内存中存储时并不是连续存储的,且其大小也不能简单的通过成员大小加和的方式得到;实际上,结构体在内存中的存储以及其大小是有一定的规则,这个规则叫做结构体内存对齐

结构体内存对齐规则:

1.结构体第一个成员的起始地址总是位于结构体偏移量为0的地址处;

2.从第二个成员开始,剩下的成员在内存中存放时要对齐到其对齐数的整数倍处;

对齐数:默认对齐数和成员自身大小的较小值,vs的默认对齐数8,Linux中无默认对齐数,对齐数是成员大小本身

3.结构体内存大小:必须是最大对齐数的整数倍

4.嵌套结构体:如果一个结构体嵌套了一个结构体,嵌套的结构体在内存对齐时对齐到其最大对齐数的整数倍处,整个结构体的内存大小是最大对齐数的整数倍(含嵌套的结构体的对齐数)

利用内存对齐规则分析上述两个结构体的内存分布及内存大小:

为什么要进行内存对齐呢?

有两个原因:
1.平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。硬件不同,读取数据的方式不同,读取到的内容也就不同,通过内存对齐可以实现跨硬件读取数据;

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

总而言之,结构体内存对齐是一种拿空间换时间的做法,尽管浪费掉了一些内存空间,但我们访问数据的速度大大提升;

但是,我们也可以做到结构体空间的最优化-->将内存空间小的数据集中在一起,比如s1,s2

修改默认对齐数:

#pragma预处理指令

1. #pragma pack(16)//修改默认对齐数为16
2. struct Stu
3. {
4.  int i;
5.  char c1;
6.  char c2;
7. };
8. #pragma pack()//恢复默认对齐数

结构体传参:

传递结构体时,尽量传递结构体地址(使用结构体指针接收)

1. struct S
2. {
3.  int data[1000];
4.  int num;
5. };
6. 
7. void print1(struct S p)//传递结构体本身
8. {
9.  printf("%d\n", p.num);//形参是实参的临时拷贝,传递结构体本身会重新开辟一块儿内存空间
10. }
11. 
12. void print2(struct S* p)//传递结构体地址  //如果不希望p所指向的内容被改变,添加const修饰
13. {                                       //const struct S* p
14.   printf("%d\n", p->num);
15. }
16. //print2效率更高,减少了空间的开辟。提高效率;
17. int main()
18. {
19.   struct S s1;
20.   print1(s1);
21.   print2(&s1);
22.   return 0;
23. }

二.位段

位段的定义及内存分配

讲完结构体就需要讲一下结构体实现位段的能力

位段-->给成员分配具体大小内存空间的结构体

注意:

1.声明和结构体相同

2.成员必须是int,unsigned int,char类型

3.设计格式:成员类型 成员名:具体大小

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

观察下列位段在内存中的分配:

位段的跨平台问题:

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

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

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

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

位段的应用:

不难发现,位段是一种对空间极度优化的结构体,往往应用到空间利用率高的数据存储,或者大量开关信息的存储;

举例:信息的传递(ip数据包的传递)

三.枚举

定义:

枚举也是一种存储数据的自定义类型,顾名思义,如果取值能够被一一枚举,那么我们就可以使用枚举来存储相应的数据

枚举关键字:enum

1. enum Sex//性别
2. {
3.  MALE,
4.  FEMALE,
5.  SECRET
6. };
7. enum Color//颜色
8. {
9.  //都有默认取值
10.   RED,//0
11.   GREEN,//1 GREEN = 5;也可以人为赋值
12.   BLUE//2
13. };
14. //enum Color,enum Sex都是枚举类型

优点:

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

       更加规范,代码量少;便于维护

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

       只能使用枚举类型的数据进行赋值,否则会报错

3. 便于调试

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

1. enum Color//颜色
2. {
3.  RED = 1,
4.  GREEN = 2,
5.  BLUE = 4
6. };
7. enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
8. clr = 5;//ERR

枚举的应用(来源于chatgpt)

四.联合体(共用体)

定义:

联合体也是一种存储多种数据的自定义类型,其特点是所有的成员共用同一块内存(所以也叫共用体)

联合体关键字:union

1. //联合体
2. union Un
3. {
4.  int a;
5.  char b;
6. };
7. int main()
8. {
9.  printf("%d\n", sizeof(Un));//4
10.   return 0;
11. }

特点:

所有成员共用同一块儿空间

联合体大小计算:

1.内存大小至少是最大成员的内存大小(必须能够保存该数据)

2.且最终大小要是最大对齐数的整数倍

利用联合体检验当前计算机存储方式(大小端的检验)

1. //大小端的检验
2. //之前写法
3. //检查首地址元素的值
4. int check_sys(int* p)
5. {
6.  int b = *(char*)p;//得到首地址
7.  return b;
8. }
9. int main()
10. {
11.   int a = 1;
12.   int ret = check_sys(&a);
13.   if (ret == 1)
14.     printf("小端");
15.   else
16.     printf("大端");
17.   return 0;
18. }
19. 
20. int check_sys()
21. {
22.   union
23.   {
24.     int i;
25.     char c;
26.   }un = {un.i=1};
27.   return un.c;
28. }
29. int main()
30. {
31.   int ret = check_sys();
32.   if (ret == 1)
33.     printf("小端");
34.   else
35.     printf("大端");
36.   return 0;
37. }

五.总结

 这篇文章详细介绍了四种自定义类型,结构体(struct),位段(指定成员大小的结构体),枚举类型(enum),联合体(union);要掌握他们的声明,定义方式,在内存中的存储方式,以及内存大小的计算;尽管在目前学习中,自定义类型的应用场景较少,但在之后的学习中会大量使用

目录
相关文章
|
6月前
|
存储 编译器 Linux
自定义类型详解(1)
自定义类型详解(1)
51 5
|
6月前
自定义类型详解(2)
自定义类型详解(2)
47 1
|
7月前
|
编译器 Linux C++
自定义类型详解
自定义类型详解
|
7月前
|
编译器 C++
自定义类型
自定义类型
|
存储 算法 程序员
自定义类型总结
自定义类型总结
80 0
|
编译器 C++
自定义类型超详细解答!!!!!(上)
自定义类型超详细解答!!!!!
|
存储
自定义类型超详细解答!!!!!(下)
自定义类型超详细解答!!!!!
自定义类型枚举(上)
自定义类型枚举
35 0
|
C语言 C++
自定义类型枚举(下)
自定义类型枚举
40 0
|
编译器 Linux C语言
自定义类型详解(上)
自定义类型详解(上)
自定义类型详解(上)