对自定义类型(结构体,枚举,联合)的理解

结构体类型

其实结构体很好理解,一理解就可以理解。
一个结构体存不同或相同的数据类型。一个结构体可以表示一个实体。你可以将想要放入的数据类型放入到一个结构体。

有时候,结构体很形象。比如可以利用一个结构体表示一个点。
一个点有x,y坐标。那么我们就可以将其封装在一个结构体里。

有了结构体类型,那么定义变量就很简单了。

定义:

struct Point
{
int x;
int y;
}p; //声明类型的同时定义结构体变量p

也可以先声明后定义
struct Point p;

初始化

实际上,结构体的初始化和数组一样,都可以在定义的同时整体赋值,但是如果想定义后再赋值,则必须一个一个的赋值。

(1)定义的同时赋值
struct Point p1 = {3,4};

(2)定义后赋值
struct Point p2;
p2.x = 3;
p2.y = 4;

当结构体存在嵌套结构时,赋值方法也和数组相同(可以利用花括号嵌套起来)。
struct Node
{
int data;
struct Point p;
}n = {10,{3,4}};

自引用

在结构体类型创建时,存在结构体自引用问题。自引用就是指在定义一个结构体时引用自身为内部变量。
struct Node
{
Int data;
struct Node next;
}
在C语言中这种情况是不允许的。因为要定义一个变量必须明确其大小,也就是说需要为其开辟空间。那么这种自引用的情况相当于没有出口的递归(因为在定义时要知道结构体变量的大小)

那么结构体到底可不可以实现自引用呢?
当然是可以的,只不过需要用指针来实现。
typedef struct Node
{
int data;
struct Node* next;
}Node;

struct Node
{
int data;
struct Node* next;
}

内存对齐问题

我们知道所有数据类型都有大小。那么结构体类型的大小是多少呢?
这时候就涉及到了结构体的内存对齐问题了。
结构体封装不同数据类型势必会造成存取元素时的不方便。
于是就出现了内存对齐。

什么是内存对齐?你只需要记住结构体的内存对齐是为了数据存取时节省时间而设计的一种存储方法。
这种存储方法是通过牺牲空间来换取时间的。

有了这种方法后结构体的大小就变得”不确定“了。
比如,
求下面一个结构体的内存大小
struct s1
{
char c1;
int i;
char c2;
};
sizeof((struct s1))?
在这里插入图片描述
为什么是12!?
这是因为内存对齐。
计算机在存 char c1时直接存入(占一个字节)
那么在存 int i (4字节)时是继续在c1后面3个字节继续存,还是新开辟一个4字节空间在新开辟的地方存呢?
答案肯定是后者了,因为这就是内存对齐。
在解释内存对齐前,先解释一下结构体偏移量和对齐数。
偏移量就是数据存储时的首地址和0地址的差。那么刚刚int i 的偏移量就是4。
对齐数一般指数据本身大小。int 的对齐数是4,char 为1。

那么结构体内存对齐时要满足什么规则?
PS:存第一个变量时不需要考虑内存对齐。
(1)第一个成员变量在偏移量为0的位置
(2)其他成员变量要对齐到其本身对齐数的整数倍的地址。比如int i 存在了偏移量为4的位置
(3)结构体总大小为最大对齐数(结构体内部数据最大的那个的对齐数)的整数倍(其实就是最小公倍数,后面会讲)
(4)存在结构体嵌套的情况也必须满足上面3条。
ps:结构体的对齐数为其内部元素的最大对齐数。

那么我们现在再看
struct s1
{
char c1;
int i;
char c2;
};
在这里插入图片描述
存c1 将其放在第一个字节处,然后空3个字节存int i ,i占了4个字节后,再存最后的char c2(占一个字节)。
此时一共占了9个字节,那么这个结构体的大小为9吗?
不,它还得满足第三条,9不能整除4(最大对齐数),那么加1为,10也不能整除,最后加到12,12可以。所以最后这个结构体的大小为12。

