目录
二、位段
(书接上文结构体,现在讲位段,跟结构体有些类似~)
2.1 什么是位段
位段与结构体类似,但也有不同:
1. 位段成员必须是int、unsigned int、signed int(char 也可以)。
2. 位段的成员名后有一个冒号和一个数字。
举个栗子~
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
不同于结构体,位段多了冒号和数字。那么这冒号和数字有什么含义呢?其实,冒号后的数字代表了比特位。比如上面的例子中,_a这个成员只占2个比特位,_b这个成员只占5个比特位。如果这样写的话,sizeof(A)就只有8个字节。
但注意,这种写法需要根据具体特殊情况使用。因为正常的int的范围很大,而如果限定为2个字节如上面的_a,则范围只能在0~3,如果你现在有一个成员你只需要它表示0~3则可以用这种位段来表示。因此,位段可以在某些特殊情况下给我们节约空间。
2.2 位段的内存分配
- 位段成员:int / unsigned int / signed int / char
- 位段空间是按要求需要以4字节或者1字节的方式开辟。
- 位段不跨平台,注重可移植性的程序应避免使用位段。
一个栗子,我们给出的A究竟占几个字节呢?
struct A
{
int _a : 2;
int _b : 5;
int _c : 10;
int _d : 30;
};
- 首先一看是int,那我们先分配四个字节。4个字节一共有32个比特位,然而我这个_a只占用了2个比特位,用完后就剩30个比特位。接着_b占用了5个比特位,用完后就剩25个比特位。接着_c占用了10个比特位,用完后还剩15个比特位。
- 然后_d需要30个比特位,此时不够了。于是我们又在内存中申请一个int的大小,4个字节。那么_d会不会用之前剩下的15个比特位呢?还是直接用新开辟的?这个问题是没有标准的,这个就造成了位段是不跨平台的。因为不同的平台处理这个问题的选择是不同的。
- 但无论如何,这个例子中的A的大小都是8个字节。
另一个栗子:
struct S
{
char a : 3;
char b : 4;
char c : 5;
char d : 4;
};
struct S s = {0};
s.a = 10;
s.c = 12;
s.c = 3;
s.d = 4;
- 因为是char,所以我们先开辟1个字节(00000000)。首先看a,a只占了3个比特位,那么这三个是占前三个还是后三个呢?没有规定。所以说位段不好跨平台使用。那么我们假设a占的是后三个比特位吧!然后是b,b占了4个比特位,现在后7个比特位就都被占用了。
- 然后还剩1个比特位了。c需要5个,不够。那么就再开辟1个字节也就是8个比特位。那么我们假设我们把这1个浪费了即不用剩下的这1个比特位,让c占用新开辟的1个字节中的后5个比特位。还剩3个比特位。
- 最后为了d再开辟一个字节,最后四个比特位给d。如果这样的话s的大小就是三个字节。
我们可以通过内存的查看来验证我们的猜想。a放的三个比特位是10的二进制序列1010的后三个010,b放的四个是1100,c放的是00011,d放的是0100。然后按照上面的猜想将他们放在三个字节中:01100010 00000011 00000100。我们在调试窗口中的内存可以查看具体内存的存储内容来验证。
2.3 位段的跨平台问题
很多问题:int有无符号、位段的最大数目、从左向右还是从右向左分配、是否舍弃剩余位置。
只能根据不同编译器写不同的代码。
三、枚举——enum
顾名思义:一一列举。
3.1 枚举的定义类型
比如,性别分成男、女、保密,所以性别就可以定义成枚举类型:
enum Sex
{
MALE,
FEMALE,
SECRET
};
我们用enum定义一个名为Sex的枚举类型,未来可能的取值有MALE FEMALE SECRET。然后用的时候我们就可以定义enum Sex s = MALE;这样一个变量。
再举个栗子:
一个星期也可以定义成枚举类型,一共有7中可能取值。
enum Day
{
Mon,
Tues,
Wed,
Thur,
Fri,
Sat,
Sun
};
(我们枚举的一定是容易、适合枚举的东西~)
另外,枚举的可能取值都是有值的,可以直接printf("%d", Mon);这样打印出来。第一个值默认为零,后面的依次递增1。当然,我们也可以自定义初值,比如在定义时让Mon = 5,那么Tues的值就是6。
3.2 枚举的优点
我们将枚举常量与#define定义的常量对比有很多优点,比如#define Mon 5与在枚举中定义Mon = 5,其实枚举常量中的更具有可读性,更有意义;并且枚举有类型检查;还可以防止命名污染;并且便于调试(调试的都是预编译之后的代码,然而用define的话,预编译之后的代码已经将定义的常量直接替换成了数值,不便于调试);更方便使用,一次定义多个常量。
建议大家在定义常量的时候多考虑枚举~
四、联合(共用体)——union
4.1 联合的定义
这种类型的变量包含一系列的成员,这些成员共用同一快空间。
栗子:
union Un
{
char c;
int i;
};
int main()
{
union Un u;
printf("%d", sizeof(u));
return 0;
}
如果你认为,char是1,加上int是4,答案应该是5,考虑对齐应该是8。那么你就是把他错当成结构体了。事实上,结果应该是4。
为了验证,我们可以printf("%p\n", &u);printf("%p\n", &(u.c));printf("%p\n", &(u.i));,会发现这三个结果相同,说明两个成员共用内存。所以我们也称之为共用体。改c的时候i也变了,改i的时候c也变了。所以联合体的成员不会同时使用。
4.2 使用实例
判断机器存储模式(大小端):
//原来的方法
int main()
{
int i = 1;
//01 00 00 00
if(1 == *(char*)&i)
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
//用联合的方法
int check_sys()
{
union Un
{
char c;
int i;
}u;
u.i = 1;
return u.c;
}
int main()
{
if(1 == check_sys())
{
printf("小端\n");
}
else
{
printf("大端\n");
}
return 0;
}
4.3 联合体大小
栗子:
union Un
{
char arr[5];
int i;
};
我们来计算它的大小,看起来应该是5。但结果是8。因为存在对齐。char类型的数组对齐数为1。然而int的对齐数是4,最大对齐数是4,所以5不是最大大小,应该变成8。
所以联合体的大小不一定是最大成员的大小,至少是最大成员的大小。如果最大成员的大小不是最大对齐数的整数倍,需要对齐。
好啦!C语言自定义类型——【结构体,枚举,联合】的介绍就结束啦!接下来的博客会有个具体实例使用的介绍!感谢大家的阅读!