“解剖”c语言——自定义类型(结构体,位段,枚举,联合体)
Posted sjp11
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了“解剖”c语言——自定义类型(结构体,位段,枚举,联合体)相关的知识,希望对你有一定的参考价值。
本章学习重点
本节课学习重点
1.结构体
1.1结构体的声明
概念:
结构体是一些值的集合,这些值称为成员变量。结构的每个成员可以是不同类型的变量 |
结构体的声明:
例如描述一个学生,一个学生有 名字,年龄,性别,学号为例,那么我们应该怎样去定义这个它呢?
1.2 结构体的内存对齐
接下来我们来讨论一个更深层次的问题,如何计算结构体的内存大小?
请问下面这个结构体的大小:
要计算这个结构体,首先我们得知道:
1.第一个成员在与结构体变量偏移量为0的地址处。
偏移量:”把存储单元的实际地址与其所在段的段地址之间的距离称为段内偏移,也称为“有效地址或偏移量”。
通俗的讲:一个存储数据的“实际地址”=段首地址+偏移量
成员的变量的地址=结构体开始地址+偏移量
2 其他成员变量要对齐到对齐数的整数倍的偏移量处。
对齐数=编译器默认的一个对齐数与该成员变量大小的较小值。
以上面为例,在vs编译器下,我们计算一下i的对齐数为多少,首先vs编译器默认的的对齐数为8,i的大小为4,对齐数为8和4中的较小值,所以i的对齐数为4.
(已知在vs编译器中默认的对齐数为8,linux中默认的值为4)
3.结构体的总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
以上面的结构体作为例子:
c1和c2的对齐数为1,i的对齐对齐数为4,因为i是对齐数中的最大值,所以i是结构体中的最大对齐数,所以结构体的总大小为4的整数倍。
那么我们来计算上面结构体的大小:
所以S1的结构体的大小为12.
那么我们来在计算一个结构体S2的大小:
那么我们来验证一下我们刚才计算的两个结构体大小是否正确:
由结构体S1和S2可以得出:
它们的结构体存放成员变量都是一样的,但是由于存放的位置不同,导致这两个结构体的大小各不相同。 |
所以我们在设计结构体时,要节省空间的话:让占用空间小的成员变量尽量集中在一起。
1.3 结构体对齐的好处
好处一: 平台移植型好
不是所有的硬件平台都能访问任意地址上的数据;某些硬件平台只能只在某些地址访问某些特定类型的数据,否则抛出硬件异常,及遇到未对齐的边界直接就不进行读取数据了。
好处二: cpu性能的提高
在32位机器中,一次读取4个字节,能使cpu性能的提高,如果没有对齐,有些数据读取需要读取两次
那么我们来看下面的例子:
蓝色代表的是i的内存,黄色代表的是c的内存,一个空格代表一个比特位
1.假如内存不对齐,那么在读取i的过程中,第一次拿出 4个字节,丢弃掉第一个字节,第二次拿出4个字节,丢弃最后的三个字节,然后拼凑出一个完整的 int 类型的数据。
2.而内存对齐,则cpu能够一次读取出来。
所以,结构体内存对齐,空间换取时间的做法,提升读取效率
1.4 #pragma pack(n)的使用(修改默认对齐数)
我们知道,在vs编译器的底下的默认对齐数为8,但我们可以通过
#pragma pack(n) 作用:修改编译器默认的对齐数,其中n是你要指定的对齐数.
#pragma pack() 作用:恢复编译器默认的对齐数
#pragma pack(4)//设置默认对齐数为1
struct S1
{
char c1;
double d;
int i;
};
#pragma pack()//取消设置对齐数,还原默认对齐数
当我们修改默认对齐数后,该结构体的大小为16,如果没有修改对齐数,则该结构体的大小为24.
结论:
结构在对齐方式不合适的时候,我么可以自己更改默认对齐数 |
2. 位段
结构体讲完,我们我们需要讲讲位段的实现。
信息的存储一般以byte(字节)为单位,实际上,有时存储一个信息不用一个或多个字节,如我们想存储0 1 2 3 我们只需要两个byte就可以了,一个字节则会存储这4个数字其中一个,则会有6个bit位被浪费掉。在计算机用于过程控制、参数检测或数据通信领域时,控制信息往往只占一个字节中的一个或几个二进制位,常常在一个字节中放几个信息。
C语言允许在一个结构体中以位为单位来指定其成员所占内存长度,这种以位为单位的成员称为“位段”或称“位域”( bit field) 。利用位段能够用较少的位数存储数据。
位段的定义:
位段的实现与结构体是类似的,只是它们之间有两个不同:
1.位段的成员必须是 int、unsigned int 或signed int 或者char(属于整型类型)。
2.位段的成员名后边有一个冒号和一个数
例如:
struct A
{
int a : 10;
int b : 20;
int c : 30;
};
位段的大小:
位段的特点:
1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
2.不能对位段进行取地址操作;
3.对位段赋值时,最好不要超过位段所能表示的最大范围
4. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。
对于不跨平台如下:
1.位段中的成员在内存中是从左向右分配,还是从右向左分配煤油一个标准的定义。(上述例子采用从左向右分配)
2.当一个结构包含两个位段,第二个位段成员比较大,无法容纳第一个位段剩余的位时,是舍弃剩余的还是利用,这是不确定的。
3.枚举
3.1 什么是枚举
枚举顾名思义就是一 一列举。把可能的值一一列举出来,把有限的可能给罗列出来:
如我们生活中:
一年四季,春 夏 秋 冬
一周的星期一到星期日是有限的7天。
颜色 红 黄 蓝 绿等
上面这些情况都可以使用枚举。
3.2 枚举的定义
1.枚举型是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号,隔开。
2.枚举型是预处理指令#define的替代。
enum Color//颜色
{
Red,
Green,
Blue,
Yellow
};
enum Day//星期
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
以上enum Day和enum color都是枚举类型,其中{}是枚举中可能取得值,也叫枚举常量。这些可能取得值都是有值的,默认从0开始,每一次递增1.
运行结果:
当然,也可以在定义的时候赋值初值。
如果枚举中只有一个值赋值,那么紧接的枚举常量都是递增+1
enum Color//颜色
{
Red=1,
Green,
Blue,
};
运行结果:
enum Color//颜色
{
Red=1,
Green=3,
Blue=5,
};
运行结果:
错误声明一:存在相同名字的枚举成员
错误声明二:存在同名的枚举类型
3.3 枚举的好处
好处一:增加代码的可读性
例如下面:我们知道Red为0,Green为1,Blue为2,在switch输入相关数字就能进入相关的函数。
好处二:它可以让数据更简洁,更易读。
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
使用枚举后:
num DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
4 union联合(共用体)
union,中文名“联合体、共用体”,在某种程度上类似结构体struct的一种数据结构,共用体(union)和结构体(struct)同样可以包含很多种数据类型和变量。
特征是这些成员共用一块空间(所以联合也叫共用体)
union Un
{
char c;
int i;
};
那么我们来计算这个联合体的大小
联合体的大小至少是最大成员的大小(因为联合至少得有能力保存最大成员那个)
那么我们来看一下联合体中两个成员变量的地址。我们发现联合体中的两个成员变量的地址都是一样的,这就证明这两个成员变量共用同一块空间。
所以联合体的结构大概是这个样子的:
c的存储内存是0到8,i的存储内存是0到32bit
联合体中c和i能否同时存在,我们来看一下例子:
成员c的创建会影响到i的值,所以i和c不能同时存在,c的改变会影响i。
总结:
联合体(union)中是各变量是“互斥”的——缺点就是不够“包容”;但优点是内存使用更为精细灵活,也节省了内存空间。
联合体大小的计算:
1.联合的大小至少是最大成员的大小。
2.当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
例子:
题目:
判断当前计算机大小端存储?
大端:数据的高位字节存放在地址的低端 低位字节存放在地址高端
小端:数据的高位字节存放在地址的高端 低位字节存放在地址低端
代码如下:
union Un
{
char c;
int i;
};
void judege()
{
union Un un;
un.i = 1;
if (un.c == 1)
{
printf("小端\\n");
}
else
{
printf("大端\\n");
}
}
完!
感谢你的点赞,关注,收藏!
以上是关于“解剖”c语言——自定义类型(结构体,位段,枚举,联合体)的主要内容,如果未能解决你的问题,请参考以下文章