目录
结构体
结构体基础知识
结构的自引用
结构体内存对齐
结构体大小计算
存在内存对齐的原因
设计结构体时的技巧
修改默认对齐数
结构体实现位段(位段的填充&可移植性)
什么是位段
位段的内存分配
位段的跨平台问题
位段的应用
枚举
枚举类型的定义
枚举的优点
枚举的使用
示例一
示例二
联合(共用体)
联合类型的定义
联合的特点
联合体的应用
总结
结构体
结构体基础知识
关于结构体的一些基础知识博主在前面的博文:《初始结构体》中有讲到,有兴趣的宝子可以点下面的链接进行学习初识结构体_遇事问春风乄的博客-CSDN博客https://blog.csdn.net/m0_71731682/article/details/130754959?spm=1001.2014.3001.5502这次博主就《初始结构体》进行一个补充
结构的自引用
结构体的自引用就是指在结构体内部,包含指向自身类型结构体的指针。
正确使用方式如下
struct Node
{
int data;
struct Node* next;
};
这是正确的使用。可是有些同学写成下面的代码
struct Node
{
int data;
struct Node next;
};
这就错了,切记,结构体自引用,成员定义只能是指针,如果结构体内成员定义为struct Node; 则会报错,因为next定义中又有next,无限循环,系统无法确定该结构体的长度,会判定定义非法
初次之外还有的同学写成下面的代码
typedef struct
{
int data;
Node* next;
}Node;
这也是错的,因为我们这里是先打开的stuct,使用了Node,而这时Node还未定义。进行编译后就会出现以下错误
对于这个错误我们可以进行改进一下就可以用了,实现如下
typedef struct Node
{
int data;
struct Node* next;
}Node;
结构体内存对齐
结构体也有自己的大小,但是结构体的大小并不是简单地将每个结构体成员的大小相加就能得到。
结构体的大小计算遵循结构体的对齐规则:
结构体大小计算
struct S1
{
char c1;
int i;
char c2;
};
第一步:找出每个成员变量的大小将其与编译器的默认对齐数相比较,取其较小值为该成员变量的对齐数
第二步:根据每个成员对应的对齐数画出它们在内存中的相对位置。
第三步:通过最大对齐数决定最终该结构体的大小。
注意:大多数情况下,成员变量已经占用的总字节个数并不一定正好为其成员变量中的最大对齐数的整数倍,这时我们需要将其扩大为最大对齐数的整数倍。如图中绿色后的黄色部分
结构体内有结构体的计算图解
存在内存对齐的原因
平台原因(移植原因): 不是所有的硬件平台都能访问任意地址上的任意数据的;某些平台只能在某些地址处取得某些特定类型的数据,否则抛出硬件异常。
比如,当一个平台要取一个整型数据时只能在地址为4的倍数的位置取得,那么这时就需要内存对齐,否则无法访问到该整型数据。
性能原因: 数据结构(尤其是栈)应该尽可能的在自然边界上对齐。原因在于,为了访问未对齐内存,处理器需要作两次内存访问;而对齐的内存访问仅需一次。
在画图时可能有博友会想,内存这么重要,在进行内存对齐的时候怎么还有内存被白白浪费掉呢?
现在看来,其实结构体的内存对齐是拿空间来换取时间的做法。
设计结构体时的技巧
struct S1
{
char c1;
int i;
char c2;
};
struct S2
{
char c1;
char c2;
int i;
};
修改默认对齐数
要修改编译器的默认对齐数,我们需要借助于以下预处理命令:
#pragma pack()
如果在该预处理命令的括号内填上数字,那么默认对齐数将会被改为对应数字;如果只使用该预处理命令,不在括号内填写数字,那么会恢复为编译器默认的对齐数。
#pragma pack(8)//设置默认对齐数为8
struct S1
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
#pragma pack(1)//设置默认对齐数为1
struct S2
{
char c1;
int i;
char c2;
};
#pragma pack()//取消设置的默认对齐数,还原为默认
于是,当结构体的对齐方式不合适的时候,我们可以自己更改默认对齐数。
结构体实现位段(位段的填充&可移植性)
什么是位段
位段,C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
位段和结构体其实是非常相似的,但是有两个不同点:
1. 位段的成员必须是 char、int、unsigned int
或signed int
。
2. 位段的成员名后边有一个冒号和一个数字。
举个例子
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
说明:
首先我们要明白位段中的这个“位”字其实指的是二进制位。
我们知道一个二进制位就是1个比特位。
所以,A中int _a : 2;
其实表示的就是
_a的大小是2bit
;
同理:
_b的大小是5bit
_c的大小是10bit
_d的大小是30bit
位段的内存分配
我们惊奇的发现为8,占了8个字节,那么为什么是8呢?
位段的跨平台问题
位段的应用
用于IP数据报,格式如下
枚举
接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
这个看起来代码量就比较多,接下来我们看看使用枚举的方式:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
这样看起来是不是更简洁了。
注意:第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
枚举类型的定义
前面我们只是声明了枚举类型,接下来我们看看如何定义枚举变量。
我们可以通过以下三种方式来定义枚举变量
1、先定义枚举类型,再定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2、定义枚举类型的同时定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
3、省略枚举名称,直接定义枚举变量
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
枚举的优点
枚举的使用
示例一
#include <stdio.h>
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}
输出结果为
示例二
在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的,所以按照 C 语言规范是没有办法遍历枚举类型的。不过在一些特殊的情况下,枚举类型必须连续是可以实现有条件的遍历。
以下示例使用 for 来遍历枚举的元素:
#include <stdio.h>
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{
// 遍历枚举元素
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
}
输出结果为
联合(共用体)
联合类型的定义
//联合类型的声明
union Un
{
char c;
int i;
};
联合的特点
union Un
{
char c;
int i;
};
int main()
{
printf("%d\n", sizeof(union Un));
union Un un;
printf("%p\n", &un);
printf("%p\n", &(un.i));
printf("%p\n", &(un.c));
return 0;
}
我们再看一下运行结果
我们发现联合体大小为 4,而且&un,&(un.i),&(un.c)的地址相同,那么这样即可证明联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小·
联合体的应用
进行系统大小端的判断
代码如下
int check_sys()
{
union
{
int i;
char c;
}un = {.i = 1};
return un.c;
}
int main()
{
int ret = check_sys();
if (ret == 1)
printf("小端\n");
else
printf("大端\n");
return 0;
}
原理图如下:
博主所用的为小段存储模式,运行结果如下
总结
关于自定义类型就讲解到这儿,欢迎各位留言交流以及批评指正,如果文章对您有帮助或者觉得作者写的还不错可以点一下关注,点赞,收藏支持一下。