1、枚举
Go 并没有明确意义上的 enum 定义,不过可借助 iota 标识符实现一组自增常量值来实现枚举类型。
const (
x = iota // 0
y // 1
z // 2
)
const (
_ = iota // 0
KB = 1 << (10 * iota) // 1 << (10 * 1)
MB // 1 << (10 * 2)
GB // 1 << (10 * 3)
)
自增作用范围为常量组,可在多常量定义中使用多个 iota,它们各自单独计数,只续确保组中每行常量的列数量相同即可。
const (
_, _ = iota, iota * 10 // 0, 0 * 10
a, b // 1, 1 * 10
c, d // 2, 2 * 10
)
如中断 iota 自增,须必须显示恢复,且后续自增值按行序递增,而非 C enum 那般按上一取值递增。
const (
a = iota // 0
b // 1
c = 100 // 100
d // 100 (与上一行常量右值表达式相同)
e = iota // 4 (恢复 iota 自增,计数包括 c, d)
f // 5
)
不同于变量在运行期分配存储内存(非优化状态),常量通常会被编译器在预处理阶段直接展开,作为指令数据使用。
var x = 0x100
const y = 0x200
func main() {
println(&x, x)
println(&y, y) // 错误: cannot take the address of y
}
2、基本类型
在使用浮点数时,需要注意小数位的有效精度。
package main
import "fmt"
func main() {
var a float32 = 1.1234567899
var b float32 = 1.12345678
var c float32 = 1.123456781
println(a, b, c)
println(a == b, a == c)
fmt.Printf("%v %v, %v\n", a, b, c)
}
其执行结果为:
在官方的语言规范中,专门提到两个别名。
byte alias for uint8
rune alias for int32
别名类型无须转化,可直接赋值。
package main
func test(x byte) {
println(x)
}
func main() {
var a byte = 0x11
var b uint8 = a
var c uint8 = a + b
test(c)
}
其结果输出为:
34
3、引用类型
所谓引用类型特指 slice、map、channel 这三种预定义类型。
相比数字、数组等类型,引用类型拥有更复杂的存储结构。除分配内存外,它们还须初始化一系列属性,诸如指针、长度,甚至包括哈希分布、数据队列等。
内置函数 new 按指定类型长度分配零值内存,返回指针,并不关心类型内部构造和初始化方式。而引用类型则必须使用 make 函数创建,编译器会将 make 转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。
当然, new 函数也可为引用类型分配内存,但这是不完整创建。以字典(map) 为例,它进分配了字典类型本身(实际就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作。
package main
import "fmt"
func main() {
p := new(map[string]int)
m := *p
m["a"] = 1 // panic: panic: assignment to entry in nil map (运行错误)
fmt.Println(m)
}
4、类型转换
如果转换的目标是指针、单向通道或没有返回值的函数类型,那么必须使用括号,以避免造成语法分解错误。
package main
func main() {
x := 100
p := *int(&x) // 错误: cannot convert &x (type *int) to type int
// invalid indirect of int(&x) (type int)
println(p)
}
正确的做法是用括号,让编译器将 *int 解析为指针类型。
(*int)(p) // 如果没有括号 ----> *(int(p))
(<-chan int)(c) // <-(chan int(c))
(func())(x) // func() x
func() int(x) // ---> 有返回值的函数类型可省略括号,但依然建议使用。
(func()int)(x) // 使用括号后,更易阅读
5、自定义类型
使用关键字type 定义用户自定义类型,包括基于现有基础类型创建,或者是结构体、函数类型等。
package main
import "fmt"
type flags = byte
const (
read flags = 1 << iota
write
exec
)
func main() {
f := read | exec
fmt.Printf("%b\n", f)
}
输出:
101
即便指定了基础类型、也只表明它们有相同底层数据结构、两者间不存在任何关系,属完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视作别名,不能隐式转换,不能直接用于比较表达式。
package main
func main() {
type data int
var d data = 10
var x int = d // 错误: cannot use d (type data) as type int in assignment
println(x)
println(x == d) // 错误: invalid operation: x == d (mismatched types int and data)
}