类型系统 命名类型 & 未命名类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 package mainimport "fmt" type Person struct { name string age int } func main () { a := struct { name string age int }{"andes" , 18 } fmt.Printf("%T\n" , a) fmt.Printf("%v\n" , a) b := Person{"Tom" , 21 } fmt.Printf("%T\n" , a) fmt.Printf("%v\n" , a) }
底层类型 1 2 3 4 5 6 type T1 string type T2 T1type T3 []string type T4 T3type T5 []T1type T6 T5
其中,T1、T2 底层类型都是 string, T3、T4 底层类型都是[]string,T6 和 T5 的底层类型都是[]T1。特别注意是,这里T6、T5 和T3、T4的底层类型不一样,一个是[]T1,一个是[]string。
类型相同和类型赋值 Go 1.9 引入了类型别名语法 type T1 = T2
,T1 的类型完全和 T2 一样。引入别名主要有如下原因:
未来解决新旧包的迁移兼容问题,比如 context 包先前并不在标准库里面,后面迁移到了标准库。
Go 的按包进行隔离的机制不太精细,有时我们需要将大包划分为几个小包进行开发,但是需要在大包里面保留全部的类型给使用者。
解决新旧类型的迁移问题,新类型先是旧类型的别名,后续的软件都基于类型编程,在合适的时间将新类型升级为旧类型不兼容,常用于软件的柔性升级。
类型可直接赋值
赋值需满足:
T1、T2类型相同
T1和 T2具有相同的底层类型,并且T1、T2里面至少有一个是未命名类型
T2 是接口类型,T1是具体类型,T1的方法集是T2方法集的超集
T1、T2都是通道类型,他们拥有相同的元素类型,并且T1和T2中至少有一个是未命名类型
a 是预声明标识符 nil,T2可以是pointer、function、slice、map、channel、interface类型中的一个
a 是一个字面量值,可以用来标识类型 T 的值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package main import ( "fmt" ) type Map map [string ]string func (m Map) Print () { for _, key := range m { fmt.Println(key) } } type iMap Mapfunc (m iMap) Print () { for _, key := range m { fmt.Println(key) } } type slice []int func (s slice) Print () { for _, v := range s { fmt.Println(v) } } func main () { mp := make (map [string ]string , 10 ) mp["hi" ] = "tata" var ma Map = mp ma.Print() im.Print() var i interface { Print() } = ma i.Print() s1 := []int [1 , 2 , 3 ] var s2 slice s2 = s1 s2.Print() }
类型强制转换
x 可以直接赋值给 T 类型变量
x 的类型和 T 具有相同的底层类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package mainimport ( "fmt" ) type Map map [string ]string func (m Map) Print () { for -, key := range m { fmt.Println(key) } } type iMap Mapfunc (m iMap) Print () { for _, key := range m { fmt.Println(key) } } func main () { mp := make (map [string ]string , 10 ) mp["hi" ] = "tata" var ma Map = mp var im iMap = (iMap) (ma) ma.Print() im.Print() }
x 的类型、T 都是未命名指针类型,并且指针指向相同底层类型
x、T 都是整值、或者都是浮点型
x 的类型和 T 都是复数类型
x 是整数值或 []byte 类型的值,T 是 string 类型
x 是一个字符串,T 是 []byte 或 []rune
字符串和字节切片之间的转换最常见,示例如下:
1 2 3 4 5 6 7 s := "hello,world" var a []byte a = []byte (s) var b string b = string (a) var c []rune c = []rune
注意:
数值类型和 string 类型之间相互转换可能造成值部分丢失
Go 语言没有语言机制支持指针和 interger 之间的直接转换
类型方法 自定义类型 1 2 3 4 type INT int type Map map [string ]string type myMap Map
自定义 struct 类型 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct Xname struct { Field1 type1 } type errorString struct { s string } struct { Field1 type1 } var s = struct {} {}
struct 初始化 1 2 3 4 type Person struct { name string age int }
推荐写法(指定字段名初始化) 1 2 3 4 5 6 7 8 9 10 a := Person{ name: "andes" , age:18 } b := Person{ name: "andes" , age:18 , } c b := Person{ name: "andes" , age:18 }
使用构造函数进行初始化 推荐方法,当结构发生变化时,构造函数可以屏蔽细节。
1 2 3 4 5 6 7 func New (text string ) error { return & errorString{text} } type errorString struct { s string }
结构字段的特点 结构的字段可以是任意类型,结构字段的类型名必须唯一, struct 字段类型可以是普通类型也可以是指针。另外结构支持内嵌自身的指针。
1 2 3 4 5 type Element struct { next, prev *Element list *List Value interface {} }
匿名字段 1 2 3 type File struct { *file }
自定义接口类型 1 2 3 4 var i interface {}type Reader interface { Read(p []byte ) (n int , err error) }
方法 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func (t TypeName) MethodName (ParamList) (Return List) { } func (t *TypeName) MethodName (ParamList) (Return List) { } func TypeName_MethodName (t TypeName, otherParamList) (Return List) { } func TypeName_MethodName (t *TypeName, otherParamList) (Return List) { } type SliceInt []int func (s SliceInt) Sum () int { sum := 0 for _, i := range s { sum += i } return sum } func SliceInt_Sum (s SliceInt) int { sum := 0 for _, i := range s { sum += i } return sum } var s SliceInt = []int {1 , 2 , 3 , 4 }s.Sum() SliceInt_Sum(s)
类型方法有如下特点:
可以为命名类型增加方法(除了接口),非命名类型不能自定义方法。
为类型增加方法有一个限制,方法定义必须和类型定义在同一个包中。
方法的命名空间的可见性和变量一样,大写开头的方法可以在包外被访问,否则只能在保内可见。
使用 type 定义的自定义类型是一个新类型,新类型不能调用原有类型方法,但是底层类型支持的运算可以被新类型继承。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type Map map [string ]string func (m Map) Print () { for _, key := range m { fmt.Println(key) } } type MyInt int func main () { var a MyInt = 10 var b MyInt = 10 c := a + b d := a * b }
方法调用 一般调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 type T struct { a int } func (t T) Get () int { return t.a } func (t *T) Set (i int ) { t.a = i } var t = &T{}t.Set(2 ) t.Get()
方法值 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 type T struct { a int } func (t T) Get () int { return t.a } func (t *T) Set (i int ) { t.a = i } func (t *T) Print () { fmt.Printf("%p, %v, %d \n" , t, t, t.a) } var t = &T{}f := t.Set f(2 ) t.Print() f(3 ) t.Print()
方法表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 type T struct { a int } func (t T) Get () int { return t.a } func (t *T) Set (i int ) { t.a = i } func (t *T) Print () { fmt.Printf("%p, %v, %d \n" , t, t, t.a) }
表达式 T.Get() (T).Set 被称之为方法表达式,方法表达式也可以被看做函数名,只不过这个函数的首个参数是接收者的实例活指针。 T.Get 的函数签名是 func(t T) int, (\ T).Set 的签名是 func(t *T, i int)。 注意这里是T.Get不能写成 (*T).Get,同理 (*T).Set不能写成 T.Set。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 t := T {a:1 } t.Get(t) (T).Get(t) f1 := T.Get f1(t) f2 := (T).Get f2(t) (*T).Set(&t, 1 ) f3 := (*T).Set f(&t, 1 )
方法集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package mainimport "fmt" type Int int func (a Int) Max (b Int) Int { if a >= b { return a } else { return b } } func (i *Int) Set (a Int) { *i = a } func (i Int) Print () { fmt.Printf("value = %d\n" , i) } func main () { var a Int = 10 var b Int = 20 c := a.Max(b) c.Print() (&c).Print() a.Set(10 ) a.Print() (&a).Set(30 ) a.Print() }
值调用和表达式调用的方法集
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 type Date struct {}func (Data) TestValue () {}func (*Data) TestPointer () {}(*Data) (&struct {} {}).TestPointer() (*Data) (&struct {} {}).TestValue (Data) (struct {} {}).TestValue () Data.TestValue(struct {} {})
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 type Data struct {}func (Data) TestValue () {}func (*Data) TestPointer () {}var a Data = struct {}{}Data.TestValue(a) (*Data).TestPointer(&a) f := a.TestValue f() y := (&a).TestValue y() g := a.TestPointer g() x := (&a).TestPointer x()
组合和方法集 组合
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package maintype X struct { a int } type Y struct { X b int } type Z struct { Y c int } func main () { x := X{a: 1 } y := Y{ X: x, b: 2 , } z := Z{ Y: y, c: 3 , } println (z.a, z.Y.a, z.Y.X.a) z = Z{} z.a = 2 println (z.a, z.Y.a, z.Y.X.a) }
嵌套层次可以有相同字段,但是最好避免,以免出现歧义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package maintype X struct { a int } type Y struct { X a int } type Z struct { Y a int } func main () { x := X{a: 1 } y := Y{ X: x, a: 2 , } z := Z{ Y: y, a: 3 , } println (z.a, z.Y.a, z.Y.X.a) z = Z{} z.a = 4 z.Y.a = 5 z.Y.X.a = 6 println (z.a, z.Y.a, z.Y.X.a) }
内嵌字段的方法调用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package mainimport "fmt" type X struct { a int } type Y struct { X b int } type Z struct { Y c int } func (x X) Print () { fmt.Printf("In = X, a = %d\n" , x.a) } func (x X) XPrint () { fmt.Printf("In = X, a = %d\n" , x.a) } func (y Y) Print () { fmt.Printf("In = Y, b = %d\n" , y.b) } func (z Z) Print () { fmt.Printf("In = Z, c = %d\n" , z.c) z.Y.Print() z.Y.X.Print() } func main () { x := X{a: 1 } y := Y{ X: x, b: 2 , } z := Z{ Y: y, c: 3 , } z.Print() z.XPrint() z.Y.XPrint() }
不推荐在多层的 struct 类型中内嵌多个同名的字段;但是并不反对 struct 定义和内嵌字段同名方法的用法,因为这提供了一种一种编程技术,使得 struct 能够重写内嵌字段的方法,提供面向对象编程中子类覆盖父类的同名方法的功能。
组合的方法集 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 package maintype X struct { a int } type Y struct { X } type Z struct { *X } func (x X) Get () int { return x.a } func (x *X) Set (i int ) { x.a = i } func main () { x := X{a: 1 } y := Y{ X: x, } println (y.Get()) y.Set(2 ) println (y.Get()) (*Y).Set(&y, 3 ) println (y.Get()) z := Z{ X: &x, } Z.Set(z, 4 ) println (z.Get()) (*Z).Set(&z, 5 ) println (z.Get()) }
编译器的自动转换仅适用于直接通过类型实例调用方法时才有效,类型实例传递给接口时,编辑器不会进行自动转换,而是会进行严格的方法集校验。
函数类型
1 2 type NewFuncType FuncLiteraltype NewFuncType OldFuncType
Go 语言函数不需要声明,可以直接调用,但是Go调用汇编语言编写的函数还是要使用函数声明语句
1 2 3 4 5 6 func (InputTypeList) OutputTypeList // 函数声明 func FuncName (InputTypeList) OutputTypeList
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 func add (a, b int ) int { return a + b } func add (int , int ) int // add 函数的签名,实际上就是 add 的字面量类型 func (int , int ) int // 匿名函数不能独立存在,常作为函数参数、返回值或者赋值给某个变量 // 匿名函数可以直接显式初始化 // 匿名函数的类型也是函数字面量类型 func (int , int ) int func (a, b int ) int { return a + b } type ADD func (int , int ) int // add 和 ADD 的类型相同,并且 add 是字面量类型 // 所以 add 可直接赋值给 ADD 类型的变量 g var g ADD = add func main () { f := func (a, b int ) int { return a + b } g(1 , 2 ) f(1 , 2 ) fmt.Printf("%T\n" , f) fmt.Printf("%T\n" , add) }
HTTP 标准库范例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 type HandlerFunc func (ResponseWriter, *Request) // 为有名函数类型添加方法 // 这是一种包装器的编程技法 // ServerHTTP calls f (w, r) func (f HandlerFunc) ServerHTTP (w ResponseWriter, r *Request) { f(w, r) } type Handler interface { ServerHTTP(ResponseWriter, *Request) } func (mux *ServeMux) Handle (patterm string , handler Handler) // 所以 HanderFunc 类型的变量可以传递给 Handler 接口变量 func (mux *ServeMux) HandleFunc (patterm string , handler func (ResponseWriter, *Request) ) { mux.Handle(pattern, HandlerFunc(handler)) }