c字段和属性的区别_C语言操控位的第二种方法:位字段

操控位的第2种方法是位字段(bit field)。位字段是一个signed int或unsigned int类型变量中的一组相邻的位(C99和C11新增了Bool类型的位字段)。位字段通过一个结构声明来建立,该结构声明为每个字段提供标签,并确定该字段的宽度。例如,下面的声明建立了一个4个1位的字段:

struct   {    unsigned int autfd   : 1;    unsigned int bldfc   : 1;    unsigned int undln   : 1;    unsigned int itals   : 1;} prnt;

根据该声明,prnt包含4个1位的字段。现在,可以通过普通的结构成员运算符(.)单独给这些字段赋值:

prnt.itals = 0;prnt.undln = 1;

由于每个字段恰好为1位,所以只能为其赋值1或0。变量prnt被存储在int大小的内存单元中,但是在本例中只使用了其中的4位。 带有位字段的结构提供一种记录设置的方便途径。许多设置(如,字体的粗体或斜体)就是简单的二选一。例如,开或关、真或假。如果只需要使用1位,就不需要使用整个变量。内含位字段的结构允许在一个存储单元中存储多个设置。 有时,某些设置也有多个选择,因此需要多位来表示。这没问题,字段不限制1位大小。可以使用如下的代码:

struct {    unsigned int code1 : 2;    unsigned int code2 : 2;    unsigned int code3 : 8;} prcode;

以上代码创建了两个2位的字段和一个8位的字段。可以这样赋值:

prcode.code1 = 0;prcode.code2 = 3;prcode.code3 = 102;

但是,要确保所赋的值不超出字段可容纳的范围。 如果声明的总位数超过了一个unsigned int类型的大小会怎样?会用到下一个unsignedint类型的存储位置。一个字段不允许跨越两个unsigned int之间的边界。编译器会自动移动跨界的字段,保持unsigned int的边界对齐。一旦发生这种情况,第1个unsignedint中会留下一个未命名的“洞”。 可以用未命名的字段宽度“填充”未命名的“洞”。使用一个宽度为0的未命名字段迫使下一个字段与下一个整数对齐:

struct {    unsigned int field1 : 1;    unsigned int        : 2;    unsigned int field2 : 1;    unsigned int        : 0;    unsigned int field3 : 1;} stuff;

这里,在stuff.field1和stuff.field2之间,有一个2位的空隙;stuff.field3将存储在下一个unsigned int中。 字段存储在一个int中的顺序取决于机器。在有些机器上,存储的顺序是从左往右,而在另一些机器上,是从右往左。另外,不同的机器中两个字段边界的位置也有区别。由于这些原因,位字段通常都不容易移植。尽管如此,有些情况却要用到这种不可移植的特性。例如,以特定硬件设备所用的形式存储数据。

1 位字段示例

通常,把位字段作为一种更紧凑存储数据的方式。例如,假设要在屏幕上表示一个方框的属性。为简化问题,我们假设方框具有如下属性:

  • 方框是透明的或不透明的;
  • 方框的填充色选自以下调色板:黑色、红色、绿色、黄色、蓝色、紫色、青色或白色;
  • 边框可见或隐藏;
  • 边框颜色与填充色使用相同的调色板;
  • 边框可以使用实线、点线或虚线样式。

可以使用单独的变量或全长(full-sized)结构成员来表示每个属性,但是这样做有些浪费位。例如,只需1位即可表示方框是透明还是不透明;只需1位即可表示边框是显示还是隐藏。8种颜色可以用3位单元的8个可能的值来表示,而3种边框样式也只需2位单元即可表示。总共10位就足够表示方框的5个属性设置。 一种方案是:一个字节存储方框内部(透明和填充色)的属性,一个字节存储方框边框的属性,每个字节中的空隙用未命名字段填充。struct boxprops声明如下:

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表示透明。showborder成员也用类似的方法。对于颜色,可以用简单的RGB(即red-green-blue的缩写)表示。这些颜色都是三原色的混合。显示器通过混合红、绿、蓝像素来产生不同的颜色。在早期的计算机色彩中,每个像素都可以打开或关闭,所以可以使用用1位来表示三原色中每个二进制颜色的亮度。常用的顺序是,左侧位表示蓝色亮度、中间位表示绿色亮度、右侧位表示红色亮度。表15.3列出了这8种可能的组合。fillcolor成员和bordercolor成员可以使用这些组合。最后,borderstyle成员可以使用0、1、2来表示实线、点线和虚线样式。

1366dbfbf6dc9ee3be309121d990917d.png

Simple Color Representation

程序 fields.c 中的程序使用boxprops结构,该程序用#define创建供结构成员使用的符号常量。注意,只打开一位即可表示三原色之一。其他颜色用三原色的组合来表示。例如,紫色由打开的蓝色位和红色位组成,所以,紫色可表示为BLUE|RED。

The fields.c Program

/* fields.c -- define and use fields */#include #include    //C99, defines bool, true, false/* line styles     */#define SOLID   0#define DOTTED  1#define DASHED  2/* primary colors  */#define BLUE    4#define GREEN   2#define RED     1/* mixed colors    */#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;  // or unsigned int (pre C99)    unsigned int fill_color     : 3;    unsigned int                : 4;    bool show_border            : 1;  // or unsigned int (pre 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){    /* create and initialize box_props structure */    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。

2 位字段和按位运算符

在同类型的编程问题中,位字段和按位运算符是两种可替换的方法,用哪种方法都可以。例如,前面的例子中,使用和unsigned int类型大小相同的结构存储图形框的信息。也可使用unsigned int变量存储相同的信息。如果不想用结构成员表示法来访问不同的部分,也可以使用按位运算符来操作。一般而言,这种方法比较麻烦。接下来,我们来研究这两种方法(程序中使用了这两种方法,仅为了解释它们的区别,我们并不鼓励这样做)。 可以通过一个联合把结构方法和位方法放在一起。假定声明了struct boxprops类型,然后这样声明联合:

union Views     /* look at data as struct or as unsigned short */{    struct box_props st_view;    unsigned short us_view;};

在某些系统中,unsigned int和boxprops类型的结构都占用16位内存。但是,在其他系统中(例如我们使用的系统),unsigned int和boxprops都是32位。无论哪种情况,通过联合,都可以使用stview成员把一块内存看作是一个结构,或者使用usview成员把相同的内存块看作是一个unsigned short。结构的哪一个位字段与unsigned short中的哪一位对应?这取决于实现和硬件。下面的程序示例假设从字节的低阶位端到高阶位端载入结构。也就是说,结构中的第1个位字段对应计算机字的0号位(为简化起见,图15.3以16位单元演示了这种情况)。

07358b9902d3e98e818e71f93df1b470.png

A union as an integer and as a structure.

程序dualview.c使用Views联合来比较位字段和按位运算符这两种方法。在该程序中,box是Views联合,所以box.stview是一个使用位字段的boxprops类型的结构,box.usview把相同的数据看作是一个unsigned short类型的变量。联合只允许初始化第1个成员,所以初始化值必须与结构相匹配。该程序分别通过两个函数显示box的属性,一个函数接受一个结构,一个函数接受一个unsigned short类型的值。这两种方法都能访问数据,但是所用的技术不同。该程序还使用了本章前面定义的itobs()函数,以二进制字符串形式显示数据,以便读者查看每个位的开闭情况。

The dualview.c Program

/* dualview.c -- bit fields and bitwise operators */#include #include #include /* BIT-FIELD CONSTANTS *//* line styles     */#define SOLID   0#define DOTTED  1#define DASHED  2/* primary colors  */#define BLUE    4#define GREEN   2#define RED     1/* mixed colors    */#define BLACK   0#define YELLOW  (RED | GREEN)#define MAGENTA (RED | BLUE)#define CYAN    (GREEN | BLUE)#define WHITE   (RED | GREEN | BLUE)​/* BITWISE CONSTANTS   */#define OPAQUE          0x1#define FILL_BLUE       0x8#define FILL_GREEN      0x4#define FILL_RED        0x2#define FILL_MASK       0xE#define BORDER          0x100#define BORDER_BLUE     0x800#define BORDER_GREEN    0x400#define BORDER_RED      0x200#define BORDER_MASK     0xE00#define B_SOLID         0#define B_DOTTED        0x1000#define B_DASHED        0x2000#define STYLE_MASK      0x3000​const char * colors[8] = {"black", "red", "green", "yellow",    "blue", "magenta", "cyan", "white"};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;};​union Views     /* look at data as struct or as unsigned short */{    struct box_props st_view;    unsigned short us_view;};​void show_settings(const struct box_props * pb);void show_settings1(unsigned short);char * itobs(int n, char * ps);​int main(void){    /* create Views object, initialize struct box view */    union Views box = {{true, YELLOW , true, GREEN, DASHED}};    char bin_str[8 * sizeof(unsigned int) + 1];​    printf("Original box settings:n");    show_settings(&box.st_view);    printf("nBox settings using unsigned int view:n");    show_settings1(box.us_view);​    printf("bits are %sn",           itobs(box.us_view,bin_str));    box.us_view &= ~FILL_MASK;          /* clear fill bits */    box.us_view |= (FILL_BLUE | FILL_GREEN); /* reset fill */    box.us_view ^= OPAQUE;               /* toggle opacity */    box.us_view |= BORDER_RED;           /* wrong approach */    box.us_view &= ~STYLE_MASK;        /* clear style bits */    box.us_view |= B_DOTTED;        /* set style to dotted */    printf("nModified box settings:n");    show_settings(&box.st_view);    printf("nBox settings using unsigned int view:n");    show_settings1(box.us_view);    printf("bits are %sn",           itobs(box.us_view,bin_str));​    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");    }}​void show_settings1(unsigned short us){    printf("box is %s.n",           (us & OPAQUE) == OPAQUE? "opaque": "transparent");    printf("The fill color is %s.n",           colors[(us >> 1) & 07]);    printf("Border %s.n",           (us & BORDER) == BORDER? "shown" : "not shown");    printf ("The border style is ");    switch(us & STYLE_MASK)    {        case B_SOLID  : printf("solid.n"); break;        case B_DOTTED : printf("dotted.n"); break;        case B_DASHED : printf("dashed.n"); break;        default       : printf("unknown type.n");    }    printf("The border color is %s.n",           colors[(us >> 9) & 07]);​}​char * itobs(int n, char * ps){    int i;    const static int size = CHAR_BIT * sizeof(int);​    for (i = size - 1; i >= 0; i--, n >>= 1)        ps[i] = (01 & n) + '0';    ps[size] = '0';​    return ps;}

