转为 mdbook

This commit is contained in:
chai2010
2022-08-04 14:58:52 +08:00
parent 7fa86ea953
commit 06a1bdf735
49 changed files with 3695 additions and 236 deletions

View File

@@ -58,9 +58,183 @@ fmt.Println(a, b, c, d) // "1 1 2 2"
如果只是简单地复制右边的常量表达式其实并没有太实用的价值。但是它可以带来其它的特性那就是iota常量生成器语法。
{% include "./ch3-06-1.md" %}
### 3.6.1. iota 常量生成器
{% include "./ch3-06-2.md" %}
常量声明可以使用iota常量生成器初始化它用于生成一组以相似规则初始化的常量但是不用每行都写一遍初始化表达式。在一个const声明语句中在第一个声明的常量所在的行iota将会被置为0然后在每一个有常量声明的行加一。
下面是来自time包的例子它首先定义了一个Weekday命名类型然后为一周的每天定义了一个常量从周日0开始。在其它编程语言中这种类型一般被称为枚举类型。
```Go
type Weekday int
const (
Sunday Weekday = iota
Monday
Tuesday
Wednesday
Thursday
Friday
Saturday
)
```
周日将对应0周一为1如此等等。
我们也可以在复杂的常量表达式中使用iota下面是来自net包的例子用于给一个无符号整数的最低5bit的每个bit指定一个名字
```Go
type Flags uint
const (
FlagUp Flags = 1 << iota // is up
FlagBroadcast // supports broadcast access capability
FlagLoopback // is a loopback interface
FlagPointToPoint // belongs to a point-to-point link
FlagMulticast // supports multicast access capability
)
```
随着iota的递增每个常量对应表达式1 << iota是连续的2的幂分别对应一个bit位置使用这些常量可以用于测试设置或清除对应的bit位的值
<u><i>gopl.io/ch3/netflag</i></u>
```Go
func IsUp(v Flags) bool { return v&FlagUp == FlagUp }
func TurnDown(v *Flags) { *v &^= FlagUp }
func SetBroadcast(v *Flags) { *v |= FlagBroadcast }
func IsCast(v Flags) bool { return v&(FlagBroadcast|FlagMulticast) != 0 }
func main() {
var v Flags = FlagMulticast | FlagUp
fmt.Printf("%b %t\n", v, IsUp(v)) // "10001 true"
TurnDown(&v)
fmt.Printf("%b %t\n", v, IsUp(v)) // "10000 false"
SetBroadcast(&v)
fmt.Printf("%b %t\n", v, IsUp(v)) // "10010 false"
fmt.Printf("%b %t\n", v, IsCast(v)) // "10010 true"
}
```
下面是一个更复杂的例子每个常量都是1024的幂
```Go
const (
_ = 1 << (10 * iota)
KiB // 1024
MiB // 1048576
GiB // 1073741824
TiB // 1099511627776 (exceeds 1 << 32)
PiB // 1125899906842624
EiB // 1152921504606846976
ZiB // 1180591620717411303424 (exceeds 1 << 64)
YiB // 1208925819614629174706176
)
```
不过iota常量生成规则也有其局限性。例如它并不能用于产生1000的幂KB、MB等因为Go语言并没有计算幂的运算符。
**练习 3.13** 编写KB、MB的常量声明然后扩展到YB。
### 3.6.2. 无类型常量
Go语言的常量有个不同寻常之处。虽然一个常量可以有任意一个确定的基础类型例如int或float64或者是类似time.Duration这样命名的基础类型但是许多常量并没有一个明确的基础类型。编译器为这些没有明确基础类型的数字常量提供比基础类型更高精度的算术运算你可以认为至少有256bit的运算精度。这里有六种未明确类型的常量类型分别是无类型的布尔型、无类型的整数、无类型的字符、无类型的浮点数、无类型的复数、无类型的字符串。
通过延迟明确常量的具体类型无类型的常量不仅可以提供更高的运算精度而且可以直接用于更多的表达式而不需要显式的类型转换。例如例子中的ZiB和YiB的值已经超出任何Go语言中整数类型能表达的范围但是它们依然是合法的常量而且像下面的常量表达式依然有效译注YiB/ZiB是在编译期计算出来的并且结果常量是1024是Go语言int变量能有效表示的
```Go
fmt.Println(YiB/ZiB) // "1024"
```
另一个例子math.Pi无类型的浮点数常量可以直接用于任意需要浮点数或复数的地方
```Go
var x float32 = math.Pi
var y float64 = math.Pi
var z complex128 = math.Pi
```
如果math.Pi被确定为特定类型比如float64那么结果精度可能会不一样同时对于需要float32或complex128类型值的地方则会强制需要一个明确的类型转换
```Go
const Pi64 float64 = math.Pi
var x float32 = float32(Pi64)
var y float64 = Pi64
var z complex128 = complex128(Pi64)
```
对于常量面值不同的写法可能会对应不同的类型。例如0、0.0、0i和`\u0000`虽然有着相同的常量值但是它们分别对应无类型的整数、无类型的浮点数、无类型的复数和无类型的字符等不同的常量类型。同样true和false也是无类型的布尔类型字符串面值常量是无类型的字符串类型。
前面说过除法运算符/会根据操作数的类型生成对应类型的结果。因此,不同写法的常量除法表达式可能对应不同的结果:
```Go
var f float64 = 212
fmt.Println((f - 32) * 5 / 9) // "100"; (f - 32) * 5 is a float64
fmt.Println(5 / 9 * (f - 32)) // "0"; 5/9 is an untyped integer, 0
fmt.Println(5.0 / 9.0 * (f - 32)) // "100"; 5.0/9.0 is an untyped float
```
只有常量可以是无类型的。当一个无类型的常量被赋值给一个变量的时候,就像下面的第一行语句,或者出现在有明确类型的变量声明的右边,如下面的其余三行语句,无类型的常量将会被隐式转换为对应的类型,如果转换合法的话。
```Go
var f float64 = 3 + 0i // untyped complex -> float64
f = 2 // untyped integer -> float64
f = 1e123 // untyped floating-point -> float64
f = 'a' // untyped rune -> float64
```
上面的语句相当于:
```Go
var f float64 = float64(3 + 0i)
f = float64(2)
f = float64(1e123)
f = float64('a')
```
无论是隐式或显式转换,将一种类型转换为另一种类型都要求目标可以表示原始值。对于浮点数和复数,可能会有舍入处理:
```Go
const (
deadbeef = 0xdeadbeef // untyped int with value 3735928559
a = uint32(deadbeef) // uint32 with value 3735928559
b = float32(deadbeef) // float32 with value 3735928576 (rounded up)
c = float64(deadbeef) // float64 with value 3735928559 (exact)
d = int32(deadbeef) // compile error: constant overflows int32
e = float64(1e309) // compile error: constant overflows float64
f = uint(-1) // compile error: constant underflows uint
)
```
对于一个没有显式类型的变量声明(包括简短变量声明),常量的形式将隐式决定变量的默认类型,就像下面的例子:
```Go
i := 0 // untyped integer; implicit int(0)
r := '\000' // untyped rune; implicit rune('\000')
f := 0.0 // untyped floating-point; implicit float64(0.0)
c := 0i // untyped complex; implicit complex128(0i)
```
注意有一点不同无类型整数常量转换为int它的内存大小是不确定的但是无类型浮点数和复数常量则转换为内存大小明确的float64和complex128。
如果不知道浮点数类型的内存大小是很难写出正确的数值算法的因此Go语言不存在整型类似的不确定内存大小的浮点数和复数类型。
如果要给变量一个不同的类型,我们必须显式地将无类型的常量转化为所需的类型,或给声明的变量指定明确的类型,像下面例子这样:
```Go
var i = int8(0)
var i int8 = 0
```
当尝试将这些无类型的常量转为一个接口值时见第7章这些默认类型将显得尤为重要因为要靠它们明确接口对应的动态类型。
```Go
fmt.Printf("%T\n", 0) // "int"
fmt.Printf("%T\n", 0.0) // "float64"
fmt.Printf("%T\n", 0i) // "complex128"
fmt.Printf("%T\n", '\000') // "int32" (rune)
```
现在我们已经讲述了Go语言中全部的基础数据类型。下一步将演示如何用基础数据类型组合成数组或结构体等复杂数据类型然后构建用于解决实际编程问题的数据结构这将是第四章的讨论主题。