所谓引用类型
(reference type)特指slice、map、channel这三种预定义类型。
相比数字、数组等类型,引用类型拥有更复杂的存储结构。除分配内存外,它们还须初始化一系列属性,诸如指针、长度,甚至包括哈希分布、数据队列等。
内置函数new按指定类型长度分配零值内存,返回指针,并不关心类型内部构造和初始化方式。而引用类型则必须使用make函数创建,编译器会将make转换为目标类型专用的创建函数(或指令),以确保完成全部内存分配和相关属性初始化。
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]) } ```c $go build-gcflags"-l" // 禁用函数内联 $go tool objdump-s"main\.mk"test TEXT main.mkslice(SB)test.go CALL runtime.makeslice(SB) TEXT main.mkmap(SB)test.go CALL runtime.makemap(SB)
当然,new函数也可为引用类型分配内存,但这是不完整创建。以字典(map)为例,它仅分配了字典类型本身(实际就是个指针包装)所需内存,并没有分配键值存储内存,也没有初始化散列桶等内部属性,因此它无法正常工作。 ```cpp 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强制要求使用显式类型转换。加上不支持操作符重载,所以我们总是能确定语句及表达式的明确含义。
a:=10 b:=byte(a) c:=a+int(b) // 混合类型表达式必须确保类型一致
同样不能将非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) // 输出二进制标记位 }
和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") }
即便指定了基础类型,也只表明它们有相同底层数据结构,两者间不存在任何关系,属完全不同的两种类型。除操作符外,自定义类型不会继承基础类型的其他信息(包括方法)。不能视作别名,不能隐式转换,不能直接用于比较表达式。
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(d==x) // 错误:invalid operation:d==x(mismatched types data and int) }
与有明确标识符的bool、int、string等类型相比,数组、切片、字典、通道等类型与具体元素类型或长度等属性有关,故称作未命名类型(unnamed type)。当然,可用type为其提供具体名称,将其改变为命名类型(named type)。
具有相同声明的未命名类型被视作同一类型。
具有相同基类型的指针。
具有相同元素类型和长度的数组(array)。
具有相同元素类型的切片(slice)。
具有相同键值类型的字典(map)。
具有相同数据类型及操作方向的通道(channel)。
具有相同字段序列(字段名、字段类型、标签,以及字段顺序)的结构体(struct)。
具有相同签名(参数和返回值列表,不包括参数名)的函数(func)。
具有相同方法集(方法名、方法签名,不包括顺序)的接口(interface)。
相关类型会在后续章节做详细说明,此处无须了解更多细节。
容易被忽视的是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) }