下面是该程序的输出:

Original box settings: Box is opaque. The fill color is yellow. Border shown. The border color is green. The border style is dashed.

Box settings using unsigned int view: box is opaque. The fill color is yellow. Border shown. The border style is dashed. The border color is green. bits are 00000000000000000010010100000111

Modified box settings: Box is transparent. The fill color is cyan. Border shown. The border color is yellow. The border style is dotted.

Box settings using unsigned int view: box is transparent. The fill color is cyan. Border not shown. The border style is dotted. The border color is yellow. bits are 00000000000000000001011100001100

这里要讨论几个要点。位字段视图和按位视图的区别是,按位视图需要位置信息。例如,程序中使用BLUE表示蓝色,该符号常量的数值为4。但是,由于结构排列数据的方式,实际存储蓝色设置的是3号位(位的编号从0开始,参见图15.1),而且存储边框为蓝色的设置是11号位。因此,该程序定义了一些新的符号常量:

#define FILL_BLUE       0x8#define BORDER_BLUE     0x800

这里,0x8是3号位为1时的值,0x800是11号位为1时的值。可以使用第1个符号常量设置填充色的蓝色位,用第2个符号常量设置边框颜色的蓝色位。用十六进制记数法更容易看出要设置二进制的哪一位,由于十六进制的每一位代表二进制的4位,那么0x8的位组合是1000,而0x800的位组合是100000000000,0x800的位组合比0x8后面多8个0。但是以等价的十进制来看就没那么明显,0x8是8,0x800是2048。 如果值是2的幂,那么可以使用左移运算符来表示值。例如,可以用下面的#define分别替换上面的#define:

#define FILL_BLUE       1<<3#define BORDER_BLUE     1<<11

这里,<

enum { OPAQUE = 0x1, FILL_BLUE = 0x8, FILL_GREEN = 0x4, FILL_RED = 0x2,       FILL_MASK = 0xE, BORDER = 0x100, BORDER_BLUE = 0x800,       BORDER_GREEN = 0x400, BORDER_RED = 0x200, BORDER_MASK = 0xE00,       B_DOTTED = 0x1000, B_DASHED = 0x2000, STYLE_MASK = 0x3000};

如果不想创建枚举变量,就不用在声明中使用标记。注意,按位运算符改变设置更加复杂。例如,要设置填充色为青色。只打开蓝色位和绿色位是不够的:

box.us_view |= (FILL_BLUE | FILL_GREEN); /* reset fill */

问题是该颜色还依赖于红色位的设置。如果已经设置了该位(比如对于黄色),这行代码保留了红色位的设置,而且还设置了蓝色位和绿色位,结果是产生白色。解决这个问题最简单的方法是在设置新值前关闭所有的颜色位。因此,程序中使用了下面两行代码:

box.us_view &= ~FILL_MASK;          /* clear fill bits */box.us_view |= (FILL_BLUE | FILL_GREEN); /* reset fill */

如果不先关闭所有的相关位,程序中演示了这种情况:

box.us_view |= BORDER_RED;           /* wrong approach */

因为BORDERGREEN位已经设置过了,所以结果颜色是BORDERGREEN |BORDERRED,被解释为黄色。这种情况下,位字段版本更简单:

box.st_view.fill_color = CYAN;  /*bit-field equivalent */

这种方法不用先清空所有的位。而且,使用位字段成员时,可以为边框和框内填充色使用相同的颜色值。但是用按位运算符的方法则要使用不同的值(这些值反映实际位的位置)。 其次,比较下面两个打印语句:

printf("The border color is %s.n", colors[pb->border_color]);printf("The border color is %s.n", colors[(us >> 9) & 07]);

第1条语句中,表达式pb->bordercolor的值在0~7的范围内,所以该表达式可用作colors数组的索引。用按位运算符获得相同的信息更加复杂。一种方法是使用us>>9把边框颜色右移至最右端(0号位~2号位),然后把该值与掩码07组合,关闭除了最右端3位以外所有的位。这样结果也在0~7的范围内,可作为colors数组的索引。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值