20-位字段示例
15.4.1 位字段示例
通常,把位字段作为一种更紧凑存储数据的方式。例如,假设要在屏幕上表示一个方框的属性。为简化问题,我们假设方框具有如下属性:
- 方框是透明的或不透明的;
- 方框的填充色选自以下调色板:黑色、红色、绿色、黄色、蓝色、紫色、青色或白色;
- 边框可见或隐藏;
- 边框颜色与填充色使用相同的调色板;
- 边框可以使用实线、点线或虚线样式。
可以使用单独的变量或全长(full-sized)结构成员来表示每个属性,但是这样做有些浪费位。例如,只需 1 位即可表示方框是透明还是不透明;只需1位即可表示边框是显示还是隐藏。8种颜色可以用3位单元的8个可能的值来表示,而3种边框样式也只需2位单元即可表示。总共10位就足够表示方框的5个属性设置。
一种方案是:一个字节存储方框内部(透明和填充色)的属性,一个字节存储方框边框的属性,每个字节中的空隙用未命名字段填充。 struct box_props 声明如下:
struct box_props {
bool opaque : 1 ;
unsigned int fill_color : 3 ;
unsigned int : 4 ;
bool show_border : 1 ;
unsigned int border_color : 3 ;
unsigned int border_style : 2 ;
unsigned int : 2 ;
};
加上未命名的字段,该结构共占用16位。如果不使用填充,该结构占用10位。但是要记住,C以 unsigned int 作为位字段结构的基本布局单元。因此,即使一个结构唯一的成员是1位字段,该结构的大小也是一个 unsigned int 类型的大小, unsigned int 在我们的系统中是32位。另外,以上代码假设C99新增的 _Bool 类型可用,在 stdbool.h 中, bool 是 _Bool 的别名。
对于 opaque 成员,1表示方框不透明,0表示透明。 show_border 成员也用类似的方法。对于颜色,可以用简单的RGB(即red-green-blue的缩写)表示。这些颜色都是三原色的混合。显示器通过混合红、绿、蓝像素来产生不同的颜色。在早期的计算机色彩中,每个像素都可以打开或关闭,所以可以使用用1位来表示三原色中每个二进制颜色的亮度。常用的顺序是,左侧位表示蓝色亮度、中间位表示绿色亮度、右侧位表示红色亮度。表15.3列出了这8种可能的组合。 fill_color 成员和 border_color 成员可以使用这些组合。最后, border_style 成员可以使用 0 、 1 、 2 来表示实线、点线和虚线样式。
| 位组合 | 十进制 | 颜色 |
| :----- | :----- | :----- | :----- | :----- |
| 000 | 0 | 黑色 |
| 001 | 1 | 红色 |
| 010 | 2 | 绿色 |
| 011 | 3 | 黄色 |
| 100 | 4 | 蓝色 |
| 101 | 5 | 紫色 |
| 110 | 6 | 青色 |
| 111 | 7 | 白色 |
程序清单15.3中的程序使用 box_props 结构,该程序用 #define 创建供结构成员使用的符号常量。注意,只打开一位即可表示三原色之一。其他颜色用三原色的组合来表示。例如,紫色由打开的蓝色位和红色位组成,所以,紫色可表示为 BLUE|RED 。
程序清单15.3 fields.c 程序
/* fields.c -- 定义并使用字段 */
#include <stdio.h>
#include <stdbool.h> // C99定义了bool、true、false
/* 线的样式 */
#define SOLID 0
#define DOTTED 1
#define DASHED 2
/* 三原色 */
#define BLUE 4
#define GREEN 2
#define RED 1
/* 混合色 */
#define BLACK 0
#define YELLOW (RED | GREEN)
#define MAGENTA (RED | BLUE)
#define CYAN (GREEN | BLUE)
#define WHITE (RED | GREEN | BLUE)
const char * colors[8] = { "black", "red", "green", "yellow",
"blue", "magenta", "cyan", "white" };
struct box_props {
bool opaque : 1; // 或者 unsigned int (C99以前)
unsigned int fill_color : 3;
unsigned int : 4;
bool show_border : 1; // 或者 unsigned int (C99以前)
unsigned int border_color : 3;
unsigned int border_style : 2;
unsigned int : 2;
};
void show_settings(const struct box_props * pb);
int main(void)
{
/* 创建并初始化 box_props 结构 */
struct box_props box = { true, YELLOW, true, GREEN, DASHED };
printf("Original box settings:\n");
show_settings(&box);
box.opaque = false;
box.fill_color = WHITE;
box.border_color = MAGENTA;
box.border_style = SOLID;
printf("\nModified box settings:\n");
show_settings(&box);
return 0;
}
void show_settings(const struct box_props * pb)
{
printf("Box is %s.\n",
pb->opaque == true ? "opaque" : "transparent");
printf("The fill color is %s.\n", colors[pb->fill_color]);
printf("Border %s.\n",
pb->show_border == true ? "shown" : "not shown");
printf("The border color is %s.\n", colors[pb->border_color]);
printf("The border style is ");
switch (pb->border_style)
{
case SOLID: printf("solid.\n"); break;
case DOTTED: printf("dotted.\n"); break;
case DASHED: printf("dashed.\n"); break;
default: printf("unknown type.\n");
}
}
下面是该程序的输出:
Original box settings:
Box is opaque.
The fill color is yellow.
Border shown.
The border color is green.
The border style is dashed.
Modified box settings:
Box is transparent.
The fill color is white.
Border shown.
The border color is magenta.
The border style is solid.
该程序要注意几个要点。首先,初始化位字段结构与初始化普通结构的语法相同:
struct box_props box = {YES, YELLOW , YES, GREEN, DASHED};
类似地,也可以给位字段成员赋值:
box.fill_color = WHITE;
另外, switch 语句中也可以使用位字段成员,甚至还可以把位字段成员用作数组的下标:
printf("The fill color is %s.\n", colors[pb->fill_color]);
注意,根据 colors 数组的定义,每个索引对应一个表示颜色的字符串,而每种颜色都把索引值作为该颜色的数值。例如,索引1对应字符串 "red" ,枚举常量 red 的值是 1 。