About…
C里面的位字段十分有用和强大,想位操作符一样,也是一种直接对数据位operator的一种方法。在《C Primer Plus》一书中,对位字段的解释十分具体和彻底。这里主要对书中内容做一些摘要和总结。
位字段!What?
位字段(bit field)是一个unsigned int或signed int中一组相邻的位。位字段由一组声明结构建立,该结构声明为每个字段提供标签,并且决定字段的宽度。个人理解就是把一个int型拆成以‘位’位单位的字段来用。
建立位字段的几种格式
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;
unsigned int : 0;
};
上面的代码定义格式并不统一。
(1)unsigned int opaque : 1;建立了一个宽度为1位(已经是最小了!)的字段,并且他的标签是opaque(就是name,以后可以通过它来使用这个位);
(2)unsigned int fill_color : 3;这个类比第一个就是宽度变大了,是占3个bit的字段。
(3)unsigned int : 4;这个有些奇怪。竟然没有tag,那怎么用?当然这个是没法用的,只是为了占4个位置(bit)。有时候你定义了28个字段,但是一个int型通常是32位,这时候就会在这个int内存中留下一个4位的‘洞’,是不是就可以用这种定义填充呢!
(4)unsigned int : 0;这个更奇怪,连宽度都没有是什么意思?! 可以这样想:一个int是4个字节组成,也就是32位,加入你定义了一系列的字段,当字段的累计宽度超过了32位,这个时候就应该有一种处理的方式:那将会使用下一个unsigned int的存储位置。不允许一个字段跨越两个unsigned int之间的边界。编译器会自动的移位一个这样的字段定义,使字段按unsigned int边界对齐。使用一个宽度为0的未命名的字段迫使下一个字段与下一个整数对齐!
十分注意!
(引自原文)一个重要的机器依赖性是将字段放置到一个int中的顺序。在有些机器上,这个顺序是从左向右,另一些机器上是从右向左。不同机器在两个字段间边界位置上也有区别。由于这些原因,位字段往往难以移植!典型的,把他们用于不可移植的用途,例如按照某个特定硬件设备所使用的确切格式来存放数据。
一个凸显位字段应用优势的代码示例
位字段占用空间十分小,并且取值只有0 和 1,这就可以用来做许多事,例如你可以规定0是关,1是开,那么他就可以当一个开关。又如在设计一个窗体的时候,是否透明、二选一的style等都是只需要两个状态标志,那么位字段会使得存储的这些数据更加紧凑。另外,在选择窗体的颜色、边框颜色等虽然有多种颜色,依旧可以用稍宽一点的位字段来标记(如有8中颜色那么可以定义宽度为3的位字段:000、001、010、011、100、101、110、111)没错!就是数组电路那一套东西,这不就是C和底层如此接近的原因吗!
窗体设计代码演示:
/*
*C的位字段使用实例
*借助位字段设计一个窗体的属性盒子
*/
#include <stdio.h>
#include <stdlib.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()
{
//创建和初始化属性盒
struct box_props box = {YES, YELLOW, YES, GREEN, DASHED};
printf("原始属性盒子的设置是:\n");
show_settings(&box);
//通过位字段修改属性盒子并输出
box.opaque = NO;
box.fill_color = WHITE;
box.border_color = MAGENTA;
box.border_style = SOLID;
printf("更新后的属性盒子设置是:\n");
show_settings(&box);
system("pause");
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" : "notshown");
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"); break;
}
}
启示
从代码中很明显可以看出位字段与普通变量的用法很相似:
初始化位字段结构体和初始化普通结构体一样。
可以为位字段赋值。
可以把位字段作为switch语句的表达式。
位字段可以作为数组的下标索引。
位的编号是从0开始的,和数组的索引性质一般。
把位字段和位运算符用好,对一些算法的实现很有帮助!但是二者在实现同一目的的过程,差别是巨大的,在不同的情况下使用不同的方式对于书写代码有很大的帮助。希望大家可以有所收获!
参考书目:《C Primer Plus》第五版