【C进阶】自定义类型(2)位段 - 枚举 - 联合

简介: 目录1、位段 1.1、什么是位段 1.2、位段的内存分配 1.3、位段的跨平台问题 1.4、位段的应用2、枚举 2.1、枚举类型的定义 2.2、枚举的优点 2.3、枚举的使用3、联合 3.1、联合类型的定义 3.2、联合的特点 3.3、联合大小的计算

1、位段

1.1、什么是位段

  • 位段的声明和结构是类似的,有两个不同:
  1. 位段的成员必须是 int、unsigned int 或signed int 。
  2. 位段的成员名后边有一个冒号和一个数字。
  • 比如:
#include<stdio.h>
struct A
{
  int _a : 2;
  int _b : 5;
  int _c : 10;
  int _d : 30;
};
int main()
{
  printf("%d\n", sizeof(struct A)); //8
  return 0;
}

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

解析:

上述代码中,_a : 2 表示 _a 只需要2个比特位,_b : 5 表示 _b 只需要5个比特位,_c : 10表示_c只需要10个比特位,_d : 30表示 _d 只需要30个比特位。将这些比特位全部+起来总共47个bit位,何来8字节呢?需要了解下面的位段内存分配:

1.2、位段的内存分配

位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型

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

位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段。

综上所述:

_a是int类型的,先开辟4个byte,_a占2个bit,还剩30bit,_b占5个bit,还剩25bit,_c占10个bit,还剩15bit,15个bit的大小不够_d的30bit大小,且_d还是int类型的,所以再次开辟4个byte,装下了_d的大小,所以位段A的大小8字节。


再比如:

#include<stdio.h>
struct S
{
  char a : 3;
  char b : 4;
  char c : 5;
  char d : 4;
};
int main()
{
  printf("%d\n", sizeof(struct S));//3
  struct S s = { 0 };
  s.a = 10;
  s.b = 12;
  s.c = 3;
  s.d = 4;
  return 0;
}

此时a是char类型的,先开辟1个字节,a占了3个bit,还剩5个,b占了4个bit,还剩1个bit,不够c的5个bit,再开辟1个字节,赋给 c 5个bit位,还剩3个,又不够给d了,再开辟1个字节足矣。综上此时开辟3个字节的大小。此情况是存在空间浪费的。那1个bit位被浪费了,但是没办法。


位段在一定程度上可以节省空间,但也会适当浪费空间(很小)。

画图解释具体空间开辟情况:image.png为了证明图中的结论,在VS编译器打开监视看看内存:image.png1.3、位段的跨平台问题

位段本身是不支持跨平台的,原因如下:

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

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

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

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

总结:

跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,但是有跨平台的问题存在。

1.4、位段的应用image.png 跟结构相比,位段可以达到同样的效果,但是可以很好的节省空间,即使有跨平台的问题存在。

2、枚举

枚举顾名思义就是一一列举。把可能的取值一一列举。

比如我们现实生活中:

一周的星期一到星期日是有限的7天,可以一一列举。

性别有:男、女、保密,也可以一一列举。

月份有12个月,也可以一一列举

这里就可以使用枚举了。

注意:枚举类型的大小始终4字节

2.1、枚举类型的定义

#include<stdio.h>
enum Day//星期
{
  Mon,
  Tues,
  Wed,
  Thur,
  Fri,
  Sat,
  Sun
};
int main()
{
  printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
  return 0;
}

image.png以上定义的 enum Day,是枚举类型。{ }中的内容是枚举类型的可能取值,也叫 枚举常量 。 从运行结果看,不难发现枚举是有初始值的,默认第一个枚举常量为0,向下依次+1。当然在定义的时候也可以赋初值。

  • 例如:
#include<stdio.h>
enum Day//星期
{
  Mon = 1,
  Tues,
  Wed,
  Thur = 3,
  Fri = 8,
  Sat,
  Sun = 0
};
int main()
{
  printf("%d %d %d %d %d %d %d\n", Mon, Tues, Wed, Thur, Fri, Sat, Sun);
  return 0;
}

image.png

2.2、枚举的优点

#define MALE 4
#define FEMALE 5
#define SECRET 6
typedef enum Sex
{
  MALE=4,
  FEMALE,
  SECRET
}Sex;
  • 我们可以使用 #define 定义常量,为什么非要使用枚举?
  • 枚举的优点:
  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量

2.3、枚举的使用

enum Color//颜色
{
  RED = 1,
  GREEN = 2,
  BLUE = 4
};
enum Color clr = GREEN;//只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
clr = 5; //ok??  err错误,左右类型不一致

