漫画 Go 语言 方法与接口

什么是方法

在Go语言中方法和函数类似,也可以认为方法是特殊类型的函数,只不过方法限制了接收者,方法也可以说是包含了接收者的函数。

9 臭流氓任我行的吸星大法 --方法与接口 - 图1

9 臭流氓任我行的吸星大法 --方法与接口 - 图2

结构体类型调用方法

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. //使用Role结构体创建一个角色代表任我行
  7. rwx := Role{"任我行", "吸星大法", 10, 9.9}
  8. rwx.Kungfu()//调用这个结构体的方法。
  9. }
  10. //创建一个结构体代表人物角色--任我行
  11. type Role struct {
  12. Name string //姓名
  13. Ability string //技能
  14. Level int //级别
  15. Kill float64 //杀伤力
  16. }
  17. //创建一个方法,只要是Role结构体就能调用。
  18. func (r Role) Kungfu() {
  19. fmt.Printf("我是:%s,我的武功:%s,已经练到%d级了,杀伤力%.1f\n", r.Name, r.Ability, r.Level, r.Kill)
  20. }

9 臭流氓任我行的吸星大法 --方法与接口 - 图3

9 臭流氓任我行的吸星大法 --方法与接口 - 图4

指针类型方法

方法的接收者可以是结构体类型,也可以是一个值,或者是一个指针类型。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. rwx := &Role{"任我行", "吸星大法", 8, 10}
  7. rwx.Kungfu() //使用Role该类型的指针调用方法
  8. zwj := &Role{"张无记", "九娘神功", 9, 12}
  9. zwj.Kungfu2() //调用指针类型方法
  10. }
  11. //创建一个结构体代表人物角色--任我行或者张无忌
  12. type Role struct {
  13. Name string //姓名
  14. Ability string //技能
  15. Level int //级别
  16. Kill float64 //杀伤力
  17. }
  18. func (r Role) Kungfu() {
  19. fmt.Printf("我是:%s,我的武功:%s,已经练到%d级了,杀伤力%.1f\n", r.Name, r.Ability, r.Level, r.Kill)
  20. }
  21. //指针类型方法
  22. func (r *Role) Kungfu2() {
  23. fmt.Printf("我是:%s,我的武功:%s,已经练到%d级了,杀伤力%.1f\n", r.Name, r.Ability, r.Level, r.Kill)
  24. }

9 臭流氓任我行的吸星大法 --方法与接口 - 图5

任意类型方法

在Go语言中,使用type关键字可以定义出新的自定义类型,有了自定义类型之后我们就可以为自定义类型添加各种方法了。

9 臭流氓任我行的吸星大法 --方法与接口 - 图6

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. // 将好家伙 定义为int类型
  6. type Haojiahuo int
  7. // 使用Clear方法将Haojiahuo的所有值清空
  8. func (h Haojiahuo) Clear() bool {
  9. h = 0
  10. return h == 0
  11. }
  12. // 使用Add方法给Haojiahuo增加值
  13. func (h Haojiahuo) Add(num int) int {
  14. return int(h) + num
  15. }
  16. func main() {
  17. var h Haojiahuo
  18. fmt.Println(h.Clear()) //调用清空方法
  19. fmt.Println(h.Add(2)) //调用添加方法加2
  20. fmt.Println(h.Clear()) //调用清空方法
  21. fmt.Println(h.Add(6)) //调用添加方法加6
  22. fmt.Println(h.Clear()) //调用清空方法
  23. fmt.Println(h)
  24. }

函数与方法的区别

  • 方法限制某个类别的行为,需要指定调用者。函数是一段独立的功能代码,可以直接调用。
  • 方法名称可以相同,只要接收者不同就可以,函数命名上不能冲突。

Go语言实现面向对象

其实在Go语言中是没有面向对象的,但是Go语言的语法设计,我们可以借助结构体,方法,接口的实现,来模拟其他语言中的面向对象的概念。首先了解一下什么是面向对象,面向对象中的三大特征是:

  • 封装 在意义上是把许多客观的事物封装成一个抽象的类,把自己的属性 方法只让可信的对象操作。
  • 继承 子类可以访问父类的属性和方法,子类也可以拥有自己的属性和方法。子类可以重写父类的方法。
  • 多态 是指一个程序中同名的方法共存的情况,调用者只需使用同一个方法名,系统会根据不同情况,调用相应的不同方法,从而实现不同的功能。多态性又被称为“一个名字,多个方法”。

1,使用结构体来实现封装

Go语言中没有像java或者.net中的class类,不过可以把struct结构体看成一个类,结构体如果用面向对象的思维来理解,结构体把字段封装到一起,数据被保护在结构体内部,程序需要访问字段的时候,需要通过结构体来访问。

