go没有面向对象的概念,因此也没有继承概念,有的只是组合。java
func main() { var v Integer = 1 fmt.Println(v.Less(2)) // true } type Integer int func (a Integer) Less(b Integer) bool { return a < b }
Integer是一个新类型(也能够当作int的别名),它和int没有本质不一样,只是它为内置的int类型增长了个新方法Less()。这里并非真正意义上的别名,由于使用这种方法定义以后的类型能够拥有更多的特性,且在类型转换时必须显式转换.golang
int 和 Integer 能够相互转换: Integer(23); int(Integer(23));
, 并且这种转换并不会建立新的值。数组
只有须要修改对象的时候,才必须用指针数据结构
func main() { var v Integer = 1 v.Add(3) fmt.Println(v) // 4 } type Integer int func (a *Integer) Add(b Integer) { *a += b // 等同 *a = *a + b }
若是没有指针:并发
func main() { var v Integer = 1 v.Add(3) fmt.Println(v) // 1 } type Integer int func (a Integer) Add(b Integer) { a += b }
Go语言和C语言同样,类型都是基于值传递的。要想修改变量的值,只能传递指针。app
使用指针的Append框架
type ByteSlice []byte func (s *ByteSlice) Append(data byte) { slice := *s slice = append(slice, data) *s = slice } var s ByteSlice = []byte{1, 2, 3} (&s).Append(4) fmt.Println(s) // [1 2 3 4]
nil指针也能够调用方法函数
bytes.Buffer中声明了一个方法:测试
func (b *Buffer) String() string { if b == nil { // Special case, useful in debugging. return "<nil>" } return string(b.buf[b.off:]) }
当这样调用时:优化
var buf *bytes.Buffer = nil fmt.Println(buf.String())
并不会报错,而是输出 <nil>
,也就是说buf为空指针时也是能够调用方法的。而若是buf的类型是bytes.Buffer而不是指针时就会直接报错。
若是你有多个类型须要定义,可使用因式分解关键字的方式,例如:
type ( IZ int FZ float STR string )
每一个值都必须在通过编译后属于某个类型(编译器必须可以推断出全部值的类型),由于 Go 语言是一种静态类型语言.
这种方式并不拥有原类型的方法,但拥有原类型的字段
func main() { s := &Sun{} // s.BaseFun() // 错误,没有该方法 fmt.Println(s.f) // 正确,有f字段 } type Base struct { f string } func (this *Base) BaseFun() { fmt.Println("BaseFun") } type Sun Base
Go语言中的大多数类型都基于值语义,包括:
基本类型,如byte、int、bool、float3二、float64和string等;
复合类型,如数组(array)、结构体(struct)和指针(pointer)等
a := [3]int{1, 2, 3} b := a b[0]++ fmt.Println(a) // [1 2 3] fmt.Println(b) // [2 2 3]
若是b是a的指针类型的话:
a := [3]int{1, 2, 3} b := &a // 或者var b = &a b[0]++ fmt.Println(a) // [2 2 3] fmt.Println(b) // &[2 2 3] fmt.Println(*b) // [2 2 3]
此时b的类型不是[3]int
,而是*[3]int
数组切片:指向数组(array)的一个区间。
map:极其常见的数据结构,提供键值查询能力。
channel:执行体(goroutine)间的通讯设施。
接口(interface):对一组知足某个契约的类型的抽象
这些类型的定义中有指针类型的字段:
type slice struct { first *T len int cap int }
type MyType struct { val *int }
Go语言的结构体(struct)和其余语言的类(class)有同等的地位,但Go语言放弃了包括继承在内的大量面向对象特性,只保留了组合(composition)这个最基础的特性
type Rect struct { x, y, width, height float64 } func (r *Rect) Area() float64 { return r.width * r.height }
rect1 := new(Rect) rect2 := &Rect{} //rect3 := &Rect{0, 0} // 错误 rect4 := &Rect{0, 0, 5, 5} rect5 := &Rect{width: 20, height: 20}
&rect{n}的类型是指针类型,若是不想得到指针,能够去掉&:
rect := Rect{} var rect Rect = Rect{}
除了第一个外,其他都是花括号{}
要么都不赋值(有默认值),要么都赋值,只想给一部分赋值的话须要标明字段名
另外一种方式
type Person struct { name string age int } var p Person // 这不仅是声明了还初始化了??? fmt.Println(p) // { 0} p.name, p.age = "chen", 23 fmt.Println(p) // {chen 23}
重复的键将致使报错:
p := Persion{ Name: "a", Name: "a", }
报错信息:
duplicate field name in struct literal: Name
var v = struct { a, b string }{"aa", "bb"} // 声明的同时初始化 fmt.Println(v) // {aa bb} var vv = []struct { a, b string }{ {"a1", "b1"}, {"a2", "b2"}, } fmt.Println(vv) // [{a1 b1} {a2 b2}]
type Point struct { x, y int } func main() { points := []Point{ {1, 2}, {3, 4}, } fmt.Println(points) }
先声明,延后初始化
var inner struct { E interface{} } inner.E = 123
type empty struct { } e1 := &empty{} e2 := &empty{} fmt.Printf("%p, %p\n", e1, e2)
输出
0x5b5f60, 0x5b5f60
若是empty里有字段,那地址就不一样了。多是为了优化,若是结构体里没有字段只有方法,那么两个结构体的执行结果确定相同,因此共用一个地址了。
type Field struct { name string } fields := []Field{ {"a"}, {"b"}, {"c"}, } 或者: fields := []*Field{ {"a"}, {"b"}, {"c"}, }
确切地说,Go语言也提供了继承,可是采用了组合的文法,因此咱们将其称为匿名组合
type Base struct { Name string } func (b *Base) Foo() { fmt.Printf("Base.Foo():%s\n", b.Name) } func (b *Base) Bar() { fmt.Printf("Base.Bar():%s\n", b.Name) } type Sub struct { Base Name string } func (s *Sub) Foo() { s.Base.Foo() // 调用Base的方法 fmt.Printf("Sub.Foo():%s\n", s.Name) } func main() { base := &Base{"i am base."} base.Foo() base.Bar() sub := &Sub{*base, "i am sub."} // 由于base是指针,这里要加* sub.Foo() sub.Base.Bar() sub.Bar() // 其实调用的是Base的方法 fmt.Println(sub.Name) }
输出:
Base.Foo():i am base. Base.Bar():i am base. Base.Foo():i am base. Sub.Foo():i am sub. Base.Bar():i am base. Base.Bar():i am base. i am sub.
这是组合,不是继承,只是能够当作是继承,下面把Sub赋值给Base是错误的:
var v *Base = sub
提示:
cannot use sub (type *Sub) as type *Base in assignment
Sub会继承Base的属性和方法,能够当成本身的使用,也能够经过类型名来访问,就像java中的super
type Person struct { string age int } p := Person{"person", 11} fmt.Println(p.age, p.string) // 11 person,直接经过类型名访问
Sub没有Bar()方法,但仍是能够调用Base的Bar(),调用形式看起来像是继承
当咱们内嵌一个类型时,该类型的全部方法会变成外部类型的方法,可是当这些方法被调用时,其接收的参数仍然是内部类型,而非外部类型。
Sub中Base是值类型,这样当在Sub中修改了Base.Name, base是不会有反应的
sub.Base.Name = "modify" fmt.Println(base.Name) // 仍是i am base.
还能够继承Base指针
type Sub struct { *Base Name string } /* 重写方法 */ func (s *Sub) Foo() { s.Base.Foo() // 调用Base的方法 fmt.Printf("Sub.Foo():%s\n", s.Name) } func main() { base := &Base{"i am base."} base.Foo() base.Bar() sub := &Sub{base, "i am sub."} sub.Foo() sub.Base.Name = "modify" fmt.Println(base.Name) // modify }
修改了sub中的Base,base也被同步了
在Go语言官方网站提供的Effective Go中曾提到匿名组合的一个小价值,值得在这里再提一下。首先咱们能够定义以下的类型,它匿名组合了一个log.Logger指针:
type Job struct{ Command string *log.Logger }
在合适的赋值后,咱们在Job类型的全部成员方法中能够很温馨地借用全部log.Logger提供的方法。好比以下的写法:
func(job *Job)Start() { job.Log("starting now...") ... // 作一些事情 job.Log("started.") }
对于Job的实现者来讲,他甚至根本就不用意识到log.Logger类型的存在,这就是匿名组合的魅力所在。在实际工做中,只有合理利用才能最大发挥这个功能的价值。
须要注意的是,无论是非匿名的类型组合仍是匿名组合,被组合的类型所包含的方法虽然都升级成了外部这个组合类型的方法,但其实它们被组合方法调用时接收者并无改变。好比上面这个Job例子,即便组合后调用的方式变成了job.Log(...),但Log函数的接收者仍然是log.Logger指针,所以在Log中不可能访问到job的其余成员方法和变量。 这其实也很容易理解,毕竟被组合的类型并不知道本身会被什么类型组合,固然就无法在实现方法时去使用那个未知的“组合者”的功能了
匿名组合类型至关于以其类型名称(去掉包名部分)做为成员变量的名字
type Logger struct{ Level int } type Y struct{ *Logger Name string *log.Logger }
Y类型中就至关于存在两个名为Logger的成员,虽然类型不一样。所以,咱们预期会收到编译错误。有意思的是,这个编译错误并非必定会发生的。假如这两个Logger在定义后再也没有被用过,那么编译器将直接忽略掉这个冲突问题,直至开发者开始使用其中的某个Logger
来源:io包下io_test.go的Buffer。
func main() { var a *A = new(A) a.f() var b *B = new(B) b.f() } type A struct { } func (this A) f() { fmt.Println("f() from A") } type I interface { f() } type B struct { A //I // 若是放开,调用b.f()会报错 }
type A struct { a string b int c map[string]string } func main() { a := &A{"a", 1, map[string]string{"a": "a"}} b := new(A) *b = *a // 浅复制 fmt.Println(a, b) a.c["b"] = "b" // 两个都改变了 a.b = 2 // 只有a改变了 fmt.Println(a, b) }
输出:
&{a 1 map[a:a]} &{a 1 map[a:a]} &{a 2 map[b:b a:a]} &{a 1 map[a:a b:b]}
要使某个符号对其余包(package)可见(便可以访问),须要将该符号定义为以大写字母开头,小写字母开头的仅对包内可见,包括包内其余的类型。
在Go语言中,一个类只须要实现了接口要求的全部函数,咱们就说这个类实现了该接口
type IFile interface { Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error) Close() error } type IRead interface { Read(buf []byte) (n int, err error) } type File struct { // ... } func (f *File) Read(buf []byte) (n int, err error) { fmt.Println("File.Read()") return 0, nil } func (f *File) Write(buf []byte) (n int, err error) { fmt.Println("File.Write()") return 0, nil } func (f *File) Close() error { fmt.Println("File.Read()") return nil } func main() { var file1 IFile = new(File) file1.Read([]byte{}) file1.Write([]byte{}) file1.Close() var file2 IRead = new(File) file2.Read([]byte{}) //file2.Close() // 错误,未定义 }
定义实现类
type Integer int func (a Integer) Less(b Integer) bool { return a < b } func (a *Integer) Add(b Integer) { *a += b }
再定义接口(这样接口就得事先知道实现类,否则不知道有Integer??)
type LessAdder interface { Less(b Integer) bool Add(b Integer) }
赋值
var a Integer = 1 var b LessAdder = &a ... (1) var b LessAdder = a ... (2) 错误
缘由在于,Go语言能够根据下面的函数:
func(a Integer) Less(b Integer) bool
自动生成一个新的Less()方法:
func(a *Integer) Less(b Integer) bool{ return(*a).Less(b) }
这样,类型*Integer就既存在Less()方法,也存在Add()方法,知足LessAdder接口。而从另外一方面来讲,根据
func(a *Integer) Add(b Integer)
这个函数没法自动生成如下这个成员方法:
func(a Integer) Add(b Integer) { (&a).Add(b) }
由于(&a).Add()改变的只是函数参数a,对外部实际要操做的对象并没有影响,这不符合用户的预期。因此,Go语言不会自动为其生成该函数。所以,类型Integer只存在Less()方法,缺乏Add()方法,不知足LessAdder接口,故此上面的语句(2)不能赋值
为了进一步证实以上的推理,咱们不妨再定义一个Lesser接口,以下:
type Lesser interface{ Less(b Integer) bool }
而后定义一个Integer类型的对象实例,将其赋值给Lesser接口:
vara Integer = 1 varb1 Lesser = &a ... (1) varb2 Lesser = a ... (2)
正如咱们所料的那样,语句(1)和语句(2)都可以编译经过
在Go语言中,只要两个接口拥有相同的方法列表(次序不一样没关系),那么它们就是等同的,能够相互赋值 下面咱们来看一个示例,这是第一个接口:
package one type ReadWriter interface{ Read(buf []byte) (n int, err error) Write(buf []byte) (n int, err error) }
第二个接口位于另外一个包中:
package two type IStream interface{ Write(buf []byte) (n int, err error) Read(buf []byte) (n int, err error) }
如下这些代码可编译经过:
var file1 two.IStream = new(File) var file2 one.ReadWriter = file1 var file3 two.IStream = file2
接口赋值并不要求两个接口必须等价。若是接口A的方法列表是接口B的方法列表的子集,那么接口B能够赋值给接口A
例如,假设咱们有Writer接口:
type Writer interface{ Write(buf []byte) (n int, err error) }
就能够将上面的one.ReadWriter和two.IStream接口的实例赋值给Writer接口:
var file1 two.IStream = new(File) var file4 Writer = file1
但反过来并不成立
定义两个接口和一个实现类
type Intf1 interface {I F() } type Intf2 interface { F() } type Impl1 struct{} func (a Impl1) F() { fmt.Println("F()") }
查询Intf1指向的对象是否也实现了Intf2:
var intf1 Intf1 = Impl1{} if what, ok := intf1.(Intf2); ok { fmt.Println("ok") fmt.Println(what == intf1) } else { fmt.Println("no") }
输出:
ok true
若是intf1.(Intf2)中的intf1不是接口类型,会报错:
var intf1 Impl1 = Impl1{} // 这样不行
判断是不是字符串
var s interface{} // s的类型必定要是接口 s = "hello" if _, ok := s.(string); ok { fmt.Println("is string") } else { fmt.Println("not string") }
也能够不写ok : var.(type)
, 可是若是没法转换的话就直接panic了。
在Go语言中,还能够更加直截了当地询问接口指向的对象实例的类型
var intf1 interface{} = Impl1{} switch intf1.(type) { // 类型查询 case int: fmt.Println("int") case string: fmt.Println("string") case Intf1: fmt.Println("intf1...") default: // 配合接口查询 if v, ok := intf1.(Intf1); ok { fmt.Printf("Intf1,%T\n", v) } if v, ok := intf1.(Impl1); ok { fmt.Printf("Impl1,%T\n", v) } }
输出:
Intf1,intf.Impl1 Impl1,intf.Impl1
注意:
if v, ok := intf1.(Intf1); ok { fmt.Printf("Intf1,%T\n", v) v.F() }
case Intf1: fmt.Println("intf1...") fallthrough
报:cannot fallthrough in type switch
switch 中直接拿到具体类型:
var i interface{} = 1 switch v := i.(type) { case int: fmt.Printf("%T", v) // int }
// ReadWriter接口将基本的Read和Write方法组合起来 type ReadWriter interface{ Reader Writer }
这个接口组合了Reader和Writer两个接口,它彻底等同于以下写法:
type ReadWriter interface{ Read(p []byte) (n int, err error) // Reader接口的方法 Write(p []byte) (n int, err error) // Writer接口的方法 }
因为Go语言中任何对象实例都知足空接口interface{},因此interface{}看起来像是能够指向任何对象的Any类型,以下:
varv1 interface{} = 1 // 将int类型赋值给interface{} varv2 interface{} = "abc" // 将string类型赋值给interface{} varv3 interface{} = &v2 // 将 *interface{}类型赋值给interface{} varv4 interface{} = struct{ X int}{1} // 声明加初始化,匿名类? varv5 interface{} = &struct{ X int}{1}
当函数能够接受任意的对象实例时,咱们会将其声明为interface{},最典型的例子是标准库fmt中PrintXXX系列的函数,例如:
func Printf(fmt string, args ...interface{}) func Println(args ...interface{}) ...
type Intf interface { Name() string } type Imp struct{} func (i *Imp) Name() string { return "implement" } var _ Intf = &Imp{}
使用 _
将变量丢掉,这样就能够在编译时验证Imp是否实现了Intf。
var _ Intf = &Imp{}
能够写在正式代码中,也能够写在测试代码中。
还能够这样:var _ Intf = (*Imp)(nil)
不少人(特别是新手)在写 Go 语言代码时常常会问一个问题,那就是一个方法的接受者类型到底应该是值类型仍是指针类型呢,Go 的 wiki 上对这点作了很好的解释,我来翻译一下。
一个值类型的接受者能够减小必定数量的垃圾生成,若是一个值被传入一个值类型接受者的方法,一个栈上的拷贝会替代在堆上分配内存(但不是保证必定成功),因此在没搞明白代码想干什么以前,别由于这个缘由而选择值类型接受者。
当你看完这个仍是有疑虑,仍是不知道该使用哪一种接受者,那么记住使用指针接受者。
社区约定的接受者命名是类型的一个或两个字母的缩写(像 c 或者 cl 对于 Client)。不要使用泛指的名字像是 me,this 或者 self,也不要使用过分描述的名字,最后,若是你在一个地方使用了 c,那么就不要在别的地方使用 cl。
在 Go 语言中,若是一个结构体和一个嵌入字段同时实现了相同的接口会发生什么呢?咱们猜一下,可能有两个问题:
在写了一些测试代码并认真深刻的读了一下标准以后,我发现了一些有意思的东西,并且以为颇有必要分享出来,那么让咱们先从 Go 语言中的方法开始提及。
Go 语言中同时有函数和方法。一个方法就是一个包含了接受者的函数,接受者能够是命名类型或者结构体类型的一个值或者是一个指针。全部给定类型的方法属于该类型的方法集。
下面定义一个结构体类型和该类型的一个方法:
type User struct { Name string Email string } func (u User) Notify() error
首先咱们定义了一个叫作 User 的结构体类型,而后定义了一个该类型的方法叫作 Notify,该方法的接受者是一个 User 类型的值。要调用 Notify 方法咱们须要一个 User 类型的值或者指针:
// User 类型的值能够调用接受者是值的方法 damon := User{"AriesDevil", "ariesdevil@xxoo.com"} damon.Notify() // User 类型的指针一样能够调用接受者是值的方法 alimon := &User{"A-limon", "alimon@ooxx.com"} alimon.Notify()
在这个例子中当咱们使用指针时,Go 调整和解引用指针使得调用能够被执行。注意,当接受者不是一个指针时,该方法操做对应接受者的值的副本(意思就是即便你使用了指针调用函数,可是函数的接受者是值类型,因此函数内部操做仍是对副本的操做,而不是指针操做 --意思是说若是在Notify()中修改了Name的值,damon和alimon的Name值是不会变的)。
咱们能够修改 Notify 方法,让它的接受者使用指针类型:
func (u *User) Notify() error
再来一次以前的调用(注意:当接受者是指针时,即便用值类型调用那么函数内部也是对指针的操做):
// User 类型的值能够调用接受者是指针的方法 damon := User{"AriesDevil", "ariesdevil@xxoo.com"} damon.Notify() // User 类型的指针一样能够调用接受者是指针的方法 alimon := &User{"A-limon", "alimon@ooxx.com"} alimon.Notify()
若是Notify()中修改了Name的值,那么damon和alimon的Name值都是会变的。
若是你不清楚到底何时该使用值,何时该使用指针做为接受者,你能够去看一下这篇介绍。这篇文章同时还包含了社区约定的接受者该如何命名。
Go 语言中的接口很特别,并且提供了难以置信的一系列灵活性和抽象性。它们指定一个特定类型的值和指针表现为特定的方式。从语言角度看,接口是一种类型,它指定一个方法集,全部方法为接口类型就被认为是该接口。
下面定义一个接口:
type Notifier interface { Notify() error }
咱们定义了一个叫作 Notifier 的接口并包含一个 Notify 方法。当一个接口只包含一个方法时,按照 Go 语言的约定命名该接口时添加 -er 后缀。这个约定颇有用,特别是接口和方法具备相同名字和意义的时候。 咱们能够在接口中定义尽量多的方法,不过在 Go 语言标准库中,你很难找到一个接口包含两个以上的方法。
当涉及到咱们该怎么让咱们的类型实现接口时,Go 语言是特别的一个。Go 语言不须要咱们显式的实现类型的接口。若是一个接口里的全部方法都被咱们的类型实现了,那么咱们就说该类型实现了该接口。
让咱们继续以前的例子,定义一个函数来接受任意一个实现了接口 Notifier 的类型的值或者指针:
func SendNotification(notify Notifier) error { return notify.Notify() }
SendNotification 函数调用 Notify 方法,这个方法被传入函数的一个值或者指针实现。这样一来一个函数就能够被用来执行任意一个实现了该接口的值或者指针的指定的行为。
用咱们的 User 类型来实现该接口而且传入一个 User 类型的值来调用 SendNotification 方法:
func (u *User) Notify() error { log.Printf("User: Sending User Email To %s<%s>\n", u.Name, u.Email) return nil } func main() { user := User{ Name: "AriesDevil", Email: "ariesdevil@xxoo.com", } SendNotification(user) } // Output: cannot use user (type User) as type Notifier in function argument: User does not implement Notifier (Notify method has pointer receiver)
为何编译器不考虑咱们的值是实现该接口的类型?接口的调用规则是创建在这些方法的接受者和接口如何被调用的基础上。下面的是语言规范里定义的规则,这些规则用来讲明是否咱们一个类型的值或者指针实现了该接口:
类型 *T 的可调用方法集包含接受者为 *T 或 T 的全部方法集
这条规则说的是若是咱们用来调用特定接口方法的接口变量是一个指针类型,那么方法的接受者能够是值类型也能够是指针类型。显然咱们的例子不符合该规则,由于咱们传入 SendNotification 函数的接口变量是一个值类型。
类型 T 的可调用方法集包含接受者为 T 的全部方法
这条规则说的是若是咱们用来调用特定接口方法的接口变量是一个值类型,那么方法的接受者必须也是值类型该方法才能够被调用。显然咱们的例子也不符合这条规则,由于咱们 Notify 方法的接受者是一个指针类型。
语言规范里只有这两条规则,我经过这两条规则得出了符合咱们例子的规则:
类型 T 的可调用方法集不包含接受者为 *T 的方法
咱们碰巧遇上了我推断出的这条规则,因此编译器会报错。Notify 方法使用指针类型做为接受者而咱们却经过值类型来调用该方法。解决办法也很简单,咱们只须要传入 User 值的地址到 SendNotification 函数就行了:
func main() { user := &User{ Name: "AriesDevil", Email: "ariesdevil@xxoo.com", } SendNotification(user) } // Output: User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>
结构体类型能够包含匿名或者嵌入字段。也叫作嵌入一个类型。当咱们嵌入一个类型到结构体中时,该类型的名字充当了嵌入字段的字段名。
下面定义一个新的类型而后把咱们的 User 类型嵌入进去:
type Admin struct { User Level string }
咱们定义了一个新类型 Admin 而后把 User 类型嵌入进去,注意这个不叫继承而叫组合。 User 类型跟 Admin 类型没有关系。
咱们来改变一下 main 函数,建立一个 Admin 类型的变量并把变量的地址传入 SendNotification 函数中:
func main() { admin := &Admin{ User: User{ Name: "AriesDevil", Email: "ariesdevil@xxoo.com", }, Level: "master", } SendNotification(admin) } // Output User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>
事实证实,咱们能够 Admin 类型的一个指针来调用 SendNotification 函数。如今 Admin 类型也经过来自嵌入的 User 类型的方法提高实现了该接口。
若是 Admin 类型包含了 User 类型的字段和方法,那么它们在结构体中的关系是怎么样的呢?
当咱们嵌入一个类型,这个类型的方法就变成了外部类型的方法,可是当它被调用时,方法的接受者是内部类型(嵌入类型),而非外部类型。– Effective Go
所以嵌入类型的名字充当着字段名,同时嵌入类型做为内部类型存在,咱们可使用下面的调用方法:
admin.User.Notify() // Output User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>
这儿咱们经过类型名称来访问内部类型的字段和方法。然而,这些字段和方法也一样被提高到了外部类型:
admin.Notify() // Output User: Sending User Email To AriesDevil<ariesdevil@xxoo.com>
因此经过外部类型来调用 Notify 方法,本质上是内部类型的方法。
下面是 Go 语言中内部类型方法集提高的规则:
给定一个结构体类型 S 和一个命名为 T 的类型,方法提高像下面规定的这样被包含在结构体方法集中:
若是 S 包含一个匿名字段 T,S 和 *S 的方法集都包含接受者为 T 的方法提高。
这条规则说的是当咱们嵌入一个类型,嵌入类型的接受者为值类型的方法将被提高,能够被外部类型的值和指针调用。
对于 *S 类型的方法集包含接受者为 *T 的方法提高
这条规则说的是当咱们嵌入一个类型,能够被外部类型的指针调用的方法集只有嵌入类型的接受者为指针类型的方法集,也就是说,当外部类型使用指针调用内部类型的方法时,只有接受者为指针类型的内部类型方法集将被提高。
若是 S 包含一个匿名字段 *T,S 和 *S 的方法集都包含接受者为 T 或者 *T 的方法提高
这条规则说的是当咱们嵌入一个类型的指针,嵌入类型的接受者为值类型或指针类型的方法将被提高,能够被外部类型的值或者指针调用。
这就是语言规范里方法提高中仅有的三条规则,我根据这个推导出一条规则:
若是 S 包含一个匿名字段 T,S 的方法集不包含接受者为 *T 的方法提高。
这条规则说的是当咱们嵌入一个类型,嵌入类型的接受者为指针的方法将不能被外部类型的值访问。这也是跟咱们上面陈述的接口规则一致。
如今咱们能够写程序来回答开头提出的两个问题了,首先咱们让 Admin 类型实现 Notifier 接口:
func (a *Admin) Notify() error { log.Printf("Admin: Sending Admin Email To %s<%s>\n", a.Name, a.Email) return nil }
Admin 类型实现的接口显示一条 admin 方面的信息。当咱们使用 Admin 类型的指针去调用函数 SendNotification 时,这将帮助咱们肯定究竟是哪一个接口实现被调用了。
如今建立一个 Admin 类型的值并把它的地址传入 SendNotification 函数,来看看发生了什么:
func main() { admin := &Admin{ User: User{ Name: "AriesDevil", Email: "ariesdevil@xxoo.com", }, Level: "master", } SendNotification(admin) } // Output Admin: Sending Admin Email To AriesDevil<ariesdevil@xxoo.com>
预料之中,Admin 类型的接口实现被 SendNotification 函数调用。如今咱们用外部类型来调用 Notify 方法会发生什么呢:
admin.Notify() // Output Admin: Sending Admin Email To AriesDevil<ariesdevil@xxoo.com>
咱们获得了 Admin 类型的接口实现的输出。User 类型的接口实现不被提高到外部类型了。
如今咱们有了足够的依据来回答问题了:
不会,由于当咱们使用嵌入类型时,类型名充当了字段名。嵌入类型做为结构体的内部类型包含了本身的字段和方法,且具备惟一的名字。因此咱们能够有同一接口的内部实现和外部实现。
若是外部类型包含了符合要求的接口实现,它将会被使用。不然,经过方法提高,任何内部类型的接口实现能够直接被外部类型使用。
在 Go 语言中,方法,接口和嵌入类型一块儿工做方式是独一无二的。这些特性能够帮助咱们像面向对象那样组织结构而后达到一样的目的,而且没有其它复杂的东西。用本文中谈到的语言特点,咱们能够以极少的代码来构建抽象和可伸缩性的框架。
接口被赋值时有类型和值的区别,若是一个接口被赋值为nil,则类型和值都是nil,若是先声明一个结构体变量,该变量的值是nil,把这个变量赋值给接口时,接口的类型不是nil,但值是nil。
下面先声明一个接口和类型:
type intf interface { String() string } type A struct { Name string } func (a *A) String() string { // 每次都判断一下a是否是nil比较好 // if a == nil { // return "" // } return a.Name }
有一个打印方法:
func printIntf(i intf) { if i == nil { fmt.Println("nil") return } fmt.Println(i.String()) }
main()方法:
func main() { var a3 *A = nil printIntf(a3) }
运行时报错:
invalid memory address or nil pointer dereference
能够看到 if i == nil
的判断没有起做用。这是由于此时的i的类型是有值的,但实际的值倒是nil,这是 var a3 *A = nil
形成的。
一个比较好的解决方案是在String()中判断。
可是若是想在printIntf()中拦截i是nil的状况怎么办?
如今能想到的办法是利用反射:
func printIntf(i intf) { // 直接判断i是否是nil适用于printIntf(nil)这种状况 if i == nil { fmt.Println("nil") return } // 能够判断main()中的状况 v := reflect.ValueOf(i) if v.IsNil() { fmt.Println("nil - 2") return } fmt.Println(i.String()) }
反射有可能影响效率,因此仍是尽可能少用。遇到这种状况仍是从新审视一下本身的设计。
package main import "fmt" func main() { a1 := A{"1"} a1.F1("11") fmt.Println(a1) // {1} a1.F2("12") fmt.Println(a1) // {12}, (&a1).F2("12") a2 := &A{"2"} a2.F1("21") fmt.Println(a2) // &{2}, (*a2).F1("21"), 修改的是值 a2.F2("22") fmt.Println(a2) // &{22} //var _ I = A{} // error,没有实现F2 var _ I = (*A)(nil) } type A struct { name string } func (a A) F1(s string) { a.name = s } func (a *A) F2(s string) { a.name = s } type I interface { F1(string) F2(string) }
能够这样理解: