- C#高级编程(第10版) C# 6 & .NET Core 1.0 (.NET开发经典名著)
- (美)Christian Nagel
- 2024字
- 2025-02-18 01:50:00
12.2 处理位
如果需要处理的数字有许多位,就可以使用BitArray类和BitVector32结构。BitArray类位于名称空间System.Collections中,BitVector32结构位于名称空间System.Collections.Specialized中。这两种类型最重要的区别是,BitArray类可以重新设置大小,如果事先不知道需要的位数,就可以使用BitArray类,它可以包含非常多的位。BitVector32结构是基于栈的,因此比较快。BitVector32结构仅包含32位,它们存储在一个整数中。
12.2.1 BitArray类
BitArray类是一个引用类型,它包含一个int数组,其中每32位使用一个新整数。这个类的成员如表12-1所示。
表12-1

辅助方法DisplayBits()遍历BitArray,根据位的设置情况,在控制台上显示1或0(代码文件BitArraySample/Program.cs):
public static void DisplayBits(BitArray bits) { foreach (bool bit in bits) { Write(bit ? 1: 0); } }
BitArraySample使用如下依赖项和名称空间:
依赖项:
NETStandard.Library
名称空间:
System System.Collections static System.Console
说明BitArray类的示例创建了一个包含8位的数组,其索引是0~7。SetAll()方法把这8位都设置为true。接着Set()方法把对应于1的位设置为false。除了Set()方法之外,还可以使用索引器,例如,下面的第5个和第7个索引:
var bits1 = new BitArray(8); bits1.SetAll(true); bits1.Set(1, false); bits1[5] = false; bits1[7] = false; Write("initialized: "); DisplayBits(bits1); WriteLine();
这是初始化位的显示结果:
initialized: 10111010
Not()方法会对BitArray类的位取反:
Write(" not "); DisplayBits(bits1); bits1.Not(); Write(" = "); DisplayBits(bits1); WriteLine();
Not()方法的结果是对所有的位取反。如果某位是true,则执行Not()方法的结果就是false,反之亦然。
not 10111010 = 01000101
这里创建了一个新的BitArray类。在构造函数中,因为使用变量bits1初始化数组,所以新数组与旧数组有相同的值。接着把第0、1和4位的值设置为不同的值。在使用Or()方法之前,显示位数组bits1和bits2。Or()方法将改变bits1的值:
var bits2 = new BitArray(bits1); bits2[0] = true; bits2[1] = false; bits2[4] = true; DisplayBits(bits1); Write(" or "); DisplayBits(bits2); Write(" = "); bits1.Or(bits2); DisplayBits(bits1); WriteLine();
使用Or()方法时,从两个输入数组中提取设置位。结果是,如果某位在第一个或第二个数组中设置为true,该位在执行Or()方法后就是true:
01000101 or 10001101 = 11001101
下面使用And()方法作用于位数组bits1和bits2:
DisplayBits(bits2); Write(" and "); DisplayBits(bits1); Write(" = "); bits2.And(bits1); DisplayBits(bits2); WriteLine();
And()方法只把在两个输入数组中都设置为true的位设置为true:
10001101 and 11001101 = 10001101
最后使用Xor()方法进行异或操作:
DisplayBits(bits1); Write(" xor "); DisplayBits(bits2); bits1.Xor(bits2); Write(" = "); DisplayBits(bits1); WriteLine();
使用Xor()方法,只有一个(不能是两个)输入数组的位设置为1,结果位才是1。
11001101 xor 10001101 = 01000000
12.2.2 BitVector32结构
如果事先知道需要的位数,就可以使用BitVector32结构替代BitArray类。BitVector32结构效率较高,因为它是一个值类型,在整数栈上存储位。一个整数可以存储32位。如果需要更多的位,就可以使用多个BitVector32值或BitArray类。BitArray类可以根据需要增大,但BitVector32结构不能。
表12-2列出了BitVector32结构中与BitArray类完全不同的成员。
表12-2

