第15章 位操作 15.4 位字段

对位进行操作的第二种方法是使用位字段(bit field),位字段是一个signed int 或unsigned int中一组相邻的位。

位字段由一个结构声明建立,该结构声明为每一个字段提供标签,并决定字段的宽度。例如,以下声明建立了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位大小,所以这不是问题,您可以使用如下的代码:

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大小,那将会使用下一个unsigned int 存储位置。不允许一个字段跨越两个unsigned int之间的边界。编译器自动的移位一个这样的字段定义,使字段按unsigned int 边界对齐。发生这种情况时,会在第一个unsigned int中留下一个未命名的洞。

您可以使用未命名的字段宽度“填充”未命名的洞。使用一个宽度为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存储在下一个int中。

一个重要的机器依赖性是将字段放置到一个int中的顺序。在有些机器上,这个顺序是从左到右;在另一些机器上顺序是从右到左。另外 ,不同机器在两个字段间边界的位置上也有区别。由于这些原因,位字段往往难以移植。典型地,把它们用于不可移填的用途,例如按照某个特定硬件设备所使用的确切格式来存放数据。

15.4.1

位字段通常作为存储数据的一个更加紧凑的方法。例如,假设您决定表示一个在屏幕上的方框的属性。

让我们使问题更简单,假设方框有以下属性:

1、框是不透明的或是透明的;

2、框的填充色选自以下调色板:黑、红、绿、黄、紫、青或白色。

3、边框可见或隐藏;

4、边框颜色与填充色使用相同的调色板。

5、边框可以使用实线、点线或虚线样式。

您只需要1 位来指明边框是透明还是不透明,只需要1位来指明边框是显示还是隐藏。使用使用3位单元的8个可能值来表示8种不同的颜色值,并且2位单元也足以表示3位可能的边框样式。那么总共10位就足够表示这5个属性所有可能的设置。

下面是这些信息的一种可能的表示方式:struct box_props声明使用未命名字段将与填充有关的信息放置在一个字节中,将与边框有关的信息放置在第二个字节中。struct box_props的声明如下:

struct box_props{
unsigned int opaque:1;
unsigned int fill_color:8;
unsigned int           :4;
unsigned int 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位。

您可以令成员opaque使用值1指明该框是不透明的,使用值0指明该框是透明的。可以对成员show_border使用同样的方法。对于颜色您可以使用RGB表示。这些颜色是混合光的三原色。监视器混合红、绿、蓝像素以重新产生不同的颜色。您可以使用1位表示三原色中每个二进制颜色的亮度。常用的顺序是左侧位表示蓝色亮度,中间位表示绿色亮度,右侧位表示红色亮度。表15.3显示了8种可能的组合。这些组合可以做为成员fill_color和border_color的值来使用。最后,您可以选择让0、1、2表示实线、点线和虚线的样式;它们可以作为成员border_style的值来使用。

简单的颜色表示法

位组合十进制颜色
0000黑色
0011红色
0102绿色
0113黄色
1004蓝色
1015紫色
1106青色
1117白色

程序清单15.3在一个简单的示例程序中使用结构box_props。它使用#define为可能的成员值创建符号常量。请注意,通过仅打开1位来表示三原色,可以使用三原色的组合来表示其他颜色。例如,紫色包括打开的蓝色位和红色位,因此可以使用BLUE | RED来表示紫色。

#include <stdio.h>
/*是否透明和是否可见*/
#define YES 1
#define NO 0
/*边框线的样式*/
#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{
    unsigned int opaque       :1;
    unsigned int fill_color   :3;
    unsigned int              :4;
    unsigned int show_border  :1;
    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 = {YES,YELLOW,YES,GREEN,DASHED};

    printf("Original box settings:\n");
    show_settings(&box);

    box.opaque=NO;
    box.fill_color=WHITE;
    box.border_color=MAGENTA;
    box.border_style=SOLID;
    printf("\n Modified box settings:\n");
    show_settings(&box);

    return 0;
}

void show_settings(const struct box_props *pb)
{
    printf("Box is %s.\n",pb->opaque==YES? "opaque" : "transparent");
    printf("The fill color is %s.\n",colors[pb->fill_color]);
    printf("Border %s.\n",pb->show_border==YES? "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");
    }
}

这里有几点需要注意:第一,可以使用与初始化一个普通的结构相同的语法来初始化一个位字段结构:

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”,并且红色的颜色值也为1。

15.4.2 位字段和位运算符

位字段和位运算符对于同类的编程问题有两种可供选择的方法。也就是说,您通常可以使用其中任何一种方法。让我们来研究同时使用这两种方法的实例(两种方法都使用的原因是为了解释其中的不同,而不是暗示同时使用这两种方法是一个好主意)。

您可以使用联合来组合使用结构方法和位方法。给定struct box_props类型的定义,您可以声明以下联合:

union views /*把数据看作结构或unsigned short变量*/
{
    struct box_props st_view;
    unsigned int ui_view;
}

程序清单15.4通过使用views联合比较位字段和位运算方法。在该程序清单中,box是一个views联合,因此box.st_view是一个使用位字段的box_props结构,box.ui_view把相同的数据看作一个unsigned int。