3、联合

3.1、联合类型的定义

  • 联合也是一种特殊的自定义类型

这种类型定义的变量也包含一系列的成员,特征是这些成员共用同一块空间(所以联合也叫共用体)。

#include<stdio.h>
//联合类型的声明
union Un
{
  char c; //1
  int i;  //4
};
int main()
{
  //联合变量的定义
  union Un u;
  //计算联合变量的大小
  printf("%d\n", sizeof(u)); //4
  return 0;
}
  • 联合体的访问和结构体类似都可以采用 . 操作符或 -> 指向操作符。
union un x;
x.a = 10;
union un* p = &x;
p->a;

3.2、联合的特点

联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)

#include<stdio.h>
union Un
{
  char c; //1
  int i;  //4
};
int main()
{
  union Un u;
  //计算联合变量的大小
  printf("%d\n", sizeof(u)); 
  printf("%p\n", &u);
  printf("%p\n", &u.c);
  printf("%p\n", &u.i);
  return 0;
}

image.png

  • 利用联合体的空间分布可以巧妙判断出大小端:
#include<stdio.h>
union un
{
  int a;
  char b;
};
int main()
{
  union un x;
  x.a = 1;
  if (x.b == 1)
  {
    printf("小端\n");
  }
  else
  {
    printf("大端\n");
  }
  return 0;
}
  • 解析:
  • image.png这里要把一组二进制序列 0x 00 00 00 01保存在a对应的空间里,此时四个字节每个都有地址,而地址具有高低之分,而我们的数据按字节对应的1bit位进行划分的时候,数据就有高低权值位之别,所以存储方案有两种,一种高权值位放在高地址处,低权值低地址处,如我们上图的左边存法,01放在低地址处,第二种方案相反,如上图右边将01放在高地址处,因为b永远在a的低地址处,b占一个字节,如上图x.b=1红色记号笔划分出,如果存储方案是第一种,那么b=1,如果是第二种,那么b=0,而第一种存储方案正式小端的存储法则,第二种正是大端的存储法则。


由编译器运行得知,是小端:

image.png

3.3、联合大小的计算

  1. 联合的大小至少是最大成员的大小。
  2. 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍。
  • 例如:
#include<stdio.h>
union Un1
{
  char c[5];
  int i;
};
union Un2
{
  short c[7];
  int i;
};
int main()
{
  printf("%d\n", sizeof(union Un1)); //8
  printf("%d\n", sizeof(union Un2)); //16
  return 0;
}

解析Un1的大小为8:

因为char类型自身大小为1字节,而编译器默认对齐数是8字节,对齐数取较小值为1字节,int类型自身大小4字节,对齐数取较小值为4字节。此时最大对齐数4字节,而char c[5]代表至少是5字节大小,又因为要是最大对齐数4的整数倍,所以增加到8字节。


解释Un2的大小16字节:

short c[7]代表该联合体大小至少14字节,short类型本身大小2字节,编译器默认对齐数8字节,此取较小值为2字节,int类型本身4字节,编译器8字节,此对齐数取较小值为4字节,最大对齐数为4字节,而14不是4的整数倍,所以增加到16字节。


相关文章
|
编译器
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(上)
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(上)
|
存储 编译器 Linux
详解结构体、位段、枚举、联合类型【C语言/进阶】
详解结构体、位段、枚举、联合类型【C语言/进阶】
76 0
|
5月前
|
编译器 C语言 C++
【C语言基础】:自定义类型(二) -->联合和枚举
【C语言基础】:自定义类型(二) -->联合和枚举
|
5月前
|
编译器 C语言 C++
【再识C进阶4】详细介绍自定义类型——结构体、枚举和联合
【再识C进阶4】详细介绍自定义类型——结构体、枚举和联合
|
6月前
|
存储 编译器 C语言
【自定义类型】--- 位段、枚举、联合
【自定义类型】--- 位段、枚举、联合
88 0
|
12月前
|
存储 编译器 C语言
C语言进阶-自定义类型:结构体、枚举、联合(2)
C语言进阶-自定义类型:结构体、枚举、联合
59 0
|
存储
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(中)
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(中)
|
存储 编译器
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(下)
自定义类型:结构体2.0(初阶+进阶)+位段+枚举+联合(下)
|
C语言
C语言进阶-自定义类型:结构体、枚举、联合(下)
C语言进阶-自定义类型:结构体、枚举、联合(下)
85 0
|
编译器 Linux C语言
C语言进阶-自定义类型:结构体、枚举、联合(上)
C语言进阶-自定义类型:结构体、枚举、联合(上)
68 0