ch03-1 done

Fixes #51
This commit is contained in:
chai2010 2015-12-20 14:56:13 +08:00
parent b88c28f867
commit 037d393b16

View File

@ -77,4 +77,115 @@ Go语言还提供了以下的bit位操作运算符, 前面4个操作运算符并
位操作运算符 `^` 作为二元运算符时是按位异或(XOR), 当用作一元运算符时表示按位取反; 也就是说, 它返回一个每个bit位都取反的数. 位操作运算符 `&^` 用于按位置零(AND NOT): 表达式 `z = x &^ y` 结果z的bit位1, 如果对应y中bit位为1, 否则对应的bit位等于x相应的bit位的值. 位操作运算符 `^` 作为二元运算符时是按位异或(XOR), 当用作一元运算符时表示按位取反; 也就是说, 它返回一个每个bit位都取反的数. 位操作运算符 `&^` 用于按位置零(AND NOT): 表达式 `z = x &^ y` 结果z的bit位1, 如果对应y中bit位为1, 否则对应的bit位等于x相应的bit位的值.
TODO
下面的代码演示了如何使用位操作解释uint8类型值的8个独立的bit位. 它使用了 Printf 函数的 %b 参数打印二进制格式的数字; 其中 %08b 中08表示打印至少8个数字, 不足的前缀用0填充.
```Go
var x uint8 = 1<<1 | 1<<5
var y uint8 = 1<<1 | 1<<2
fmt.Printf("%08b\n", x) // "00100010", the set {1, 5}
fmt.Printf("%08b\n", y) // "00000110", the set {1, 2}
fmt.Printf("%08b\n", x&y) // "00000010", the intersection {1}
fmt.Printf("%08b\n", x|y) // "00100110", the union {1, 2, 5}
fmt.Printf("%08b\n", x^y) // "00100100", the symmetric difference {2, 5}
fmt.Printf("%08b\n", x&^y) // "00100000", the difference {5}
for i := uint(0); i < 8; i++ {
if x&(1<<i) != 0 { // membership test
fmt.Println(i) // "1", "5"
}
}
fmt.Printf("%08b\n", x<<1) // "01000100", the set {2, 6}
fmt.Printf("%08b\n", x>>1) // "00010001", the set {0, 4}
```
(6.5节给出了一个可以远大于一个字节的整数集的实现.)
在 x<<n x>>n 移位运算中, 决定了移位操作bit数部分必须是无符号数; 被操作的 x 数可以是有符号或无符号数. 算术上, 一个 x<<n 左移运算等价于乘以 2^n, 一个 x>>n 右移运算等价于除以 2^n.
左移运算用零填充右边空缺的bit位, 无符号数的右移运算也是用0填充左边空缺的bit位, 但是有符号数的右移运算会用符号位的值填充左边空缺的bit位. 因为这个原因, 最好用无符号运算, 这样你可以将整数完全当作一个bit位模式处理.
尽管Go提供了无符号数和运算, 即使数值本身不可能出现负数我们还是倾向于使用有符号的int类型, 就是数组的长度那样, 虽然使用 uint 似乎是一个更合理的选择. 事实上, 内置的 len 函数返回一个有符号的int, 我们可以像下面这个逆序循环那样处理.
```Go
medals := []string{"gold", "silver", "bronze"}
for i := len(medals) - 1; i >= 0; i-- {
fmt.Println(medals[i]) // "bronze", "silver", "gold"
}
```
另一个选择将是灾难性的. 如果 len 返回一个无符号数, 那么 i 也将是无符号的 uint, 然后条件 i >= 0 则永远为真. 在三次迭代之后, 也就是 i == 0 时, i-- 语句将不会产生 -1, 而是变成一个uint的最大值(可能是 2^64 - 1), 然后 medals[i] 表达式将发生运行时 panic 异常(§5.9), 也就是试图访问一个切片范围以外的元素.
出于这个原因, 无符号数往往只有在位运算或其它特殊的运算常见才会使用, 就像 bit 集合, 分形二进制文件格式, 或者是哈希和加密操作等. 它们通常并不用于仅仅是表达非负数量的场合.
一般来说, 需要一个显式的转换将一个值从一种类型转化位另一种类型, 并且算术和逻辑运算的二元操作中必须是相同的类型. 虽然这偶尔会导致很长的表达式, 但是它消除了所有的类型相关的问题, 也使得程序容易理解.
从其他类似场景下, 考虑下面这个代码:
```Go
var apples int32 = 1
var oranges int16 = 2
var compote int = apples + oranges // compile error
```
当尝试编译这三个语句时, 将产生一个错误信息:
```
invalid operation: apples + oranges (mismatched types int32 and int16)
```
这种类型不匹配的问题可以有几种不同的方法修复, 最常见方法是将它们都显式转型位一个常见类型:
```Go
var compote = int(apples) + int(oranges)
```
如2.5节所述, 对于每种类型T, 类型转换操作T(x)将x转换位T类型, 如果转换允许的话. 许多 整形数之间的相互转换并不会改变数值; 它们只是告诉编译器如何解释这个值. 但是对于将一个大尺寸的整数类型转位一个小尺寸的整数类型, 或者是将一个浮点数转位整数, 可能会改变数值或丢失精度:
```Go
f := 3.141 // a float64
i := int(f)
fmt.Println(f, i) // "3.141 3"
f = 1.99
fmt.Println(int(f)) // "1"
```
浮点数到整数的转换将丢失任何小数部分, 向数轴零方向截断. 你应该避免操作目标类型表示范围的数值类型转换, 因为截断的行为依赖于具体的实现:
```Go
f := 1e100 // a float64
i := int(f) // 结果依赖于具体实现
```
任何大小的整数字面值都可以用以0开始的八进制格式书写, 例如 0666, 或用以0x或0X开头的十六进制格式书写, 例如 0xdeadbeef. 十六进制数字可以用大写或小写字母. 如今八进制数据通常用于POSIX操作系统上的文件访问权限标志, 十六进制数字则更强调数字值的bit位模式.
当使用 fmt 包打印一个数值时, 我们可以用 %d, %o, 或 %x 控制输出的进制格式, 就像下面的例子:
```Go
o := 0666
fmt.Printf("%d %[1]o %#[1]o\n", o) // "438 666 0666"
x := int64(0xdeadbeef)
fmt.Printf("%d %[1]x %#[1]x %#[1]X\n", x)
// Output:
// 3735928559 deadbeef 0xdeadbeef 0XDEADBEEF
```
请注意 fmt 的两个使用技巧. 通常 Printf 格式化字符串包含多个 % 参数时将对应相同数量的额外操作数, 但是 % 之后的 `[1]` 副词告诉Printf函数再次使用第一个操作数. 第二, % 后的 `#` 副词告诉 Printf 在用 %o, %x 或 %X 输出时生成 0, 0x 或 0X前缀.
字符面值通过一对单引号直接包含对应字符. 最简单的例子是 ASCII 中类似 'a' 字符面值, 但是我们可以通过转义的数值来表示任意的Unicode码点对应的字符, 马上将会看到例子.
字符使用 `%c` 参数打印, 或者是 `%q` 参数打印带单引号的字符:
```Go
ascii := 'a'
unicode := '国'
newline := '\n'
fmt.Printf("%d %[1]c %[1]q\n", ascii) // "97 a 'a'"
fmt.Printf("%d %[1]c %[1]q\n", unicode) // "22269 国 '国'"
fmt.Printf("%d %[1]q\n", newline) // "10 '\n'"
```