BitVectorSample使用如下依赖项和名称空间:
依赖项:
NETStandard.Library System.Collections.Specialized
名称空间:
System.Collections.Specialized System.Text static System.Console
示例代码用默认构造函数创建了一个BitVector32结构,其中所有的32位都初始化为false。接着创建掩码,以访问位矢量中的位。对CreateMask()方法的第一个调用创建了用来访问第一位的一个掩码。调用CreateMask()方法后,bit1被设置为1。再次调用CreateMask()方法,把第一个掩码作为参数传递给CreateMask()方法,返回用来访问第二位(它是2)的一个掩码。接着,将bit3设置为4,以访问位编号3。bit4的值是8,以访问位编号4。
然后,使用掩码和索引器访问位矢量中的位,并相应地设置字段(代码文件BitArraySample/Program.cs):
var bits1 = new BitVector32(); int bit1 = BitVector32.CreateMask(); int bit2 = BitVector32.CreateMask(bit1); int bit3 = BitVector32.CreateMask(bit2); int bit4 = BitVector32.CreateMask(bit3); int bit5 = BitVector32.CreateMask(bit4); bits1[bit1] = true; bits1[bit2] = false; bits1[bit3] = true; bits1[bit4] = true; bits1[bit5] = true; WriteLine(bits1);
BitVector32结构有一个重写的ToString()方法,它不仅显示类名,还显示1或0,来说明位是否设置了,如下所示:
BitVector32{00000000000000000000000000011101}
除了用CreateMask()方法创建掩码之外,还可以自己定义掩码,也可以一次设置多位。十六进制值abcdef与二进制值101010111100110111101111相同。用这个值定义的所有位都设置了:
bits1[0xabcdef] = true; WriteLine(bits1);
在输出中可以验证设置的位:
BitVector32{00000000101010111100110111101111}
把32位分别放在不同的片段中非常有用。例如,IPv4地址定义为一个4字节的数,该数存储在一个整数中。可以定义4个片段,把这个整数拆分开。在多播IP消息中,使用了几个32位的值。其中一个32位的值放在这些片段中:16位表示源号,8位表示查询器的查询内部码,3位表示查询器的健壮变量,1位表示抑制标志,还有4个保留位。也可以定义自己的位含义,以节省内存。
下面的例子模拟接收到值0x79abcdef,把这个值传送给BitVector32结构的构造函数,从而相应地设置位:
int received = 0x79abcdef; BitVector32 bits2 = new BitVector32(received); WriteLine(bits2);
在控制台上显示了初始化的位:
BitVector32{01111001101010111100110111101111}
接着创建6个片段。第一个片段需要12位,由十六进制值0xfff定义(设置了12位)。片段B需要8位,片段C需要4位,片段D和E需要3位,片段F需要两位。第一次调用CreateSection()方法只是接收0xfff,为最前面的12位分配内存。第二次调用CreateSection()方法时,将第一个片段作为参数传递,从而使下一个片段从第一个片段的结尾处开始。CreateSection()方法返回一个BitVector32. Section类型的值,该类型包含了该片段的偏移量和掩码。
// sections: FF EEE DDD CCCC BBBBBBBB // AAAAAAAAAAAA BitVector32.Section sectionA = BitVector32.CreateSection(0xfff); BitVector32.Section sectionB = BitVector32.CreateSection(0xff, sectionA); BitVector32.Section sectionC = BitVector32.CreateSection(0xf, sectionB); BitVector32.Section sectionD = BitVector32.CreateSection(0x7, sectionC); BitVector32.Section sectionE = BitVector32.CreateSection(0x7, sectionD); BitVector32.Section sectionF = BitVector32.CreateSection(0x3, sectionE);
把一个BitVector32.Section类型的值传递给BitVector32结构的索引器,会返回一个int,它映射到位矢量的片段上。这里使用一个帮助方法IntToBinaryString(),获得该int数的字符串表示:
WriteLine($"Section A: {IntToBinaryString(bits2[sectionA], true)}"); WriteLine($"Section B: {IntToBinaryString(bits2[sectionB], true)}"); WriteLine($"Section C: {IntToBinaryString(bits2[sectionC], true)}"); WriteLine($"Section D: {IntToBinaryString(bits2[sectionD], true)}"); WriteLine($"Section E: {IntToBinaryString(bits2[sectionE], true)}"); WriteLine($"Section F: {IntToBinaryString(bits2[sectionF], true)}");
IntToBinaryString()方法接收整数中的位,并返回一个包含0和1的字符串表示。在实现代码中遍历整数的32位。在迭代过程中,如果该位设置为1,就在StringBuilder的后面追加1,否则,就追加0。在循环中,移动一位,以检查是否设置了下一位。
public static string IntToBinaryString(int bits, bool removeTrailingZero) { var sb = new StringBuilder(32); for (int i = 0; i < 32; i++) { if ((bits & 0x80000000) ! = 0) { sb.Append("1"); } else { sb.Append("0"); } bits = bits << 1; } string s = sb.ToString(); if (removeTrailingZero) { return s.TrimStart('0'); } else { return s; } }
结果显示了片段A~F的位表示,现在可以用传递给位矢量的值来验证:
Section A: 110111101111 Section B: 10111100 Section C: 1010 Section D: 1 Section E: 111 Section F: 1