记得一个联合允许其第一个成员被初始化,因此,初始化值和结构相匹配。程序使用一个基于结构的函数和一个基于unsigned int的函数来显示box属性。两种方式都允许你访问数据,但技术不同。该程序也使用本章前面定义的itobs()函数以二进制字符串形式显示数据,这样您可以看到哪些位是打开的哪些位是关闭的。

#include <stdio.h>
/*位字段常量*/
/*是否透明和是否可见*/
#define YES 1
#define NO 0
/*边框线的样式*/
#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)

/*位运算中使用的常量*/
#define OPAQUE 0x1
#define FILL_BLUE 0x8
#define FILL_GREEN 0x4
#define FILL_RED 0x2
#define FILL_MARK 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{
    unsigned int opaque       :1;
    unsigned int fill_color   :3;
    unsigned int              :4;
    unsigned int show_border  :1;
    unsigned int border_color :3;
    unsigned int border_style :2;
    unsigned int              :2;
    };

union Views /*把数据看作结构或unsigned short 变量*/
    {
        struct box_props st_view;
        unsigned int ui_View;
    };

void show_settings(const struct box_props * pb);
void show_settings1(unsigned short);
char * itobs(int n,char *ps);    /*把short值以二进制形式显示出来*/

int main(void)
{
    /*创建Views对象,初始化结构box view*/
    union Views box = {{YES,YELLOW,YES,GREEN,DASHED}};
    char bin_str[8*sizeof(unsigned int)+1];

    printf("Original box settings:\n");
    show_settings(&box.st_view);
    printf("\nBox setting using unsigned int view:\n");
    show_settings1(box.ui_View);

    printf("bits are %s\n",itobs(box.ui_View,bin_str));
    box.ui_View &= ~FILL_MARK;  /*把代表填充色的位清0*/
    box.ui_View |= (FILL_BLUE | FILL_GREEN);  /*重置填充色*/
    box.ui_View ^= OPAQUE; /*转置指示是否透明的位*/
    box.ui_View |= BORDER_RED;  /*错误的方法*/
    box.ui_View &= ~STYLE_MASK;  /*清除样式位*/
    box.ui_View |= B_DOTTED;  /*把样式设置为点*/
    printf("\nModified box setting :\n");
    show_settings(&box.st_view);
    printf("\nBox settings using unsigned int view:\n");
    show_settings1(box.ui_View);
    printf("bits are %s\n",itobs(box.ui_View,bin_str));

    return 0;
}

void show_settings(const struct box_props *pb)
{
    printf("Box is %s.\n",pb->opaque==YES? "opaque" : "transparent");
    printf("The fill color is %s.\n",colors[pb->fill_color]);
    printf("Border %s.\n",pb->show_border==YES? "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]);
}

/*把int 转换成二进制字符串*/
char * itobs(int n,char *ps)
{
    int i;
    static int size=8*sizeof(unsigned int);

    for(i=size-1;i>=0;i--,n>>=1)
        ps[i]=(01&n)+'0';
    ps[size]='\0';
    return ps;
}

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

#define FILL_BLUE 0X8
#define BORDER_BLUE 0X800

这里,如果仅把位3设为1,那么值为0x8;如果仅把位11设为1,那么值为0x800。您可以使用第一个常量设置填充色的蓝色位,使用第二个常量设置边框色的蓝色位。使用16进制的表示方法,很容易看出哪些位相关。记得每个16进制的数字代表4位。因此,0x8和0x800具有相同的位模式,只是后面8位都填上0.

如果值为2的幂,那么您可以使用左移运算符来提供值。例如,您可以使用左移运算符来提供值。例如,您可以使用下面的语句来代替上面的#define语句:

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

这里第二个操作数是2的幂次。也就是说0x8是2的3次幂,ox800是2的11次幂。同样地,表达式1<<n是第n位设为1的整数的值。表达式1<<11是常量表达式,在编译时对其求值。

请注意,使用位运算符改变设置更为复杂。例如,考虑将填充色设为青色。仅仅打开蓝色位和绿色位是不够的:

box.ui_view |= (FILL_BLUE | FILL_GREEN); /*重置填充色*/

问题是该颜色也依赖于红色位的设置。如果已经设置了该位(比如对于黄色)这段代码保留有红色位的设置,并设置了蓝色和绿色 ,结果产生白色。解决该问题最简单的方法是在设置新值之前首先将所有的颜色位关闭。这就是程序使用下面代码的原因:

box.ui_view &= ~FILL_MASK;    /*把代表填充色的位清零*/
box.ui_view |= (FILL_BLUE | FILL_GREEN); /*重置填充色*/

为了展示如果你不清除相关位的结果,程序还做了如下的事情:

box.ui_view |= BORDER_RED;    /*错误的方法*/

由于BORDER_GREEN位已经设置过,结果颜色就是BORDER_GREEN | BORDER_RED,这被解释为黄色。这种情况下使用位字段更为简单:

box.st_view.fill_color = CYAN;    /*等价的位字段方法*/

您不需要首先清空所有位。而且,使用位字段成员时,您可以对边框颜色和填充色使用相同的颜色值;但对于运算符而言,您需要使用不同的值(这些值反应实际位的位置)。

其次,比较下面的两个语句:

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

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

转载于:https://my.oschina.net/idreamo/blog/1023348

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值