9 臭流氓任我行的吸星大法 --方法与接口 - 图7

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. // 定义结构体实现封装
  6. type Haojiahuo struct {
  7. Name string
  8. Age int
  9. }
  10. //使用NewPerson方法创建一个对象
  11. func NewPerson(name string) *Haojiahuo {
  12. return &Haojiahuo{
  13. Name: name,
  14. }
  15. }
  16. // 使用SetAge方法设置结构体成员的Age
  17. func (h *Haojiahuo) SetAge(age int) {
  18. h.Age = age
  19. }
  20. // 使用GetAge方法获取成员现在的Age
  21. func (h *Haojiahuo) GetAge() int {
  22. return h.Age
  23. }
  24. func main() {
  25. //创建一个对象
  26. h := NewPerson("好家伙")
  27. h.SetAge(18) //访问封装的方法设置年龄
  28. fmt.Println(h.Name, h.GetAge()) //使用对象封装的方法获取年龄
  29. }

2,继承的实现

继承可以解决代码复用的问题,结构体内嵌套一个匿名结构体,也可以嵌套多层结构体。

9 臭流氓任我行的吸星大法 --方法与接口 - 图8

9 臭流氓任我行的吸星大法 --方法与接口 - 图9

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. // 创建一个结构体起名 Ouyangcrazy 代表父类
  6. type Ouyangcrazy struct {
  7. Name string
  8. Age int
  9. Ability string
  10. }
  11. //创建一个结构体代表子类
  12. type YangGuo struct {
  13. Ouyangcrazy //包含父类所有属性
  14. Address string //单独子类有的字段
  15. }
  16. // 父类的方法
  17. func (o *Ouyangcrazy) ToadKongfu() {
  18. fmt.Println(o.Name, "的蛤蟆功!")
  19. }
  20. //子类的方法
  21. func (y *YangGuo) NewKongfu() {
  22. fmt.Println(y.Name, "子类自己的新功夫!")
  23. }
  24. //子类重写父类的方法
  25. // func (y *YangGuo) ToadKongfu() {
  26. // fmt.Println(y.Name, "的新蛤蟆功!")
  27. // }
  28. func main() {
  29. o := &Ouyangcrazy{Name: "欧阳疯", Age: 70} //创建父类
  30. o.ToadKongfu() //父类对象访问父类方法
  31. y := &YangGuo{Ouyangcrazy{Name: "杨过", Age: 18}, "古墓"} //创建子类
  32. fmt.Println(y.Name) //子类对象访问父类中有的字段
  33. fmt.Println(y.Address) //子类访问自己的字段
  34. y.ToadKongfu() //子类对象访问父类方法
  35. y.NewKongfu() //子类访问自己的方法
  36. //y.ToadKongfu() //如果存在自己的方法 访问自己重写的方法
  37. }

9 臭流氓任我行的吸星大法 --方法与接口 - 图10

接口

9 臭流氓任我行的吸星大法 --方法与接口 - 图11

9 臭流氓任我行的吸星大法 --方法与接口 - 图12

3,使用接口来实现多态

接口的意义是对其他类型的一个概括,接口内可以定义很多个方法,谁将这些方法实现,就可以认为是实现了该接口。Go语言的多态,主要是通过接口来实现。

9 臭流氓任我行的吸星大法 --方法与接口 - 图13

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. h := Haojiahuo{name: "好家伙"}
  7. fmt.Println(h.name)
  8. l := Laolitou{name: "老李头"}
  9. fmt.Println(l.name)
  10. //testInterface 需要参数类型为Kongfu接口类型的参数
  11. //h实现了Kongfu接口的方法
  12. //h就是这个接口的实现 就可以作为这个函数的参数
  13. testInterface(h)
  14. var kf Kongfu
  15. kf = h
  16. kf.Toad()
  17. l.PlayGame()
  18. }
  19. //测试方法
  20. func testInterface(k Kongfu) {
  21. k.Toad()
  22. k.SixSwords()
  23. }
  24. //定义接口
  25. type Kongfu interface {
  26. Toad() //蛤蟆功
  27. SixSwords() //六脉神剑
  28. }
  29. //实现类
  30. type Haojiahuo struct {
  31. name string
  32. }
  33. //实现类
  34. type Laolitou struct {
  35. name string
  36. }
  37. //实现方法
  38. func (o Haojiahuo) Toad() {
  39. fmt.Println(o.name, "实现了蛤蟆功..")
  40. }
  41. //实现方法
  42. func (o Haojiahuo) SixSwords() {
  43. fmt.Println(o.name, "实现了六脉神剑..")
  44. }
  45. //实现方法
  46. func (f Laolitou) Toad() {
  47. fmt.Println(f.name, "也实现了蛤蟆功..")
  48. }
  49. //实现方法
  50. func (f Laolitou) SixSwords() {
  51. fmt.Println(f.name, "也实现了六脉神剑.")
  52. }
  53. //实现自己的方法
  54. func (f Laolitou) PlayGame() {
  55. fmt.Println(f.name, "玩游戏..")
  56. }

