预计阅读本页时间:-
对位进行操作的第二种方法是使用位字段(bit field),位字段是一个signed int或unsigned int中一组相邻的位(C99还允许_Bool类型位字段)。位字段由一个结构声明建立,该结构声明为每个字段提供标签,并决定字段的宽度。例如,以下声明建立了 4个1位字段:
广告:个人专属 VPN,独立 IP,无限流量,多机房切换,还可以屏蔽广告和恶意软件,每月最低仅 5 美元
该定义使prnt包含4个1位字段。现在,您可以使用普通的结构成员运算符将值赋给单独的字段:
因为每个字段都正好为1位,所以1和0是惟一可以用于赋值的值。变量prnt被存储在一个int大小的存储单元中,但是在本例中仅有其中的4位被使用。
带有位字段的结构提供一种保存设置的方便的方法。许多设置,如字体的粗体或斜体,是简单的二选一问题,例如打开或关闭,是或否,真或假。在您只需要单个位时,不需要使用整个变量。带有位字段的结构允许您在单个单元中存储多项设置。
有时,对于某个设置有两个以上的选择,因此您需要用多位来表示所有的选择。因为字段不限于1位大小,所以这不是一个问题。您可以使用如下代码:
这段代码创建两个2位字段和一个8位字段。您可以使用以下方法进行赋值:
只须确保值没有超出字段的容量。
如果您所声明的总位数超过一个unsigned int大小,那么将会发生什么?那将会使用下一个unsigned int存储位置。不允许一个字段跨越两个unsigned int之间的边界。编译器自动地移位一个这样的字段定义,使字段按unsigned int边界对齐。发生这种情况时,会在第一个unsigned int中留下一个未命名的洞。
您可以使用未命名的字段宽度“填充”未命名的洞。使用一个宽度为0的未命名的字段迫使下一个字段与下一个整数对齐:
这里,stuff.fieldl和stuff.field2之间有一个2位的间隙,stuff.field3存储在下一个int中。
一个重要的机器依赖性是将字段放置到一个int中的顺序。在有些机器上,这个顺序是从左向右;在另一些机器上顺序是从右向左。另外,不同机器在两个字段间边界的位置上也有区别。由于这些原因,位字段往往难以移植。典型地,把它们用于不可移植的用途,例如按照某个特定硬件设备所使用的确切格式来存放数据。
15.4.1 位字段实例
位字段通常作为存储数据的一个更加紧凑的方法。例如,假设您决定表示一个在屏幕上的方框的属性。让我们使问题更简单,假设方框具有如下属性:
● 框是不透明的或透明的。
● 框的填充色选自以下调色板:黑色、红色、绿色、黄色、蓝色、紫色、青色或白色。
● 边框可见或隐藏。
● 边框颜色与填充色使用相同的调色板。
● 边框可以使用实线、点线或虚线样式。
您可以使用一个单独的变量或全长结构成员来表示每个属性,但是这样做有点浪费数据位。例如,您只需要1位来指明方框是不透明还是透明,只需要1位来指明边框是显示还是隐藏。可以使用3位单元的8个可能值来表示8种可能的颜色值,并且2位单元也足以表示3种可能的边框样式。那么,总共10位就足够表示这5个属性所有可能的设置。
下面是这些信息的一种可能的表示方式;struct box_props声明使用未命名字段将与填充有关的信息放置在一个字节中,将与边框有关的信息放置在第二个字节中。struct box_props的声明如下:
未命名字段的填充使该结构总共占用了 16位。如果没有填充,那么该结构将为10位。然而,请记住,C使用unsigned int作为位字段结构的基本布局单元。因此,即使一个结构的惟一成员是1位字段,该结构的大小也与一个unsigned int的大小相同,unsigned int在我们的系统中为32位。
您可以令成员opaque使用值1指明该框是不透明的,使用值0指明该框透明。可以对成员show_border使用同样的方法。对于颜色,您可以使用简单的RGB(代表red-green-blue)表示。这些颜色是混合光的三原色。监视器混合红、绿、蓝像素以重新产生不同的颜色。在早期的计算机色彩中,每个像素可以是打开或关闭状态,所以您可以使用1位来表示三原色中每个二进制颜色的亮度。常用的顺序是左侧位表示蓝色亮度,中间的位表示绿色亮度,右侧位表示红色亮度。表15.3显示了 8种可能的组合。这些组合可以作为成员fill_color和border_color的值来使用。最后,您可以选择让0、1和2表示实线、点线和虚线样式;它们可以作为成员border_style的值来使用。
表15.3 简单的颜色表示法
位 组 合 | 十 进 制 | 颜 色 |
---|---|---|
000 | 0 | 黑色 |
001 | 1 | 红色 |
010 | 2 | 绿色 |
011 | 3 | 黄色 |
100 | 4 | 蓝色 |
101 | 5 | 紫色 |
110 | 6 | 青色 |
111 | 7 | 白色 |
程序清单15.3在一个简单的示例程序中使用结构box_props。它使用#define为可能的成员值创建符号常量。请注意,通过仅打开1位来表示三原色,可以使用三原色的组合来表示其他颜色。例如,紫色包含打开的蓝色位和红色位,因此可以使用BLUEIRED来表示紫色。
程序清单15.3 fields.c程序
下面输出
这里有几点需要注意。第一,可以使用与初始化普通的结构相同的语法来初始化一个位字段结构:
类似地,您可以为位字段成员赋值:
您还可以使用位字段成员作为switch语句的值表达式。您甚至可以把位字段成员用作一个数组索引:
请注意,数组colors的定义使得每个数组索引对应于一个表示颜色的字符串,而该颜色又将这个索引值作为其数字颜色值。例如,数组索引1对应于字符串“red”,并且红色的颜色值也为1。
15.4.2 位字段和位运算符
位字段和位运算符对于同类的编程问题是两种可供选择的方法。也就是说,您通常可以使用其中任何一种方法。例如,在前面的实例中,用大小为unsigned int的结构存放关于一个图形框的信息。作为代替,您也可以使用unsigned int变量来保存相同的信息。这样,不是使用结构成员符号来访问不同的部分,而是使用位运算符来达到这个目的。通常这种方法稍微麻烦一些。让我们来研究同时使用这两种方法的实例(两种方法都使用的原因是为了解释其中的不同,而不是暗示同时使用这两种方法是一个好主意)。
您可以使用一个联合来组合使用结构方法和位方法。给定struct box_props类型的定义,您可以声明以下联合:
在某些系统上,一个unsigned int与一个box_props结构都占用16位的内存。在其他系统上,例如作者的系统上,unsigned int和box_props都为32位。在每种情况中,通过该联合,您都可以用成员st_view将一块内存看作一个结构,或者使用成员ui_view将相同的内存块看作一个unsigned int。结构的哪一个位字段与unsigned int中的哪一位相对应?这依赖于实现和硬件。在使用Microsoft Visual C/C++ 7.1的IBM PC上,从低位端向高位端将结构载入内存。换句话说,结构中的第一个位字段放入字的0位(为了简单,图15.3使用16位单元解释这种思想)。
图15.3 整数和结构的联合
程序清单15.4通过使用Views联合比较位字段方法和位运算方法。在该程序清单中,box是一个Views联合,因此box.st_view是一个使用位字段的box_prop结构,box.ui_view把相同的数据看作一个unsigned int。
记得一个联合允许其第一个成员被初始化,因此,初始化值和结构相匹配。程序使用一个基于结构的函数和一个基于unsigned int的函数来显示box属性。两种方式都允许你访问数据,但是技术不同。该程序也使用本章前面定义的itobs()函数以二进制字符串形式显示数据,这样您可以看到哪些位是打开的,哪些位是关闭的。
程序清单15.4 dualview.c程序
下面是输出:
这里有几点需要讨论。位字段和按位视图之间的一个区别是按位视图需要位置信息。例如,我们使用BLUE表示蓝色。这个常量的数字值为4。但是,由于在结构中数据的排列方式,实际保存填充色的蓝色设置的位是位3 (请记住,编号方式从0开始,参见图15.1),而且保存边框颜色的蓝色设置的位是位11。因此,该程序定义了一些新的常量:
这里,如果仅把位3设为1,那么值为0x 8;如果仅把位11设为1,那么值为0x800。您可以使用第一个常量设置填充色的蓝色位,使用第二个常量设置边框颜色的蓝色位。使用十六进制的表示方法很容易看出哪些位相关。记得每个十六进制的数字代表4位。因此,0x800和0x8具有相同的位模式,只是后边8位都填上0。但在它们的等值的十进制数看来,2048和8之间的关系就不那么明显了。
如果值为2的幂,那么您可以使用左移运算符来提供值。例如,您可以使用以下语句来代替上面的#define语句:
这里,第二个操作数是2的幂次。也就是说,0×8是2的3次幂,0×800是2的11次幂。同样地,表达式1<<n是第n位设为1的整数的值。表达式1<<11是常量表达式,在编译时对其求值。
您可以使用枚举的方法代替#define来创建符号常量。例如,您可以使用以下语句:
如果您不想创建枚举变量,那么就不需要在声明中使用标记。
请注意,使用位运算符改变设置更为复杂。例如,考虑将填充色设为青色。仅仅打开蓝色位和绿色位是不够的:
问题是该颜色也依赖于红色位设置。如果已经设置了该位(比如对于黄色),这段代码保留了红色位设置,并设置了蓝色和绿色位,结果是产生白色。解决该问题最简单的方法是在设置新值之前首先将所有颜色位关闭。这就是程序使用下面代码的原因:
为了展示如果你不首先清除相关位的结果,程序还做了如下事情:
由于BORDER_GREEN位已经设置过,结果颜色就是BORDER_GREEN IBORDER_RED,这被解释为黄色。在这种情况下,使用位字段方法更简单:
您不需要首先清空所有位。而且,使用位字段成员时,您可以对填充色和边框颜色使用相同的颜色值;但是对于位运算符方法,您需要使用不同的值(这些值反映实际位的位置)。
其次,比较下面两个打印语句:
在第一个语句中,表达式pb->border_color值的范围是0到7,因此该表达式可以作为数组colors的索引。使用位运算符获得相同的信息更加复杂。一种方法是使用ui>>9将边框颜色位右移到该值的最右端(位0到2)。然后,将该值与掩码07组合,因此关闭了最右端3位以外所有的位。结果也是在0到7的范围内,可以作为数组colors的索引。