package mainimport ( "fmt" "sort" "io" "os" "errors" "math")func main() { fmt.Println("--interface--") // Go语言不是一种 “传统” 的面向对象编程语言:它里面没有类和继承的概念。 // 但是Go语言里有非常灵活的接口概念,通过它可以实现很多面向对象的特性。 // 很多面向对象的语言都有相似的接口概念,但Go语言中接口类型的独特之处在于它是满足隐式实现的。 // 也就是说,我们没有必要对于给定的具体类型定义所有满足的接口类型;简单地拥有一些必需的方法就足够了。 // 这种设计可以让你创建一个新的接口类型满足已经存在的具体类型却不会去改变这些类型的定义; // 当我们使用的类型来自于不受我们控制的包时这种设计尤其有用。 // 接口类型是对其它类型行为的抽象和概括;因为接口类型不会和特定的实现细节绑定在一起, // 通过这种抽象的方式我们可以让我们的函数更加灵活和更具有适应能力。 // 接口是双方约定的一种合作协议。接口实现者不需要关心接口会被怎样使用, // 调用者也不需要关心接口的实现细节。接口是一种类型,也是一种抽象结构,不会暴露所含数据的格式、类型及结构。 // 接口声明的格式 // 每个接口类型由数个方法组成。接口的形式代码如下: // type 接口类型名 interface{ // 方法名1( 参数列表1 ) 返回值列表1 // 方法名2( 参数列表2 ) 返回值列表2 // … // } // 接口类型名:使用 type 将接口定义为自定义的类型名。Go语言的接口在命名时, // 一般会在单词后面添加 er,如有写 *** 作的接口叫 Writer,有字符串功能的接口叫 Stringer, // 有关闭功能的接口叫 Closer 等。 // 方法名:当方法名首字母是大写时,且这个接口类型名首字母也是大写时,这个方法可以被接口所在的包(package)之外的代码访问。 // 参数列表、返回值列表:参数列表和返回值列表中的参数变量名可以被忽略,例如: // type writer interface{ // Write([]byte) error // } // 开发中常见的接口及写法 // Go语言提供的很多包中都有接口,例如 io 包中提供的 Writer 接口: // type Writer interface { // Write(p []byte) (n int, err error) // } // 这个接口可以调用 Write() 方法写入一个字节数组([]byte),返回值告知写入字节数(n int)和可能发生的错误(err error)。 // 类似的,还有将一个对象以字符串形式展现的接口,只要实现了这个接口的类型, // 在调用 String() 方法时,都可以获得对象对应的字符串。在 fmt 包中定义如下: // type Stringer interface { // String() string // } // Go语言的每个接口中的方法数量不会很多。Go语言希望通过一个接口精准描述它自己的功能, // 而通过多个接口的嵌入和组合的方式将简单的接口扩展为复杂的接口。本章后面的小节中会介绍如何使用组合来扩充接口。 // Go语言实现接口的条件 // 如果一个任意类型 T 的方法集为一个接口类型的方法集的超集,则我们说类型 T 实现了此接口类型。T 可以是一个非接口类型,也可以是一个接口类型。 // 实现关系在Go语言中是隐式的。两个类型之间的实现关系不需要在代码中显式地表示出来。Go语言中没有类似于 implements 的关键字。 // Go编译器将自动在需要的时候检查两个类型之间的实现关系。 // 接口定义后,需要实现接口,调用方才能正确编译通过并使用接口。接口的实现需要遵循两条规则才能让接口可用。 // 接口被实现的条件一:接口的方法与实现接口的类型方法格式一致 // 接口被实现的条件二:接口中所有方法均被实现 // 在类型中添加与接口签名一致的方法就可以实现该方法。签名包括方法中的名称、参数列表、返回参数列表。 // 也就是说,只要实现接口类型中的方法的名称、参数列表、返回参数列表中的任意一项与接口要实现的方法不一致, // 那么接口的这个方法就不会被实现。 // 为了抽象数据写入的过程,定义 DataWriter 接口来描述数据写入需要实现的方法,接口中的 WriteData() 方法表示将数据写入, // 写入方无须关心写入到哪里。实现接口的类型实现 WriteData 方法时,会具体编写将数据写入到什么结构中。 // 这里使用file结构体实现 DataWriter 接口的 WriteData 方法,方法内部只是打印一个日志,表示有数据写入,详细实现过程请参考下面的代码。 // 实例化file f := new(file) f.WriteData("data") // 声明一个DataWriter的接口 var writer DataWriter // 将接口赋值f,也就是*file类型 // writer = f // 使用DataWriter接口进行数据写入 writer.WriteData("data") // 传统的派生式接口及类关系构建的模式,让类型间拥有强耦合的父子关系。这种关系一般会以“类派生图”的方式进行。 // 经常可以看到大型软件极为复杂的派生树。随着系统的功能不断增加,这棵“派生树”会变得越来越复杂。 // 对于Go语言来说,非侵入式设计让实现者的所有类型均是平行的、组合的。如何组合则留到使用者编译时再确认。 // 因此,使用GO语言时,不需要同时也不可能有“类派生图”,开发者唯一需要关注的就是“我需要什么?”,以及“我能实现什么?”。 // 7.3 Go语言类型与接口的关系 // 在Go语言中类型和接口之间有一对多和多对一的关系,下面将列举出这些常见的概念,以方便读者理解接口与类型在复杂环境下的实现关系。 // 一个类型可以实现多个接口 // 网络上的两个程序通过一个双向的通信连接实现数据的交换,连接的一端称为一个 Socket。Socket 能够同时读取和写入数据, // 这个特性与文件类似。因此,开发中把文件和 Socket 都具备的读写特性抽象为独立的读写器概念。 // Socket 和文件一样,在使用完毕后,也需要对资源进行释放。 // 把 Socket 能够写入数据和需要关闭的特性使用接口来描述,请参考下面的代码: // 实例化Socket -- 一个类型可以实现多个接口 s := new(Socket) usingWriter(s) usingCloser(s) // 多个类型可以实现相同的接口 // Service 接口定义了两个方法:一个是开启服务的方法(Start()),一个是输出日志的方法(Log())。 // 使用 GameService 结构体来实现 Service,GameService 自己的结构只能实现 Start() 方法, // 而 Service 接口中的 Log() 方法已经被一个能输出日志的日志器(Logger)实现了, // 无须再进行 GameService 重新实现一遍。所以,选择将 Logger 嵌入到 GameService // 能最大程度地避免代码冗余,简化代码结构。详细实现过程如下: fmt.Println() fmt.Println() var ss Service = new(GameService) ss.Start() ss.Log("hello") // Go语言类型断言简述 // 类型断言(Type Assertion)是一个使用在接口值上的 *** 作,用于检查接口类型变量所持有的值是否实现了期望的接口或者具体的类型。 // 在Go语言中类型断言的语法格式如下: // value, ok := x.(T) // 其中,x 表示一个接口的类型,T 表示一个具体的类型(也可为接口类型)。 // 该断言表达式会返回 x 的值(也就是 value)和一个布尔值(也就是 ok),可根据该布尔值判断 x 是否为 T 类型: // 如果 T 是具体某个类型,类型断言会检查 x 的动态类型是否等于具体类型 T。如果检查成功,类型断言返回的结果是 x 的动态值,其类型是 T。 // 如果 T 是接口类型,类型断言会检查 x 的动态类型是否满足 T。如果检查成功,x 的动态值不会被提取,返回值是一个类型为 T 的接口值。 // 无论 T 是什么类型,如果 x 是 nil 接口值,类型断言都会失败。 fmt.Println() fmt.Println() var x interface{} x = 10 value, ok := x.(int) fmt.Print(value, ",", ok) // 需要注意如果不接收第二个参数也就是上面代码中的 ok,断言失败时会直接造成一个 panic。如果 x 为 nil 同样也会 panic。 // fmt.Println() // fmt.Println() // var y interface{} // y = "Hello" // value1 := y.(int) // fmt.Println(value1) // 类型断言还可以配合 switch 使用,示例代码如下: var a int a = 10 getType(a) // Go语言排序(借助sort.Interface接口) // 排序 *** 作和字符串格式化一样是很多程序经常使用的 *** 作。 // 尽管一个最短的快排程序只要 15 行就可以搞定,但是一个健壮的实现需要更多的代码, // 并且我们不希望每次我们需要的时候都重写或者拷贝这些代码。 // 幸运的是,sort 包内置的提供了根据一些排序函数来对任何序列排序的功能。 // 它的设计非常独到。在很多语言中,排序算法都是和序列数据类型关联,同时排序函数和具体类型元素关联。 // 相比之下,Go语言的 sort.sort 函数不会对具体的序列和它的元素做任何假设。 // 相反,它使用了一个接口类型 sort.Interface 来指定通用的排序算法和可能被排序到的序列类型之间的约定。 // 这个接口的实现由序列的具体表示和它希望排序的元素决定,序列的表示经常是一个切片。 // 一个内置的排序算法需要知道三个东西:序列的长度,表示两个元素比较的结果,一种交换两个元素的方式; // 这就是 sort.Interface 的三个方法: // package sort // type Interface interface { // Len() int // 获取元素数量 // Less(i, j int) bool // i,j是序列元素的大小。 // Swap(i, j int) // 交换元素 // } // 为了对序列进行排序,我们需要定义一个实现了这三个方法的类型,然后对这个类型的一个实例应用 // sort.sort 函数。思考对一个字符串切片进行排序,这可能是最简单的例子了。 // 下面是这个新的类型 MyStringList 和它的 Len,Less 和 Swap 方法 // type MyStringList []string // func (m MyStringList ) Len() int { return len(m) } // func (m MyStringList ) Less(i, j int) bool { return m[i] < m[j] } // func (m MyStringList ) Swap(i, j int) { m[i], m[j] = m[j], m[i] } // 使用sort.Interface接口进行排序 // 对一系列字符串进行排序时,使用字符串切片([]string)承载多个字符串。 // 使用 type 关键字,将字符串切片([]string)定义为自定义类型 MyStringList。 // 为了让 sort 包能识别 MyStringList,能够对 MyStringList 进行排序, // 就必须让 MyStringList 实现 sort.Interface 接口。 // 准备一个内容被打乱顺序的字符串切片 names := new (MyStringList) names.List = []string{"3. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Blood",} // 使用sort包进行排序 sort.sort(names) // 遍历打印结果 for _, v := range names.List { fmt.Printf("%s\n", v) } fmt.Println() fmt.Println() fmt.Println() fmt.Println() names2 := MyStringListC{"3. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Blood",} // 使用sort包进行排序 sort.sort(names2) // 遍历打印结果 for _, v := range names2 { fmt.Printf("%s\n", v) } fmt.Println() fmt.Println() names3 := sort.StringSlice{ "3. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Blood", } sort.sort(names3) for _, v := range names3 { fmt.Printf("%s\n", v) } fmt.Println() fmt.Println() names4 := []string{ "30. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Blood", } sort.Strings(names4) // 遍历打印结果 for _, v := range names4 { fmt.Printf("%s\n", v) } // sort 包中内建的类型排序接口 // 类 型 实现 sort.lnterface 的类型 直接排序方法 说 明 // 字符串(String) StringSlice sort.Strings(a [] string) 字符 ASCII 值升序 // 整型(int) IntSlice sort.Ints(a []int) 数值升序 // 双精度浮点(float64) float64Slice sort.float64s(a []float64) 数值升序 names6 := sort.StringSlice{ "3. Triple Kill", "5. Penta Kill", "2. Double Kill", "4. Quadra Kill", "1. First Blood", } fmt.Println() fmt.Println() sort.sort(names6) for _, v := range names6 { fmt.Printf("%s\n", v) } // 使用 sort.Strings fmt.Println() fmt.Println() names7 := []string{ "3. Triple Kill", "5. Penta Kill", "2. Double Kill", "40. Quadra Kill", "1. First Blood", } sort.Strings(names7) // 遍历打印结果 for _, v := range names7 { fmt.Printf("%s\n", v) } // 对结构体排序 // 完整实现sort.Interface进行结构体排序 // 将一批英雄名单使用结构体定义,英雄名单的结构体中定义了英雄的名字和分类。 // 排序时要求按照英雄的分类进行排序,相同分类的情况下按名字进行排序,详细代码实现过程如下。 // 准备英雄列表 // 结构体初始化 Hero{"吕布", Tank} 使用花括号,按照顺序赋值, & 取地址,拿到指针 heros := Heros{ &Hero{"吕布", Tank}, &Hero{"李白", Assassin}, &Hero{"妲己", Mage}, &Hero{"貂蝉", Assassin}, &Hero{"关羽", Tank}, &Hero{"诸葛亮", Mage}, } sort.sort(heros) fmt.Println() fmt.Println() for _,v := range heros{ // fmt.Printf("%+v\n", v) fmt.Printf(" name = %s King = %d \n",v.name, v.Kind) } // 2) 使用sort.Slice进行切片元素排序 // 从 Go 1.8 开始,Go语言在 sort 包中提供了 sort.Slice() 函数进行更为简便的排序方法。 // sort.Slice() 函数只要求传入需要排序的数据,以及一个排序时对元素的回调函数, // 类型为 func(i,j int)bool,sort.Slice() 函数的定义如下: // func Slice(slice interface{}, less func(i, j int) bool) heros3 := Heros{ &Hero{"吕布", Tank}, &Hero{"李白", Assassin}, &Hero{"妲己", Mage}, &Hero{"貂蝉", Assassin}, &Hero{"关羽", Tank}, &Hero{"诸葛亮", Mage}, } // 闭包? sort.Slice(heros3, func(i, j int) bool { if heros[i].Kind != heros[j].Kind { return heros[i].Kind < heros[j].Kind } return heros[i].name < heros[j].name }) fmt.Println() fmt.Println() for _,v := range heros{ // fmt.Printf("%+v\n", v) fmt.Printf(" name = %s King = %d \n",v.name, v.Kind) } // Go语言接口的嵌套组合 // 在Go语言中,不仅结构体与结构体之间可以嵌套(实现继承),接口与接口间也可以通过嵌套创造出新的接口。 // 一个接口可以包含一个或多个其他的接口,这相当于直接将这些内嵌接口的方法列举在外层接口中一样。 // 只要接口的所有方法被实现,则这个接口中的所有嵌套接口的方法均可以被调用。 // 系统包中的接口嵌套组合 // Go语言的 io 包中定义了写入器(Writer)、关闭器(Closer)和写入关闭器(WriteCloser)3 个接口,代码如下: // type Writer interface { // Write(p []byte) (n int, err error) // } // type Closer interface { // Close() error // } // 写入关闭器(WriteCloser),这个接口由 Writer 和 Closer 两个接口嵌入。 // 也就是说,WriteCloser 同时拥有了 Writer 和 Closer 的特性。 // type WriteCloser interface { // Writer // Closer // } // 在代码中使用接口嵌套组合 // 在代码中使用 io.Writer、io.Closer 和 io.WriteCloser 这 3 个接口时, // 只需要按照接口实现的规则实现 io.Writer 接口和 io.Closer 接口即可。 // 而 io.WriteCloser 接口在使用时,编译器会根据接口的实现者确认它们是否同时实现了 // io.Writer 和 io.Closer 接口,详细实现代码如下: // 声明一个设备结构 // type device struct { // } // // 实现io.Writer的Write()方法 // func (d *device) Write(p []byte) (n int, err error) { // return 0, nil // } // // 实现io.Closer的Close()方法 // func (d *device) Close() error { // return nil // } // 声明写入关闭器, 并赋予device的实例 // 对 device 实例化,由于 device 实现了 io.WriteCloser 的所有嵌入接口, // 因此 device 指针就会被隐式转换为 io.WriteCloser 接口。 // 给 io.WriteCloser 或 io.Writer 更换不同的实现者,可以动态地切换实现代码。 // 实现动态调用实现着,类似java的多态 var wc io.WriteCloser = new(device) // 写入数据 wc.Write(nil) // 关闭设备 wc.Close() // 声明写入器, 并赋予device的新实例 // 给 io.WriteCloser 或 io.Writer 更换不同的实现者,可以动态地切换实现代码。 var writeonly io.Writer = new(device) // 写入数据 writeonly.Write(nil) // Go语言接口和类型之间的转换 // Go语言中使用接口断言(type assertions)将接口转换成另外一个接口, // 也可以将接口转换为另外的类型。接口的转换在开发中非常常见,使用也非常频繁。 // 类型断言的格式 // 类型断言是一个使用在接口值上的 *** 作。语法上它看起来像 i.(T) 被称为断言类型,这里 i 表示一个接口的类型和 T 表示一个类型。 // 一个类型断言检查它 *** 作对象的动态类型是否和断言的类型匹配。 // 类型断言的基本格式如下: // t := i.(T) 其中,i 代表接口变量,T 代表转换的目标类型,t 代表转换后的变量。 // 这里有两种可能。 // 第一种,如果断言的类型 T 是一个具体类型,然后类型断言检查 i 的动态类型是否和 T 相同。如果这个检查成功了, // 类型断言的结果是 i 的动态值,当然它的类型是 T。换句话说,具体类型的类型断言从它的 *** 作对象中获得具体的值。 // 如果检查失败,接下来这个 *** 作会抛出 panic。例如: fmt.Println("断言:") var w io.Writer w = os.Stdout ff := w.(*os.file) // 成功: ff == os.Stdout fmt.Printf("成功: ff == os.Stdout 是 %T \n", ff) // c := w.(*bytes.Buffer) // 死机:接口保存*os.file,而不是*bytes.buffer // fmt.Printf("%T \n", c) // 第二种,如果相反断言的类型 T 是一个接口类型,然后类型断言检查是否 i 的动态类型满足 T。如果这个检查成功了, // 动态值没有获取到;这个结果仍然是一个有相同类型和值部分的接口值,但是结果有类型 T。 // 换句话说,对一个接口类型的类型断言改变了类型的表述方式,改变了可以获取的方法集合(通常更大),但是它保护了接口值内部的动态类型和值的部分。 // 在下面的第一个类型断言后,w 和 rw 都持有 os.Stdout 因此它们每个有一个动态类型 *os.file, // 但是变量 w 是一个 io.Writer 类型只对外公开出文件的 Write 方法,然而 rw 变量也只公开它的 Read 方法。 var w2 io.Writer w2 = os.Stdout // 具体实现 Stdout 实现了 io.ReaDWriter 接口 rw2 := w2.(io.ReaDWriter) // 成功:*os.file具有读写功能 ,改变了可以获取的方法集合(通常更大) fmt.Printf("io.Writer 断言 io.ReaDWriter 结果 %T ",rw2) // 输出结果 io.Writer 断言 io.ReaDWriter 结果 *os.file % // w2 = new(bytes.ByteCounter) // rw2 = w2.(io.ReaDWriter) // 死机:*字节计数器没有读取方法 // 如果断言 *** 作的对象是一个 nil 接口值,那么不论被断言的类型是什么这个类型断言都会失败。 // 几乎不需要对一个更少限制性的接口类型(更少的方法集合)做断言,因为它表现的就像赋值 *** 作一样, // 除了对于 nil 接口值的情况。 // 如果 i 没有完全实现 T 接口的方法,这个语句将会触发宕机。触发宕机不是很友好,因此上面的语句还有一种写法: // t,ok := i.(T) 注意!种断言书写格式更应该被推广 // 这种写法下,如果发生接口未实现时,将会把 ok 置为 false,t 置为 T 类型的 0 值。 // 正常实现时,ok 为 true。这里 ok 可以被认为是:i 接口是否实现 T 类型的结果 // 将接口转换为其他接口 // 实现某个接口的类型同时实现了另外一个接口,此时可以在两个接口间转换。 // 鸟和猪具有不同的特性,鸟可以飞,猪不能飞,但两种动物都可以行走。如果使用结构体实现鸟和猪, // 让它们具备自己特性的 Fly() 和 Walk() 方法就让鸟和猪各自实现了飞行动物接口(Flyer)和行走动物接口(Walker)。 // 将鸟和猪的实例创建后,被保存到 interface{} 类型的 map 中。 // interface{} 类型表示空接口,意思就是这种接口可以保存为任意类型。 // 对保存有鸟或猪的实例的 interface{} 变量进行断言 *** 作,如果断言对象是断言指定的类型, // 则返回转换为断言对象类型的接口;如果不是指定的断言类型时,断言的第二个参数将返回 false。例如下面的代码: // var obj interface = new(bird) // f, isFlyer := obj.(Flyer) // 代码中,new(bird) 产生 *bird 类型的 bird 实例,这个实例被保存在 interface{} 类型的 obj 变量中。 // 使用 obj.(Flyer) 类型断言,将 obj 转换为 Flyer 接口。f 为转换成功时的 Flyer 接口类型,isFlyer 表示是否转换成功,类型就是 bool。 // 创建动物的名字到实例的映射 animals := map[string]interface{}{ "bird": new(bird), // new(bird) 产生 *bird 类型的 bird 实例 "pig": new(pig), // new(pig) 产生 *pig 类型的的 pig 实例 } // 遍历映射 fmt.Println("将接口转换为其他接口:") fmt.Println("将接口转换为其他接口:") fmt.Println("将接口转换为其他接口:") for name, obj := range animals { // 判断对象是否为飞行动物 f, isFlyer := obj.(Flyer) // 接口转换 // 判断对象是否为行走动物 w, isWalker := obj.(Walker) // 接口转换 fmt.Printf("isFlyer %T isWalker %T \n",f,w) // 结果 isFlyer *main.bird isWalker *main.bird // isFlyer <nil> isWalker *main.pig fmt.Printf("name: %s isFlyer: %v isWalker: %v\n", name, isFlyer, isWalker) // 如果是飞行动物则调用飞行动物接口 if isFlyer { f.Fly() } // 如果是行走动物则调用行走动物接口 if isWalker { w.Walk() } } // 将接口转换为其他类型 // 在代码 1 中,可以实现将接口转换为普通的指针类型。例如将 Walker 接口转换为 *pig 类型,请参考下面的代码: p1 := new(pig) var awalker Walker = p1 // 由于 pig 实现了 Walker 接口,因此可以被隐式转换为 Walker 接口类型保存于 awalker 中。 p2 := awalker.(*pig) // 由于 awalker 中保存的本来就是 *pig 本体,因此可以转换为 *pig 类型。 (转换为对应的类型指针) fmt.Printf("p1=%p p2=%p", p1, p2) // 对比发现,p1 和 p2 指针是相同的。 p1=0x119a3d0 p2=0x119a3d0 // 如果尝试将上面这段代码中的 Walker 类型的 awalker 转换为 *bird 类型,将会发出运行时错误,请参考下面的代码: // p1 = new(pig) // var awalker2 Walker = p1 // p4 := awalker2.(*bird) // panic: interface conversion: main.Walker is *main.pig, not *main.bird // fmt.Printf("p1=%p p2=%p", p1, p4) // 因此,接口在转换为其他类型时,接口内保存的实例对应的类型指针,必须是要转换的对应的类型指针。 // 总结 // 接口和其他类型的转换可以在Go语言中自由进行,前提是已经完全实现。 // 接口断言类似于流程控制中的 if。但大量类型断言出现时,应使用更为高效的类型分支 switch 特性。 // Go语言空接口类型(interface{}) // 空接口是接口类型的特殊形式,空接口没有任何方法,因此任何类型都无须实现空接口。从实现的角度看, // 任何值都满足这个接口的需求。因此空接口类型可以保存任何值,也可以从空接口中取出原值。 // 空接口类型类似于 C# 或 Java 语言中的 Object、C语言中的 voID*、C++ 中的 std::any。 // 在泛型和模板出现前,空接口是一种非常灵活的数据抽象保存和使用的方法。 // 空接口的内部实现保存了对象的类型和指针。使用空接口保存一个数据的过程会比直接用数据对应类型的变量保存稍慢。 // 因此在开发中,应在需要的地方使用空接口,而不是在所有地方使用空接口。 // 将值保存到空接口 var any interface{} fmt.Println("\n将值保存到空接口") fmt.Println(any) fmt.Printf(" 当前类型 %T \n",any) any = 1 fmt.Println(any) fmt.Printf(" 当前类型 %T \n",any) // 当前类型 int any = "hello" fmt.Println(any) fmt.Printf(" 当前类型 %T \n",any) any = false fmt.Println(any) fmt.Printf(" 当前类型 %T \n",any) // 从空接口获取值 fmt.Println("\n从空接口获取值") // 声明a变量, 类型int, 初始值为1 var aa int = 1 // 声明i变量, 类型为interface{}, 初始值为a, 此时i的值变为1 var i interface{} = aa // i 当前类型 int // 声明b变量, 尝试赋值i // var b int = i 编译错误 cannot use i (type interface {}) as type int in assignment: need type assertion // 类似于无论集装箱装的是茶叶还是烟草,集装箱依然是金属做的,不会因为所装物的类型改变而改变。 // 编译器提示我们得使用 type assertion,意思就是类型断言。 // 修改后,代码可以编译通过,并且 b 可以获得 i 变量保存的 a 变量的值:1。 var b int = i.(int) fmt.Println(b) fmt.Printf(" 当前类型 %T \n",b) // 空接口的值比较 fmt.Println("\n类型不同的空接口间的比较结果不相同") // 空接口在保存不同的值后,可以和其他变量值一样使用==进行比较 *** 作。空接口的比较有以下几种特性。 // 1) 类型不同的空接口间的比较结果不相同 // 保存有类型不同的值的空接口进行比较时,Go语言会优先比较值的类型。因此类型不同,比较结果也是不相同的,代码如下: // a保存整型 var aaa interface{} = 100 // b保存字符串 var bbb interface{} = "hi" // 两个空接口不相等 fmt.Println(aaa == bbb) // 2) 不能比较空接口中的动态值 // c保存包含10的整型切片 fmt.Println("\n不能比较空接口中的动态值") var c interface{} = []int{10} // d保存包含20的整型切片 var d interface{} = []int{20} fmt.Println("\n不能比较空接口中的动态值",c,d) // 输出==>不能比较空接口中的动态值 [10] [20] // 这里会发生崩溃 // fmt.Println(c == d) // panic: runtime error: comparing uncomparable type []int // map 宕机错误,不可比较 // 切片([]T) 宕机错误,不可比较 // 通道(channel) 可比较,必须由同一个 make 生成,也就是同一个通道才会是 true,否则为 false // 数组([容量]T) 可比较,编译期知道两个数组是否一致 // 结构体 可比较,可以逐个比较结构体的值 // 函数 可比较 // Go语言类型分支(switch判断空接口中变量的类型) fmt.Println("\nGo语言类型分支(switch判断空接口中变量的类型)") // type-switch 流程控制的语法或许是Go语言中最古怪的语法。 // 它可以被看作是类型断言的增强版。它和 switch-case 流程控制代码块有些相似。 // 一个 type-switch 流程控制代码块的语法如下所示: var xxx interface{} = new(bird) switch t := xxx.(type) { case Walker: fmt.Printf("Type Circle %T with value %v\n", t, t) // fallthrough cannot fallthrough in type switchgo case Flyer: // 既可以判断指针也可以判断接口类型 fmt.Printf("Type Square %T with value %v\n", t, t) case nil: fmt.Printf("nil value: nothing to check?\n") default: fmt.Printf("Unexpected type %T\n", t) } // Type Square *main.Square with value &{5} fmt.Println("\n使用类型分支判断基本类型") printType(1024) printType("pig") printType(true) fmt.Println("\n使用类型分支判断接口类型") // 多个接口进行类型断言时,可以使用类型分支简化判断过程。 // 现在电子支付逐渐成为人们普遍使用的支付方式,电子支付相比现金支付具备很多优点。 // 例如,电子支付能够刷脸支付,而现金支付容易被偷等。 // 使用类型分支可以方便地判断一种支付方法具备哪些特性,具体请参考下面的代码。 // 使用电子支付判断 // var alipay interface{} = new(Alipay) 或者 都可以 // var alipay = new(Alipay) 都可以 var alipay interface{} = new(Alipay) print(alipay) // 使用现金判断 print(new(Cash)) // 输出 // *main.Alipay can use faceID // *main.Cash may be stolen // Go语言error接口:返回错误信息 fmt.Println("\nGo语言error接口:返回错误信息") // 错误处理在每个编程语言中都是一项重要内容,通常开发中遇到的分为异常与错误两种, // Go语言中也不例外。本节我们主要来学习一下Go语言中的错误处理。 // 在C语言中通过返回 -1 或者 NulL 之类的信息来表示错误,但是对于使用者来说,如果不查看相应的 API 说明文档, // 根本搞不清楚这个返回值究竟代表什么意思,比如返回 0 是成功还是失败? // 针对这样的情况,Go语言中引入 error 接口类型作为错误处理的标准模式, // 如果函数要返回错误,则返回值类型列表中肯定包含 error。error 处理过程类似于C语言中的错误码,可逐层返回,直到被处理。 // error 基本用法 // Go语言中返回的 error 类型究竟是什么呢?查看Go语言的源码就会发现 error 类型是一个非常简单的接口类型,如下所示: // The error built-in interface type is the conventional interface for // representing an error condition, with the nil value representing no error. // type error interface { // Error() string // } // error 接口有一个签名为 Error() string 的方法,所有实现该接口的类型都可以当作一个错误类型。 // Error() 方法给出了错误的描述,在使用 fmt.Println 打印错误时,会在内部调用 Error() string 方法来得到该错误的描述。 // 一般情况下,如果函数需要返回错误,就将 error 作为多个返回值中的最后一个(但这并非是强制要求)。 // 创建一个 error 最简单的方法就是调用 errors.New 函数,它会根据传入的错误信息返回一个新的 error,示例代码如下: // 创建一个error errors.New("错误的描述") result, err := Sqrt(-13) if err != nil { fmt.Println(err) } else { fmt.Println(result) } // 如下OK fmt.Println(Sqrt(16)) // 自定义错误类型 // 除了上面的 errors.New 用法之外,我们还可以使用 error 接口自定义一个 Error() 方法,来返回自定义的错误信息。 fmt.Println("\n 自定义返回错误信息") resultU, err := SqrtU(-13) if err != nil { fmt.Println(err) } else { fmt.Println(resultU) }}type dualError struct { Num float64 Msg string}func (e dualError) Error() string { return fmt.Sprintf("Wrong!!!,because \"%f\" is a negative number, 业务描述msg = %s", e.Num, e.Msg)}func SqrtU(f float64) (float64, error) { if f < 0 { return -1, dualError{Num: f, Msg : "你怎么可以对一个负数开方"} } return math.Sqrt(f), nil}func Sqrt(f float64) (float64, error) { if f < 0 { return -1, errors.New("math: square root of negative number") } return math.Sqrt(f), nil}// 电子支付方式type Alipay struct {}// 为Alipay添加CanUseFaceID()方法, 表示电子支付方式支持刷脸func (a *Alipay) CanUseFaceID() {}// 现金支付方式type Cash struct {}// 为Cash添加Stolen()方法, 表示现金支付方式会出现偷窃情况func (a *Cash) Stolen() {}// 具备刷脸特性的接口type CantainCanUseFaceID interface { CanUseFaceID()}// 具备被偷特性的接口type ContainStolen interface { Stolen()}// 打印支付方式具备的特点func print(payMethod interface{}) { switch payMethod.(type) { case CantainCanUseFaceID: // 可以刷脸 fmt.Printf("%T can use faceID\n", payMethod) // 分别对刷脸和被偷的特性进行打印 case ContainStolen: // 可能被偷 fmt.Printf("%T may be stolen\n", payMethod) }}func printType(v interface{}) { switch v.(type) { case int: fmt.Println(v, "is int") case string: fmt.Println(v, "is string") case bool: fmt.Println(v, "is bool") default: fmt.Printf("Unexpected type\n") }}// 定义飞行动物接口type Flyer interface { Fly()}// 定义行走动物接口type Walker interface { Walk()}// 定义鸟类type bird struct {}// 实现飞行动物接口func (b *bird) Fly() { fmt.Println("bird: fly")}// 为鸟添加Walk()方法, 实现行走动物接口func (b *bird) Walk() { fmt.Println("bird: walk")}// 定义猪type pig struct {}// 为猪添加Walk()方法, 实现行走动物接口func (p *pig) Walk() { fmt.Println("pig: walk")}// 声明一个设备结构type device struct {}// 实现io.Writer的Write()方法func (d *device) Write(p []byte) (n int, err error) { return 0, nil}// 实现io.Closer的Close()方法func (d *device) Close() error { return nil}// 声明英雄的分类type HeroKind int// 定义HeroKind常量, 类似于枚举const ( None HeroKind = iota Tank Assassin Mage)// 定义英雄名单的结构type Hero struct { name string // 英雄的名字 Kind HeroKind // 英雄的种类}// 将英雄指针的切片定义为Heros类型type Heros []*Hero// 实现sort.Interface接口取元素数量方法func (s Heros) Len() int { return len(s)}// 实现sort.Interface接口比较元素方法func (s Heros) Less(i, j int) bool { // 如果英雄的分类不一致时, 优先对分类进行排序 if s[i].Kind != s[j].Kind { return s[i].Kind < s[j].Kind } // 默认按英雄名字字符升序排列 return s[i].name < s[j].name}// 实现sort.Interface接口交换元素方法func (s Heros) Swap(i, j int) { s[i], s[j] = s[j], s[i]}// 接口实现不受限于结构体,任何类型都可以实现接口。// 要排序的字符串切片 []string 是系统定制好的类型,无法让这个类型去实现 // sort.Interface 排序接口。因此,需要将 []string 定义为自定义的类型。// 将[]string定义为MyStringList类型 非结构体类型type MyStringListC []string// 实现sort.Interface接口的获取元素数量方法func (m MyStringListC) Len() int { return len(m)}// 实现sort.Interface接口的比较元素方法func (m MyStringListC) Less(i, j int) bool { return m[i] < m[j]}// 实现sort.Interface接口的交换元素方法func (m MyStringListC) Swap(i, j int) { m[i], m[j] = m[j], m[i]}// 要注意:结构体类型实现接口需要用 指针(m *MyStringList) 接收实现接口// type GameService struct// Logger // 嵌入日志器// }// 将[]string定义为MyStringList类型type MyStringList struct{ List []string} // 实现sort.Interface接口的获取元素数量方法func (m *MyStringList) Len() int { return len(m.List)}// 实现sort.Interface接口的比较元素方法func (m *MyStringList) Less(i, j int) bool { return (m.List)[i] < (m.List)[j]}// 实现sort.Interface接口的交换元素方法func (m *MyStringList) Swap(i, j int) { (m.List)[i], (m.List)[j] = (m.List)[j], (m.List)[i]} // 类型断言还可以配合 switch 使用,示例代码如下:func getType(a interface{}) { switch a.(type) { case int: fmt.Println("the type of a is int") case string: fmt.Println("the type of a is string") case float64: fmt.Println("the type of a is float") default: fmt.Println("unkNown type") }}// 多个类型可以实现相同的接口// Service 接口定义了两个方法:一个是开启服务的方法(Start()),一个是输出日志的方法(Log())。// 使用 GameService 结构体来实现 Service,GameService 自己的结构只能实现 Start() 方法,// 而 Service 接口中的 Log() 方法已经被一个能输出日志的日志器(Logger)实现了,// 无须再进行 GameService 重新实现一遍。所以,选择将 Logger 嵌入到 GameService // 能最大程度地避免代码冗余,简化代码结构。详细实现过程如下:// 一个服务需要满足能够开启和写日志的功能type Service interface { Start() // 开启服务 Log(string) // 日志输出}// 日志器type Logger struct {}// 实现Service的Log()方法func (g *Logger) Log(l string) { fmt.Println(l)}// 游戏服务type GameService struct { Logger // 嵌入日志器}// 实现Service的Start()方法func (g *GameService) Start() { fmt.Println("Starting ...")}// 使用io.Writer的代码, 并不知道Socket和io.Closer的存在func usingWriter( writer Writer){ writer.Write( nil )}// 使用io.Closer, 并不知道Socket和io.Writer的存在func usingCloser( closer Closer) { closer.Close()}type Socket struct {}func (s *Socket) Write(p []byte) (n int, err error) { fmt.Println("write ......") return 0, nil}func (s *Socket) Close() error { fmt.Println("close ......") return nil}// Socket 结构的 Write() 方法实现了 io.Writer 接口:type Writer interface { Write(p []byte) (n int, err error)}// 同时,Socket 结构也实现了 io.Closer 接口:type Closer interface { Close() error}// 定义一个数据写入器type DataWriter interface { WriteData(data interface{}) error // 能否写入 CanWrite() bool}// 定义文件结构,用于实现DataWritertype file struct {}// 实现DataWriter接口的WriteData方法// file 的 WriteData() 方法使用指针接收器。输入一个 interface{} 类型的 data,返回 error。func (d *file) WriteData(data interface{}) error { // 模拟写入数据 fmt.Println("WriteData:", data) return nil}// Go语言的接口实现是隐式的,无须让实现接口的类型写出实现了哪些接口。这个设计被称为非侵入式设计。func (d *file) CanWrite() bool { // 模拟写入数据 return true}
总结
以上是内存溢出为你收集整理的【java转go】Go语言接口笔记全部内容,希望文章能够帮你解决【java转go】Go语言接口笔记所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)