结构体、位段和联合

>结构体类型创建 

结构体类型的声明

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

结构的声明:

tag可以省略,list也可以省略,但是两者只能省略其一,如果没有tag,则list也可作Strut的名称,但建议都保留
需要注意的一点是, 最后的分号不能丢

特殊的声明

在声明结构的时候,可以不完全的声明。
比如:


去掉*p则可以编译通过

不同的结构体,哪怕内部元素完全一样也是不同的类型。相同类型的结构体直接才可以相互赋值。
上面两个结构在声明的时候省略掉了结构体标签。

结构成员

结构成员可以是标量、数组、指针,甚至其他结构体也可以

结构体变量的成员是通过点操作符(.)访问的,点操作符接受两个操作数
如:

struct Stu s; //定义结构体变量

有时候我们得到的不是结构体变量而是指向结构体的指针,那么访问成员方法如下:


结构体的性质
①所有元素的地址是依次递增的
②首元素地址(元素类型的指针)与结构体地址(结构体指针)是一致的

数组中所有元素的地址是连续的,但是结构体没有这条性质,这是 数组与结构体的区别之一。

结构的自引用

有时候会出现在结构体中包含一个类型为该结构本身的成员的情况,这就叫做结构的自引用。

struct A
{
int a;
struct A *obj;
};
以上述代码为例,画图可以更清晰的看到自引用的作用:

可以一直指向下一个结构体,组成链表,这样只需要找到第一个结构体的位置就可以得到所有结构体的位置。
但是如果代码如下:
struct A
{
int a;
struct A next;
};
则无法编译。
因为系统无法衡量开辟多大的空间,如果是A *obj形式的话那么开辟的空间大小很明确。

还有一点需要注意:
由上图可知: 结构体实现自引用不能省略标签

结构的不完整声明

有时候会出现以下情况:

struct B在A中是未定义结构体,所以在A之前需要一个 不完整声明,声明之后会有struct B出现
改为:


>

首先明确几个概念:
结构体变量不能整体赋值,但是结构体变量可以整体初始化

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

//初始化:定义变量的同时赋初值(开辟空间且内容就是目标值)
struct Point p3 = { x, y };

struct Stu //类型声明
{
char name[15];
int age;
};
struct Stu s = { "top", 20 }; //初始化

struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = { 10, { 4, 5 }, NULL }; //结构体嵌套初始化

struct Node n2 = { 20, { 5, 6 }, NULL }; //结构体嵌套初始化

以上是结构体的基本使用。接下来深入讨论一些东西

>结构体内存对齐 

这个片段主要讨论一个问题: 计算结构体的大小

先练习两道题:

第一题分析如下:

第二题分析如下:

由此可得出结构体的部分对齐规则:
1.第一个成员在与结构体变量偏移量为0的地址处, 第一个成员不需要对齐,但是有对齐数
2.其他成员变量要对齐到对齐数的整数倍的地址处。
对齐数=编译器默认的对齐数与该成员对齐数之中的较小数
vs中默认值为8
Linux中默认值为4
编译器默认对齐数是可以更改的,调整默认对齐数可以用#pragma pack(1/4/8),取消当前对齐数设置用#pragma pack( )
注意:对齐数只能调小,不能调大。
3.总对齐数要能整除该结构体的最大对齐数
再看一段代码:

这段代码中,S4中嵌套了结构体S3,这种情况下该怎么做呢?
先来明确一下嵌套结构体的对齐规则:
在上述三个对齐规则的基础上,嵌套结构体还需要满足:
嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数的整数倍
即:

那么,为什么存在内存对齐呢?

1.平台原因(移植原因)
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,其他数据不能直接访问,否则抛出硬件异常。
2.性能原因
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需要一次访问。

总之,结构体内存对齐是拿空间来换时间的做法。

假设CPU只能访问4的倍数,
如果不对齐,则访问b时则需要访问0*18,0*18-a再访问0*14,0*14取a个字节,需要2次内存访问,如下图:

但是对齐后0*18中a后的3个字节空出来,b放在0*14中,则只需要一次访问,如下图:

在设计结构体的时候,既想要利用对齐节省时间,又想要节省空间的话,就 让占用空间小的成员尽量集中在一起

结构体传参

这部分不举例了,直接说结论

之前的函数调用中,函数传参的时候参数是需要压栈的。
但是结构体传参时不会降级,所以会出现结构体过大,参数压栈系统开销较大,性能下降的问题。因此,禁止直接传结构体, 结构体传参时必须要传结构体的地址。

>位段,位段计算机大小。 

位段的声明和结构是类似的,但是有两点不同:
1.位段的成员必须是int、unsigned int 或者 signed int
2.位段的成员后边有一个冒号和一个数字
例:

A就是一个位段类型。
A的大小是多少呢?


位段的内存分配

1.位段的成员可以是int、 unsigned int、 signed int 或者是char 类型
2.位段的空间上是按照需要以4个字节或1个字节的方式来开辟的
3.位段涉及到很多不确定因素,所以是不跨平台的

//栗子


1.在内存中从右向左分配
2.当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,舍弃剩余的位(超出内存的部分会被直接丢弃)
3.int位段默认为有符号数

不能跨平台的原因

1.int位段被当做有符号数还是无符号数是不确定的
2.位段中最大位的数目不确定
3.位段中的成员在内存在从左往右还是从右往左分配无法定义
4、当一个结构包含两个位段,第二个位段比较大时,剩余的位是舍弃还是利用,是不确定的

位段比相同效果的结构更节省空间,但是有跨平台问题

>枚举 

枚举类型的格式与结构体基本相似


枚举的本质还是整数,里面的元素本质也是0,1,2,3等整数,枚举类型的值可自由设定

枚举的优点
1.增加代码的可读性和可维护性
2.根#define定义的标识符相比,枚举有类型检查,更加严谨
3.防止了封装
4.便于调试
5.使用方便,一次可以定义多个变量

枚举的使用

枚举常量给枚举变量赋值,才不会出现类型的差异

联合(共同体)

联合定义的变量也包含一系列的成员,特征是这些成员公用一块空间,所以联合又叫共同体
所有联合体的地址一样
比如:


联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小

联合大小的计算

联合的大小至少是最大成员的大小
当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍

联合和解构

可以将long类型的IP地址转化为点分十进制的表示形式