9 臭流氓任我行的吸星大法 --方法与接口 - 图14

使用接口对方法进行约束,然后让方法实现接口,这样规范了方法。通过使用同样的接口名称,但是在调用的时候使用不同的类,实现执行不同的方法。这样就实现了Go语言中的多态。

空接口

空接口就是不包含任何方法的接口,所有的类型都可以实现空接口,因此空接口可以实现存储任意类型的数据, 谁实现它就被看作是谁的实现类。

  1. package main
  2. import (
  3. "fmt"
  4. )
  5. func main() {
  6. Test1(0)//使用int类型做为参数传入到函数
  7. Test1("ceshi")//使用string类型做为参数传入到函数
  8. }
  9. //空接口
  10. type T interface {
  11. }
  12. //定义一个函数 接收Test接口类型的数据
  13. func Test1(t T) {
  14. fmt.Println(t)
  15. }

9 臭流氓任我行的吸星大法 --方法与接口 - 图15

可以将空接口类型写为interface{} 这种类型可以理解为任何类型。类似其他语言中的object

空接口的使用

空接口既然可以传任意类型,利用这个特性可以把空接口interface{}当做容器使用。

  1. func Test() {
  2. //创建一个map类型 key为string val为空接口,这样值就可以存储任意类型了
  3. m := make(map[string]interface{})
  4. m["a"] = "zhangsan"
  5. m["b"] = 1.1
  6. m["c"] = true
  7. fmt.Println(m)
  8. }
  9. package main
  10. import "fmt"
  11. // 字典结构
  12. type Dictionary struct {
  13. data map[string]interface{} // 数据key为string值为interface{}类型
  14. }
  15. // 获取值
  16. func (d *Dictionary) GetData(key string) interface{} {
  17. return d.data[key]
  18. }
  19. // 设置值
  20. func (d *Dictionary) SetData(key string, value interface{}) {
  21. d.data[key] = value
  22. }
  23. // 创建一个字典
  24. func NewDict() *Dictionary {
  25. return &Dictionary{
  26. data: make(map[string]interface{}),//map类型使用前需要初始化,所以需要使用make创建 防止空指针异常。
  27. }
  28. }
  29. func main() {
  30. // 创建字典实例
  31. dict := NewDict()
  32. // 添加数据
  33. dict.SetData("001", "第一条数据")
  34. dict.SetData("002", 3.1415)
  35. dict.SetData("003", false)
  36. // 获取值
  37. d := dict.GetData("001")
  38. fmt.Println(d)
  39. }

Go语言中的错误

错误和异常不同,错误是在程序中正常存在的,可以预知的失败在意料之中。而异常通常指在不应该出现问题的地方出现问题,比如空指针,这在人们的意料之外。go语言没有 try......catch 这样的方式来捕获异常所以Go定义属于自己的一种错误类型,用error表示错误。

9 臭流氓任我行的吸星大法 --方法与接口 - 图16

例如:我们在读取一个不存在的文件时候。如果文件正常存在就返回文件的内容,否则就返回一个err信息。

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. )
  6. func main() {
  7. //使用io/ioutil包下的读取文件方法
  8. conent, err := ioutil.ReadFile("test.txt")
  9. if err != nil {
  10. fmt.Println(err)//打印错误信息
  11. } else {
  12. fmt.Println(string(conent))//返回正常信息
  13. }
  14. }

通常情况一个函数如果有错误,都会在返回值最后一个,返回一个error类型的错误,根据这个值来判断是否是非nil的值,如果是nil表示没有错误,如果nil不为空,则需要进行错误处理。

error的定义是一个接口,接口内部包含一个返回字符串类型的方法Error()

  1. type error interface {
  2. Error() string
  3. }

知道了error的定义是一个接口类型,那么只要实现了这个接口 都可以用来处理错误信息。来返回一个错误提示给用户。Go语言也提供了一个内置包errors ,来创建一个错误对象。

9 臭流氓任我行的吸星大法 --方法与接口 - 图17

  1. package main
  2. import (
  3. "errors"
  4. "fmt"
  5. )
  6. func main() {
  7. err := errors.New("错误信息...")
  8. fmt.Println(err)
  9. num, err2 := Calculation(0)
  10. fmt.Println(num, err2)
  11. }
  12. //通过内置errors包创建错误对象来返回
  13. func Calculation(divisor int) (int, error) {
  14. if divisor == 0 {
  15. return 0, errors.New("错误:除数不能为零.")
  16. }
  17. return 100 / divisor, nil
  18. }