正因为存在内存对齐,所以数据的不同位置也可能造成结构体的大小不同。
struct s1
{
int i;
char c1;
char c2;
}
此时其大小就变为了8
i占4个字节,两个char 型占2个字节。
此时一共占6个字节,但是6不能整除4(最大对齐数),所以大小就为8。

结论:在定义结构体时应该尽量定义占据内存较小的结构体。

位段及位段的大小计算

位段

位段的声明和结构是类似的(不过位段是为了节省空间),有两个不同:
(1)位段的成员必须是int ,unsigned int,signed int(有符号整型)
(2)位段的成员后面有一个冒号和一个数字。
数字表示要存多少个比特位。
位段的空间是按照4个字节(int)或者1个字节(char)分配的。char 其实也是整型。
位段节省空间是通过连续存储数据实现的。
比如,
struct A
{
int a : 14;
int b : 16;
}
位段创建了4个字节(4*8=32个比特位),当a存储14个比特位后。
发现b要存16个比特位,可以放下,那么就在第15个比特位开始连续存16个比特位。
那么其实这个位段只占了4个字节。
在这里插入图片描述

位段的不确定因素

位段有很多不确定因素,这导致了位段是不跨平台的。
(1)位段中的成员在内存中是从左向右存,还是从右向左存是不确定的。
(2)位段中的前一个成员剩下的空间不足以存下下一个成员时。此时下一个成员应该全部存在新开辟一个空间里,还是在前一个成员剩下的空间存一部分,然后在新空间里存一部分呢。(这也是不确定的)

位段的大小计算

其实前面我已经做了简单的介绍了。
位段的连续存储使得位段占据内存很小。
但也因为很多不确定因素导致在不同的环境下其大小可能不同。(比如上面第二条)
在这里插入图片描述
在这里插入图片描述
从上面两个截图可以看出,我的计算机面对位段存储时,当第二个成员不能在第一个成员剩下的空间完整存储时,会在新的空间存储。(a剩余1个比特位,不足以存下b,以此类推,最后导致此位段占据了16个字节)

枚举+联合

枚举

枚举见名知意,即使一个一个全部列举出来。
比如

定义

enum Day{
Mon,
Tues,
Wed,
Thur,
Fri,
Sta,
Sun
};
一共有7种星期
enum Sex
{
MALE,
FEMALE
};
2种性别

枚举里面的常量是有值的,都是整型。
如果不赋初值,那么就是从0开始递增。
如果在定义是赋值,那么就从赋的值开始递增。
比如,
在这里插入图片描述
只能拿枚举常量给枚举变量赋值,枚举出来的常量就是枚举变量的所有可能的值。其实给枚举变量赋值整型数据也是可以的,但是这就失去了枚举的意义。所以我们一般不这样做!
enum DAY r = a;(正确)
enum DAY r = 9;(可以但不推荐)
当然枚举也不一定就非得列举完。
只要列举需要用到的就可以了。

优点

(1)增加代码的可读性和可维护性
(2)和#define定义的标识符比较枚举有类型检查,更加严谨。
(3)防止了命名污染(封装)
(4)方便,一次就定义了多个常量。

联合

联合又称共用体。(这是因为联合里的成员变量共用一个空间)

声明

union Un
{
char c;
int i;
};
sizeof(union Un)?
在这里插入图片描述

联合的特点

(1)联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)
(2)联合里的每一个成员都可以看作第一个成员变量。
printf("%p\n",&r.c);
printf("%p\n",&r.i);
在这里插入图片描述
利用这个特点,我们可以测出计算机的大小端:
在这里插入图片描述

联合大小的计算

(1)联合的大小至少是最大成员的大小。
(2)当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。short (2字节)
在这里插入图片描述

发布了57 篇原创文章 · 获赞 11 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_42419462/article/details/94660833