在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
一、基本类型清晰完备的预定义基础类型,使得开发跨平台应用时无须过多考虑符合和长度差异。
支持八进制、十进制以及科学计数法。标准库 math 定义了各数字类型的取值范围。 import ( "fmt" "math" ) func main() { a, b, c := 100, 0144, 0x64 fmt.Println(a, b, c) fmt.Printf("0b%b, %#o, %#x\n", a, a, a) fmt.Println(math.MinInt8, math.MaxInt8) } 输出: 100 100 100 0b1100100, 0144, 0x64 -128 127 标准库 strconv 可在不同进制(字符串)间转换。 import ( "strconv" ) func main() { a, _ := strconv.ParseInt("1100100", 2, 32) b, _ := strconv.ParseInt("0144", 8, 32) c, _ := strconv.ParseInt("64", 16, 32) println(a, b, c) println("0b" + strconv.FormatInt(a, 2)) println("0" + strconv.FormatInt(a, 8)) println("0x" + strconv.FormatInt(a, 16)) } 输出: 100 100 100 0b1100100 0144 0x64 使用浮点数时,须注意小数位的有效精度,相关细节可参考 IEEE-754 标准。 func main() { var a float32 = 1.1234567899 // 注意:默认浮点数类型是 float64 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) } 输出: +1.123457e+000 +1.123457e+000 +1.123457e+000 true true 1.1234568 1.1234568, 1.1234568 别名在官方的语言规范中,专门提到 两个 别名。
别名类型无须转换,可直接赋值。 func test(x byte) { println(x) } func main() { var a byte = 0x11 var b uint8 = a var c uint8 = a + b test(c) } 但这并不表示,拥有相同底层结构的就属于别名。就算在 64位 平台上 int 和 int64 结构完全一致,也分属不同类型,须显式转换。 func add(x, y int) int { return x + y } func main() { var x int = 100 var y int64 = x // 错误:cannot use x (type int) as type int64 in assignment add(x, y) // 错误:cannot use y (type int64) as type int in argument to add } 二、引用类型所谓引用类型(reference type),特指 slice 、map 、channel 这三种预定义类型。相比 数字 、数组 等类型,引用类型 拥有更复杂的存储结构。除分配内存外,他们还须初始化一系列属性,诸如 指针 、长度 ,甚至包括哈希分布、数据队列等。 内置函数 new() 按指定类型长度分配零值内存,返回指针,并不关心类型内部结构和初始化方式。而 引用类型 则必须使用 make() 函数创建,编译器会将 make() 转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。 // test.go 文件 package main func mkslice() []int { s := make([]int, 0, 10) s = append(s, 100) return s } func mkmap() map[string]int { m := make(map[string]int) m["a"] = 1 return m } func main() { m := mkmap() println(m["a"]) s := mkslice() println(s[0]) } 输出: $ go build -gcflags "-l" // 禁用函数内联 $ go tool objdump -s "main\.mk" test TEXT main.mkslie(SB) test.go CALL runtime.makeslice(SB) TEXT main.mkmap(SB) test.go CALL runtime.makemap(SB)
当然,new() 函数也可为引用类型分配内存,但这是不完整创建。以字典(map)为例,它仅分配了字典类型本身(实际就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作。 import "fmt" func main() { p := new(map[string]int) // 函数 new 返回指针 m := *p m["a"] = 1 // 报错:panic: assignment to entry in nil map [运行期错误] fmt.Println(m) } 三、类型转换隐式转换造成的问题远大于它带来的好处。 除 常量 、别名类型 以及 未命名类型 外,Go 强制要求使用显示类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义。 func main() { a := 10 b := byte(a) c := a + int(b) // 混合类型表达式必须确保类型一致 fmt.Println(c) } 同样不能讲 非bool 类型结果当作 true/false 使用。 func main() { x := 100 var b bool = x // 报错:cannot use x (type int) as type bool in assignment if x { // 报错:non-bool x (type int) used as if condition } } 语法歧义如果转换的目标 指针 、单向通道 或 没有返回值的函数 类型,那么必须使用 括号(),以避免造成语法分解错误。 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 解析为指针类型。
四、自定义类型使用关键字 type 定义用户自定义类型,包括基于现有基础类型创建,或者是 结构体 、函数类型 等。 type flags byte const ( read flags = 1 << iota write exec ) func main() { f := read | exec fmt.Printf("%b\n", f) // 输出二进制标志位 } 输出: 101 和 var 、const 类似,多个 type 定义可以合并成组,可在 函数 或 代码块内定义局部类型。 func main() { type ( // 组 user struct { // 结构体 name string age uint8 } event func(string) bool // 函数类型 ) u := user{"Tom", 20} fmt.Println(u) var f event = func(s string) bool { println(s) return s != "" } f("abc") } 输出: {Tom 20} abc 即便指定了基础类型,也只表明它们有相同底层数据结构,两者间不存在任何关系,属完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视作别名,不能隐式转换,不能直接用于比较表达式。 func main() { type data int var d data = 10 var x int = d // 错误:annot use d (type data) as type int in assignment println(x) println(d == x) // 错误:invalid operation: d == x (mismatched types data and int) } 未命名类型与有明确标识符的 bool 、int 、string 等类型相比,数组 、切片 、字典 、通道 等类型与具体元素类型或长度等属性有关,故称作 未命名类型(unnamed type)。当然,可用 type 为其提供 具体名称,将其改变为 命名类型(named type)。 具有相同声明的未命名类型视作同一类型。
容易被忽视的是 struct tag,它也属于类型组成部分,而不仅仅是元数据描述。 func main() { var a struct { // 匿名结构类型 x int `X` s string `S` } var b struct { x int s string } b = a // 错误:cannot use a (type struct { x int "X"; s string "S" }) as type struct { x int; s string } in assignment fmt.Println(b) } 同样,函数的参数顺序也属签名组成部分。 func main() { var a func(int, string) var b func(string, int) b = a // 错误:cannot use a (type func(int, string)) as type func(string, int) in assignment b("s", 1) } 未命名类型转换规则:
func main() { type data [2]int var d data = [2]int{1, 2} // 基础类型相同,右值为 未命名类型 fmt.Println(d) a := make(chan int, 2) var b chan<- int = a // 双向通道 转换为 单向通道,其中 b 为 未命名类型 b <- 2 } |
请发表评论