Go语言学习之5 进阶-排序、链表、二叉树、接口

本节主要内容:

1. 结构体和方法
2. 接口

1. 结构体和方法

     (1). 用来自定义复杂数据结构
     (2). struct里面可以包含多个字段(属性)
     (3). struct类型可以定义方法,注意和函数的区分
     (4). struct类型是值类型
     (5). struct类型可以嵌套
     (6). Go语言没有class类型,只有struct类型

(1). struct 声明:
     type 标识符 struct {
           field1 type
           field2 type
      }

1 type Student struct {
2     Name string
3     Age int
4     Score int
5 }
example

(2). struct 中字段访问:和其他语言一样,使用点(.)

1 var stu Student
2 
3 stu.Name = "tony"
4 stu.Age = 18
5 stu.Score=20
6 
7 fmt.Printf("name=%s age=%d score=%d", stu.Name, stu.Age, stu.Score)
example

(3). struct定义的三种形式:
     (a) var stu Student
     (b) var stu *Student = new (Student)
     (c) var stu *Student = &Student{}

     其中b和c返回的都是指向结构体的指针,访问形式如下:
     stu.Name、stu.Age和stu.Score 或者 (*stu).Name、(*stu).Age。

 1 package main 
 2 
 3 import "fmt"
 4 
 5 type Student struct {
 6     Name string
 7     Age int
 8     score float32
 9 }
10 
11 func main() {
12     //下面定义并初始化
13     var stu1 Student = Student {
14         Name : "zhangsan",
15         Age : 10,
16         score : 99.99,
17     }
18 
19     //struct定义的形式1
20     var stu2 Student
21     stu2.Name = "zhangsan2"
22     stu2.Age = 15
23     stu2.score = 99.66
24     
25     //struct定义的形式2
26     var stu3 *Student = new(Student)
27     stu3.Name = "lisi" //(*stu1).Name = "lisi"
28     stu3.Age = 20  //(*stu1).Age = 20
29     stu3.score = 88.88  //(*stu1).score = 88.88
30 
31     //struct定义的形式3
32     var stu4 *Student = &Student{
33         Name:"wangwu", 
34         Age:19,  
35         score:99.88,
36     }
37 
38     fmt.Println(stu1)  //{zhangsan 10 99.99}
39     fmt.Println(stu2)  //{zhangsan2 15 99.66}
40     fmt.Println(stu3)  //&{lisi 20 88.88}
41     fmt.Println(stu4)  //&{wangwu 19 99.88}
42 }
example

(4). struct的内存布局:struct中的所有字段在内存是连续的,布局如下:

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Student struct {
 6     Name  string
 7     Age   int
 8     score float32
 9 }
10 
11 func main() {
12     var stu Student
13 
14     stu.Age = 18
15     stu.Name = "hua"
16     stu.score = 80
17 
18     var stu1 *Student = &Student{
19         Age:  20,
20         Name: "hua",
21     }
22 
23     var stu3 = Student{
24         Age:  20,
25         Name: "hua",
26     }
27     
28     fmt.Println(stu1.Name)
29     fmt.Println(stu3)
30     fmt.Printf("Name:%p
", &stu.Name) //Name:0xc042002720
31     fmt.Printf("Age: %p
", &stu.Age)  //Age: 0xc042002730
32     fmt.Printf("score:%p
", &stu.score)  //score:0xc042002738
33 }
example

(5). 链表定义
     type Student struct {
          Name string
          Next* Student
     }
    每个节点包含下一个节点的地址,这样把所有的节点串起来了,通常把链表中的第一个节点叫做链表头。

  1 package main
  2 
  3 import (
  4     "fmt"
  5     "math/rand"
  6 )
  7 
  8 type Student struct {
  9     Name string
 10     Age int 
 11     Score float32
 12     Id string
 13     next *Student
 14 }
 15 
 16 //遍历链表
 17 func trans(p *Student) {
 18     for p != nil {
 19         fmt.Printf("name = %s, Age = %d, Score = %f, id = %s, next = %p
", p.Name, p.Age, p.Score, p.Id, p.next)
 20         p = p.next
 21     }
 22     fmt.Println()
 23 }
 24 
 25 //头部插入
 26 func insertHead(head **Student, new_node *Student) {
 27     new_node.next = *head
 28     *head = new_node
 29 }
 30 
 31 //尾部插入
 32 func insertTail(p *Student, new_node *Student) {
 33     for p.next != nil {
 34         p = p.next
 35     }
 36     p.next = new_node
 37 }
 38 
 39 //删除节点
 40 func delNode(p *Student, id string) {
 41     var pre_node *Student = p
 42     for p != nil {
 43         if p.Id == id {
 44             pre_node.next = p.next
 45             break
 46         }
 47         pre_node = p
 48         p = p.next
 49     }
 50 }
 51 
 52 //当前节点后面插入
 53 func addNode(p *Student, id string, add_node *Student) {
 54     for p != nil {
 55         if p.Id == id {
 56             add_node.next = p.next
 57             p.next = add_node
 58             break
 59         }
 60         p = p.next
 61     }
 62 }
 63 
 64 func checkNode(p *Student, id string) {
 65     for p != nil {
 66         if p.Id == id {
 67             fmt.Printf("name = %s, Age = %d, Score = %f, id = %s, next = %p
", p.Name, p.Age, p.Score, p.Id, p.next)
 68             return
 69         }
 70         p = p.next
 71     }
 72     fmt.Printf("Do not find id = %s
", id)
 73 }
 74 
 75 func main() {
 76     var stu1 Student = Student {
 77         Name:"name1",
 78         Age:rand.Intn(100),
 79         Score:rand.Float32()*100,
 80         Id:"000001",
 81     }
 82     trans(&stu1)
 83 
 84     var head *Student = &stu1
 85 
 86     var stu2 Student = Student {
 87         Name:"name2",
 88         Age:rand.Intn(100),
 89         Score:rand.Float32()*100,
 90         Id:"000002",
 91     }
 92     insertHead(&head, &stu2)  //头部插入
 93     trans(head)
 94 
 95     var stu3 Student = Student {
 96         Name:"name3",
 97         Age:rand.Intn(100),
 98         Score:rand.Float32()*100,
 99         Id:"000003",
100     }
101 
102     insertTail(head, &stu3)  //尾部插入
103     trans(head)
104 
105     for i := 4; i < 10 ; i++ {
106         stu := Student {
107             Name:fmt.Sprintf("name%d", i),
108             Age:rand.Intn(100),
109             Score:rand.Float32()*100,
110             Id:fmt.Sprintf("00000%d", i),
111         }
112 
113         addNode(head, "000003", &stu)  //增加节点
114     }
115     trans(head)
116 
117     delNode(head, "000005")  //删除节点
118     trans(head)
119 
120     checkNode(head, "000006") //
121     checkNode(head, "0000010")
122 }
单链表的增、删、查

(6). 双链表定义
     type Student struct {
          Name string
          Next* Student
          Prev* Student
     }
    如果有两个指针分别指向前一个节点和后一个节点,我们叫做双链表

(7). 二叉树定义
     type Student struct {
          Name string
          left* Student
          right* Student
     }
    如果每个节点有两个指针分别用来指向左子树和右子树,我们把这样的结构叫做二叉树

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Student struct {
 6     Name  string
 7     Age   int
 8     Score float32
 9     left  *Student
10     right *Student
11 }
12 
13 func trans(root *Student) {
14     if root == nil {
15         return
16     }
17     fmt.Println(root)
18 
19     trans(root.left)
20     trans(root.right)
21 
22 }
23 
24 func main() {
25     var root *Student = new(Student)
26 
27     root.Name = "stu01"
28     root.Age = 18
29     root.Score = 100
30 
31     var left1 *Student = new(Student)
32     left1.Name = "stu02"
33     left1.Age = 18
34     left1.Score = 100
35 
36     root.left = left1
37 
38     var right1 *Student = new(Student)
39     right1.Name = "stu04"
40     right1.Age = 18
41     right1.Score = 100
42 
43     root.right = right1
44 
45     var left2 *Student = new(Student)
46     left2.Name = "stu03"
47     left2.Age = 18
48     left2.Score = 100
49 
50     left1.left = left2
51 
52     trans(root)
53 }
二叉树示例

(8). 结构体是用户单独定义的类型,不能和其他类型进行强制转换

 1 package main 
 2 
 3 func main() {
 4     type Student struct {
 5         Number int
 6     }
 7 
 8     type Stu Student //alias
 9 
10     var a Student
11     a.Number = 10
12 
13     var b Stu
14     a = b // cannot use b (type Stu) as type Student in assignment
15 }
example
 1 package main
 2 
 3 import "fmt"
 4 
 5 type integer int
 6 
 7 type Student struct {
 8     Number int
 9 }
10 
11 type Stu Student //alias
12 
13 func main() {
14 
15     var i integer = 1000
16     var j int = 100
17 
18     // j = i  //cannot use i (type integer) as type int in assignment
19     j = int(i)  //进行强制转换  ok
20     fmt.Println(j)
21 
22     var a Student
23     a = Student{30}
24 
25     var b Stu
26     a = Student(b)  //进行强制转换  ok
27     fmt.Println(a)  //{0}
28 }
example2

(9).(工厂模式) golang中的struct没有构造函数,一般可以使用工厂模式来解决这个问题

 1 package main 
 2 
 3 import "fmt"
 4 
 5 type student struct {
 6     Name string
 7     Age int
 8 }
 9 
10 func NewStudent(name string, age int) *student {
11     return &student{
12         Name:name,
13         Age:age,
14     }
15 }
16 
17 func main() {
18     s := new (student)
19     s = NewStudent("tony", 20)
20     fmt.Println(s) //&{tony 20}
21 }
example

(10). 再次强调
     a). make 用来创建map、slice、channel
     b). new用来创建值类型

(11). (struct中的tag) 我们可以为struct中的每个字段,写上一个tag。这个tag可以通过反射的机制获取到,最常用的场景就是json序列化和反序列化
     type student struct {
            Name stirng "this is name field"
            Age int "this is age field"
      }

 1 package main
 2 
 3 import (
 4     "encoding/json"
 5     "fmt"
 6 )
 7 
 8 type Student struct {
 9     Name  string `json:"student_name"`
10     Age   int    `json:"age"`
11     Score int    `json:"score"`
12 }
13 
14 type Student2 struct {
15     name  string
16     age   int
17     score int
18 }
19 
20 func main() {
21     var stu Student = Student{
22         Name:  "stu01",
23         Age:   18,
24         Score: 80,
25     }
26 
27     data, err := json.Marshal(stu)
28     if err != nil {
29         fmt.Println("json encode stu failed, err:", err)
30         return
31     }
32 
33     fmt.Println(string(data))  //{"student_name":"stu01","age":18,"score":80}
34 
35     var stu2 Student2 = Student2{
36         name:  "stu02",
37         age:   20,
38         score: 90,
39     }
40 
41     data2, err2 := json.Marshal(stu2)
42     if err2 != nil {
43         fmt.Println("json encode stu failed, err:", err2)
44         return
45     }
46 
47     fmt.Println(string(data2))  // {}  由于结构体成员变量首字母小写,在json序列化时对外不可见,因此为空。改为首字母大写就OK
48 }
tag

(12). (匿名字段)结构体中字段可以没有名字,即匿名字段
      type Car struct {
            Name string
            Age int
      }

      type Train struct {
           Car
           Start time.Time
           int
      }

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "time"
 6 )
 7 
 8 type Car struct {
 9     Name string
10     Age int 
11 }
12 
13 type Train struct {
14     Car
15     Start time.Time
16     int
17 }
18 
19 func main() {
20     var t Train
21     
22     //如果没有命名冲突可以直接这样访问
23     //t.Name = "demo"
24     //t.Age = 20
25     
26     t.Car.Name = "demo"
27     t.Car.Age = 20
28     t.int = 100
29 
30     fmt.Println(t) //{{demo 20} 0001-01-01 00:00:00 +0000 UTC 100}
31 }
匿名字段示例

(13). 匿名字段冲突处理

 1 package main
 2 
 3 import (
 4     "fmt"
 5 )
 6 
 7 type Cart1 struct {
 8     name string
 9     age  int
10 }
11 
12 type Cart2 struct {
13     name string
14     age  int
15 }
16 
17 type Train struct {
18     Cart1
19     Cart2
20 }
21 
22 func main() {
23     var t Train
24 
25     // t.name = "train"
26     // t.age = 100
27 
28     // fmt.Println(t) //ambiguous selector t.name
29     
30     t.Cart1.name = "train1"
31     t.Cart1.age = 100
32 
33     t.Cart2.name = "train2"
34     t.Cart2.age = 200
35 
36     fmt.Println(t) //{{train1 100} {train2 200}}
37 }
匿名字段冲突示例

 (14). 方法

        a. 方法定义

            方法其实就是一个函数,在 func 这个关键字和方法名中间加入了一个特殊的接收器类型。接收器可以是结构体类型或者是非结构体类型。接收器是可以在方法的内部访问的。

             Golang中的方法是作用在特定类型的变量上,因此自定义类型,都可以有方法,而不仅仅是struct。

             定义:func (recevier type) methodName(参数列表)(返回值列表) {}

 1 package main 
 2 
 3 import "fmt"
 4 
 5 type Student struct {
 6     Name string
 7     Age int
 8 }
 9 
10 //为结构体Student定义init方法
11 func (p *Student) init(name string, age int) {
12     p.Name = name
13     p.Age = age
14 }
15 
16 func main() {
17     var stu Student
18     stu.init("zhansan", 20)
19     fmt.Printf("name = %s, age = %d
", stu.Name, stu.Age) //name = zhansan, age = 20
20 }
example

"类的"方法:
        Go 语言不像其它面相对象语言一样可以写个类,然后在类里面写一堆方法,但其实Go语言的方法很巧妙的实现了这种效果:我们只需要在普通函数前面加个接受者(receiver,写在函数名前面的括号里面),这样编译器就知道这个函数(方法)属于哪个struct了。

1). 在 Go 中,(接收者)类型关联的方法不写在类型结构里面,就像类那样;耦合更加宽松;类型和方法之间的关联由接收者来建立。
2). 方法没有和数据定义(结构体)混在一起:它们是正交的类型;表示(数据)和行为(方法)是独立的。

注意:Go语言不允许为简单的内置类型添加方法,所以下面定义的方法是非法的。

1 package main
2 
3 //cannot define new methods on non-local type int
4 func (a int) add(b int) {
5 }
6 
7 func main() {
8 
9 }
error example
 1 package main
 2 
 3 import(
 4   "fmt"
 5 )
 6 
 7 //将int定义别名myInt
 8 type myInt int
 9  
10 func Add(a ,b int) int {             //函数
11     return a + b
12 }
13 
14 //cannot define new methods on non-local type int
15 // func (a int) Add(b int) {
16 // }
17 
18 //对myInt类型定义Add方法
19 func (a myInt) Add (b myInt) myInt {   //方法
20     return a + b
21 }
22  
23 func main() {
24     a, b := 3,4
25     var aa, bb myInt = 3, 4
26     fmt.Println(Add(a, b)) //7
27     fmt.Println(aa.Add(bb))  //7
28 }
right example

        b. 方法的调用

 1 package main
 2 
 3 import "fmt"
 4 
 5 type A struct {
 6     a int
 7 }
 8 
 9 func (this A) test() {
10     fmt.Println(this.a)
11 }
12 
13 func main() {
14     var t A
15     t.a = 100
16     t.test() //100
17 }
example

        c. 方法和函数的区别
           函数调用: function(variable, 参数列表)
           方法:variable.function(参数列表)

        为什么我们已经有函数了还需要方法呢?

        I). Go 不是纯粹的面向对象编程语言,而且Go不支持类。因此,基于类型的方法是一种实现和类相似行为的途径。
        II). 相同的名字的方法可以定义在不同的类型上,而相同名字的函数是不被允许的。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type People struct {
 6     Age int
 7 }
 8 
 9 type Animal struct {
10     Age int
11 }
12 
13 func (p People) Eat() {
14     fmt.Println("People age is ", p.Age)
15 }
16 
17 func (a Animal) Eat() {
18     fmt.Println("Animal age is ", a.Age)
19 }
20 
21 func main() {
22     var p People = People {
23         Age:20,
24     }
25 
26     var a Animal = Animal {
27         Age:2,
28     }
29 
30     p.Eat()
31     a.Eat()
32 }
example

       d. 指针接收器与值接收器

        本质上和函数的值传递和地址传递是一样的。

        在上面的例子中,我们只使用值接收器的方法。还可以创建使用指针接收器的方法。值接收器和指针接收器之间的区别在于,在指针接收器的方法内部的改变对于调用者是可见的,然而值接收器的情况不是这样的。

 1 #include<stdio.h>
 2 
 3 void set(int *s, int newValue)
 4 {
 5     *s = newValue;
 6 }
 7 
 8 int main()
 9 {
10     int num = 1;
11     printf("before num = %d
", num);  //before num = 1
12     set(&num, 10);
13     printf("after num = %d
", num);  //after num = 10
14 }
C语言通过传递指针改变变量的值
 1 package main
 2 
 3 import "fmt"
 4 
 5 type People struct {
 6     Name string
 7     Age int
 8 }
 9 
10 func (p People) ChangeAge(age int) {
11     p.Age = age
12 }
13 
14 func (p *People) ChangeName(name string) {
15     p.Name = name
16 }
17 
18 func main() {
19     var p People = People {
20         Name:"zhangsan",
21         Age:20,
22     }
23     
24     fmt.Printf("before name = %s, age = %d
", p.Name, p.Age) //before name = zhangsan, age = 20
25     // (&p).ChangeName("lisi")   //OK
26     p.ChangeName("lisi") //p.ChangeName("lisi") 自动被Go语言解释为 (&p).ChangeName("lisi")
27     p.ChangeAge(10)
28     fmt.Printf("after name = %s, age = %d
", p.Name, p.Age) //after name = lisi, age = 20
29 }
值传递与指针传递区别

       那么什么时候使用指针接收器,什么时候使用值接收器?
        一般来说,指针接收器可以使用在:对方法内部的接收器所做的改变应该对调用者可见时。
指针接收器也可以被使用在如下场景:
      1. 当拷贝一个结构体的代价过于昂贵时。
          考虑下一个结构体有很多的字段。在方法内使用这个结构体做为值接收器需要拷贝整个结构体,这是很昂贵的。在这种情况下使用指针接收器,结构体不会被拷贝,只会传递一个指针到方法内部使用。
      2. 在其他的所有情况,值接收器都可以被使用。

      e. 在方法中使用值接收器 与 在函数中使用值参数

      i) 当一个函数有一个值参数,它只能接受一个值参数。

      ii) 当一个方法有一个值接收器,它可以接受值接收器和指针接收器。

      iii) 当一个方法有一个指针接收器,它可以接受值接收器和指针接收器。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Car struct {
 6     weight int
 7     name   string
 8 }
 9 
10 func InitChange(p Car) {
11     p.name = "func"
12     p.weight = 200
13 }
14 
15 //值接收器
16 func (p Car) InitChange() {
17     p.name = "receiver"
18     p.weight = 600
19     
20 }
21 
22 //指针接收器
23 func (p *Car) InitChange2() {
24     p.name = "receiver2"
25     p.weight = 800
26     
27 }
28 
29 func main() {
30     var c Car = Car{
31         weight:100,
32         name:"bike",
33     }
34     
35     p := &c
36     
37     // Run(&c) // cannot use p (type *Car) as type Car in argument to Run
38     InitChange(c)  //传值
39     fmt.Println(c, " running in the func") //{200 bike}  running in the func
40     
41     // c.Run()
42     // 为了方便Go语言把 p.Run() 解释为 (*p).Run(),因此在Run中改变值不起作用
43     p.InitChange()  //{100 receiver}  running int the receiver
44     fmt.Println(c, " running in the receiver") //{100 bike}  running in the receiver
45 
46     // 为了方便Go语言把 c.Run() 解释为 (&c).Run(),因此在Change中改变值起作用
47     // c.InitChange2()  //传值
48     p.InitChange2()  //传指针
49     fmt.Println(c, " running in the receiver2") //{800 receiver2}  running in the Change
50 }
example

      f. 匿名字段的方法

       属于结构体的匿名字段的方法可以被直接调用,就好像这些方法是属于定义了匿名字段的结构体一样。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Car struct {
 6     weight int
 7     name   string
 8 }
 9 
10 func (p Car) Run() {
11     fmt.Println("running")
12 }
13 
14 //Bike不仅继承了Car的成员变量weight和name,同时继承了Run方法
15 type Bike struct {
16     Car //匿名字段
17     wheel int
18 }
19 
20 func main() {
21     var b Bike = Bike {
22         Car: Car{
23             weight:100,
24             name:"bike",
25         },
26         wheel:2,
27     }
28     
29 
30     fmt.Println(b) //{{100 bike} 2}
31     b.Run() //running 匿名字段方法 Run 
32 }
调用匿名字段方法

      g. 方法的访问控制,通过大小写控制

      在不同的包之间,方法要对外可见需要首字母大写。

      h. 继承

      如果一个struct嵌套了另一个匿名结构体,那么这个结构可以直接访问匿名结构体的方法,从而实现了继承。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Car struct {
 6     weight int
 7     name   string
 8 }
 9 
10 func (p Car) Run() {
11     fmt.Println("running")
12 }
13 
14 //Bike不仅继承了Car的成员变量weight和name,同时继承了Run方法
15 type Bike struct {
16     Car
17     wheel int
18 }
19 
20 func main() {
21     var a Bike
22     a.weight = 100
23     a.name = "bike"
24     a.wheel = 2
25 
26     fmt.Println(a) //{{100 bike} 2}
27     a.Run() //running
28 }
example

      i. 组合和匿名字段

      如果一个struct嵌套了另一个有名结构体,那么这个模式就叫组合。

      go里面的继承是通过组合来实现的。
      匿名字段是一个特殊的组合。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Car struct {
 6     weight int
 7     name   string
 8 }
 9 
10 func (p Car) Run() {
11     fmt.Println("running")
12 }
13 
14 type Bike struct {
15     Car
16     lunzi int
17 }
18 
19 type Train struct {
20     c Car  //组合
21 }
22 
23 func main() {
24     var a Bike
25     a.weight = 100
26     a.name = "bike"
27     a.lunzi = 2
28 
29     fmt.Println(a)
30     a.Run()
31 
32     var b Train
33     //注意访问方式
34     b.c.weight = 100
35     b.c.name = "train"
36     b.c.Run()
37 }
组合

      j. 多重继承

      如果一个struct嵌套了多个匿名结构体,那么这个结构可以直接访问多个匿名结构体的方法,从而实现了多重继承。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type People struct {
 6     Name string
 7     Age  int
 8 }
 9 
10 type Animal struct {
11     Place  string
12     Weight   int
13 }
14 
15 func (p People) Eat() {
16     fmt.Println("People eat food")
17 }
18 
19 func (p People) Sleep() {
20     fmt.Println("People sleep")
21 }
22 
23 func (p Animal) Eat() {
24     fmt.Println("Animal sleep")
25 }
26 
27 func (p Animal) Run() {
28     fmt.Println("Animal running")
29 }
30 
31 func (p Animal) Cry() {
32     fmt.Println("Animal cry")
33 }
34 
35 //Test继承了People和Animal里面的成员变量和方法
36 type Test struct {
37     People
38     Animal
39 }
40 
41 func main() {
42     var t Test
43     t.Name = "sara"
44     t.Age = 20
45 
46     t.Place = "xian"
47     t.Weight = 200
48 
49     // t.Eat()  //ambiguous selector t.Eat
50     t.People.Eat()
51     t.Animal.Eat()
52 
53     t.Sleep()  //t.People.Sleep()
54     t.Run() //t.Animal.Run() 
55     t.Cry() //t.Animal.Cry() 
56 }
example

2. 接口

什么是接口?
       在面向对象的领域里,接口一般这样定义:接口定义一个对象的行为。接口只指定了对象应该做什么,至于如何实现这个行为(即实现细节),则由对象本身去确定。

       在 Go 语言中,接口就是方法签名(Method Signature)的集合。当一个类型定义了接口中的所有方法,我们称它实现了该接口。这与面向对象编程(OOP)的说法很类似。接口指定了一个类型应该具有的方法,并由该类型决定如何实现这些方法。

(1). 定义
      Interface类型可以定义一组方法,但是这些不需要实现。并且interface不能包含任何变量。
      type example interface{

             Method1(参数列表) 返回值列表
             Method2(参数列表) 返回值列表
             …
      }

 1 package main
 2 
 3 import "fmt"
 4 
 5 type People struct {
 6     name string
 7     age  int
 8 }
 9 
10 type Test interface {
11     Eat()
12     Sleep()
13 }
14 
15 func (p People) Eat() {
16     fmt.Println("people eat")
17 }
18 
19 func (p People) Sleep() {
20     fmt.Println("people sleep")
21 }
22 
23 func main() {
24 
25     var t Test
26     fmt.Println(t) //<nil>
27 
28     var people People = People {
29         name: "people",
30         age:  100,
31     }
32 
33     t = people
34     t.Eat()
35     t.Sleep()
36 
37     fmt.Println("t:", t)  //t: {people 100}
38 }
example

(2). interface类型默认是一个指针
     如(1)中的例子var t Test    fmt.Println(t) //<nil>

(3). 接口的内部表示

     我们可以把接口看作内部的一个元组 (type, value)。 type 是接口底层的具体类型(Concrete Type),而 value 是具体类型的值。

 1 package main
 2 
 3 import (  
 4     "fmt"
 5 )
 6 
 7 type Test interface {  
 8     Tester()
 9 }
10 
11 type MyFloat float64
12 
13 func (m MyFloat) Tester() {  
14     fmt.Println(m)
15 }
16 
17 func describe(t Test) {  
18     fmt.Printf("Interface type %T value %v
", t, t)
19 }
20 
21 func main() {  
22     var t Test
23     f := MyFloat(89.7)
24     t = f
25     describe(t)  //Interface type main.MyFloat value 89.7
26     t.Tester()  //89.7
27 }
example

(4). 接口实现
     a. Golang中的接口,不需要显示的实现。只要一个变量,含有接口类型中的所有方法,那么这个变量就实现这个接口。因此,golang中没有implement
类似的关键字。
     b. 如果一个变量含有了多个interface类型的方法,那么这个变量就实现了多个接口。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type People struct {
 6     name string
 7     age  int
 8 }
 9 
10 type EatInter interface {
11     Eat()
12 }
13 
14 type SleepInter interface {
15     Sleep()
16 }
17 
18 func (p People) Eat() {
19     fmt.Println("people eat")
20 }
21 
22 func (p People) Sleep() {
23     fmt.Println("people sleep")
24 }
25 
26 func main() {
27     var e EatInter
28     var s SleepInter
29 
30     var people People = People {
31         name: "people",
32         age:  100,
33     }
34 
35     //people实现了EatInter和SleepInter接口
36     e = people
37     s = people
38     e.Eat()
39     s.Sleep()
40 
41     fmt.Println("e:", e)  //e: {people 100}
42     fmt.Println("s:", s)  //s: {people 100}
43 }
example

     c. 如果一个变量只含有了1个interface的部分方法,那么这个变量没有实现这个接口。

(5). 多态
     一种事物的多种形态,都可以按照统一的接口进行操作。

 1 package main
 2 
 3 import "fmt"
 4 
 5 //一个接口Test,方法Eat()和Sleep()多种实现(People和Animal),这就是多态
 6 type Test interface {
 7     Eat()
 8     Sleep()
 9 }
10 
11 type People struct {
12     Name string
13 }
14 
15 type Animal struct {
16     Name  string
17 }
18 
19 func (p People) Eat() {
20     fmt.Printf("People %s eat
", p.Name)
21 }
22 
23 func (p People) Sleep() {
24     fmt.Printf("People %s sleep
", p.Name)
25 }
26 
27 func (p Animal) Eat() {
28     fmt.Printf("Animal %s eat
", p.Name)
29 }
30 
31 func (p Animal) Sleep() {
32     fmt.Printf("Animal %s sleep
", p.Name)
33 }
34 
35 func main() {
36 
37     var t Test
38 
39     var a Animal = Animal {
40         Name:  "Cat",
41     }
42 
43     t = a
44     t.Eat()
45     t.Sleep()
46     fmt.Println("t:", t)
47 
48     var p People = People {
49         Name: "people",
50     }
51 
52     t = p
53     t.Eat()
54     t.Sleep()
55     fmt.Println("t:", t)
56 }
example

    练习:调用Sort系统函数实现对自定义数组的排序

 1 package main
 2 
 3 import (
 4     "fmt"
 5     "math/rand"
 6     "sort"
 7 )
 8 
 9 type Student struct {
10     Name     string
11     Id       string
12     Age      int
13     sortType int
14 }
15 
16 type Book struct {
17     Name   string
18     Author string
19 }
20 
21 //官网的Sort没有实现对任意类型的排序,为了实现对StudentArray数组的排序,
22 //查询官网发现Sort的定义,参数的是一个接口,该接口中只要实现Len,Less,Swap三个方法就可以调用Sort函数
23 // func Sort(data Interface)
24 // type Interface interface {
25 //        Len() int
26 //        Less(i, j int) bool
27 //        Swap(i, j int)
28 // }
29 
30 type StudentArray []Student
31 
32 func (p StudentArray) Len() int {
33     return len(p)
34 }
35 
36 func (p StudentArray) Less(i, j int) bool {
37     return p[i].Name < p[j].Name  //对名字桉升序排列
38 }
39 
40 func (p StudentArray) Swap(i, j int) {
41     p[i], p[j] = p[j], p[i]
42 }
43 
44 func main() {
45     var stus StudentArray
46     for i := 0; i < 10; i++ {
47         stu := Student{
48             Name: fmt.Sprintf("stu%d", rand.Intn(100)),
49             Id:   fmt.Sprintf("110%d", rand.Int()),
50             Age:  rand.Intn(100),
51         }
52 
53         stus = append(stus, stu)
54     }
55 
56     for _, v := range stus {
57         fmt.Println(v)
58     }
59 
60     fmt.Println("

")
61 
62     sort.Sort(stus)
63     for _, v := range stus {
64         fmt.Println(v)
65     }
66 }
自定义类型排序

(6). 接口嵌套
     一个接口可以嵌套在另外的接口,如下所示:

     type ReadWrite interface {
             Read(b Buffer) bool
             Write(b Buffer) bool
     }
     type Lock interface {
             Lock()
             Unlock()
     }
     type Close interface {
             Close()
     }
     type File interface {
             ReadWrite
             Lock
            Close
     }

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Reader interface {
 6     Read()
 7 }
 8 
 9 type Writer interface {
10     Write()
11 }
12 
13 //接口嵌套
14 type ReadWriter interface {
15     Reader
16     Writer
17 }
18 
19 type File struct {
20 }
21 
22 func (f *File) Read() {
23     fmt.Println("read data")
24 }
25 
26 func (f *File) Write() {
27     fmt.Println("write data")
28 }
29 
30 func Test(rw ReadWriter) {
31     rw.Read()
32     rw.Write()
33 }
34 
35 func main() {
36     var f *File
37     var b interface{}
38     b = f
39     // Test(f)
40     v, ok := b.(ReadWriter) //f中实现了Reader和Writer接口,因此ok为true
41     fmt.Println(v, ok) //<nil> true
42 }
example

(7). 类型断言,由于接口是一般类型,不知道具体类型,如果要转成具体类型,可以采用以下方法进行转换:
     var t int
     var x interface{}
     x = t
     y = x.(int) //转成int

     var t int
     var x interface{}
     x = t
     y, ok = x.(int) //转成int,带检查。y为x的值

类型断言用于提取接口的底层值(Underlying Value)。
在语法 i.(T) 中,接口 i 的具体类型是 T,该语法用于获得接口的底层值。

 1 package main
 2 
 3 import (  
 4     "fmt"
 5 )
 6 
 7 func assert(i interface{}) {  
 8     // s := i.(int)
 9     if v, ok := i.(int); ok { //此时当传入assert(s)时程序不会panic
10         fmt.Println(v)
11     }
12 }
13 func main() {  
14     var s interface{} = 56
15     assert(s)
16 
17     s = "hello"
18     assert(s) //panic: interface conversion: interface {} is string, not int
19 }
example

注意:v, ok := i.(T)
          如果 i 的具体类型是 T,那么 v 赋值为 i 的底层值,而 ok 赋值为 true。
          如果 i 的具体类型不是 T,那么 ok 赋值为 false,v 赋值为 T 类型的零值,此时程序不会报错。

(8). 类型断言,采用type switch方式

     类型选择用于将接口的具体类型与很多 case 语句所指定的类型进行比较。它与一般的 switch 语句类似。唯一的区别在于类型选择指定的是类型,而一般的 switch 指定的是值。
     类型选择的语法类似于类型断言。类型断言的语法是 i.(T),而对于类型选择,类型 T 由关键字 type 代替。

     练习,写一个函数判断传入参数的类型

func classifier(items ...interface{}) {
    for i, x := range items { 
    switch x.(type) {
        case bool: fmt.Printf("param #%d is a bool
", i)
        case float64: fmt.Printf("param #%d is a float64
", i)
        case int, int64: fmt.Printf("param #%d is an int
", i)
        case nil: fmt.Printf("param #%d is nil
", i)
        case string: fmt.Printf("param #%d is a string
", i)
        default: fmt.Printf("param #%d’s type is unknown
", i)
    }
}
 1 package main
 2 
 3 import "fmt"
 4 
 5 type Student struct {
 6     Name string
 7     Sex  string
 8 }
 9 
10 func Test(a interface{}) {
11     b, ok := a.(Student)
12     if ok == false {
13         fmt.Println("convert failed")
14         return
15     }
16     //b += 3
17     fmt.Println(b)
18 }
19 
20 func just(items ...interface{}) {
21     for index, v := range items {
22         switch v.(type) {
23         case bool:
24             fmt.Printf("%d params is bool, value is %v
", index, v)
25         case int, int64, int32:
26             fmt.Printf("%d params is int, value is %v
", index, v)
27         case float32, float64:
28             fmt.Printf("%d params is float, value is %v
", index, v)
29         case string:
30             fmt.Printf("%d params is string, value is %v
", index, v)
31         case Student:
32             fmt.Printf("%d params student, value is %v
", index, v)
33         case *Student:
34             fmt.Printf("%d params *student, value is %v
", index, v)
35         }
36     }
37 }
38 
39 func main() {
40     var b Student = Student{
41         Name: "stu01",
42         Sex:  "female",
43     }
44     Test(b)
45     just(28, 8.2, "this is a test", b, &b)
46 }
example

      还可以将一个类型和接口相比较。如果一个类型实现了接口,那么该类型与其实现的接口就可以互相比较。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Describer interface {  
 6     Describe()
 7 }
 8 type Person struct {  
 9     name string
10     age  int
11 }
12 
13 func (p Person) Describe() {  
14     fmt.Printf("%s is %d years old", p.name, p.age)
15 }
16 
17 func findType(i interface{}) {  
18     switch v := i.(type) {
19     case Describer:
20         v.Describe()
21     default:
22         fmt.Printf("unknown type
")
23     }
24 }
25 
26 func main() {  
27     findType("zhangsan")  //unknown type
28     p := Person{
29         name: "zhangsan",
30         age:  25,
31     }
32     findType(p)  //zhangsan is 25 years old
33 }
example

 (9). 空接口,Interface{}
      空接口没有任何方法,所以所有类型都实现了空接口。
      var a int
      var b interface{}  //空接口
      b = a

 1 package main
 2 
 3 import (  
 4     "fmt"
 5 )
 6 
 7 func describe(i interface{}) {  
 8     fmt.Printf("Type = %T, value = %v
", i, i)
 9 }
10 
11 func main() {  
12     s := "Hello World"
13     describe(s) //Type = string, value = Hello World
14     i := 20
15     describe(i)  //Type = int, value = 20
16     strt := struct {
17         name string
18     }{
19         name: "zhangsan",
20     }
21     describe(strt) //Type = struct { name string }, value = {zhangsan}
22 }
example

(10). 判断一个变量是否实现了指定接口

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Describer interface {  
 6     Describe() string
 7 }
 8 
 9 type Person struct {  
10     Name string
11     Age  int
12 }
13 
14 func (p Person) Describe() string {  
15     str := fmt.Sprintf("%s is %d years old", p.Name, p.Age)
16     return str
17 }
18 
19 func findType(a interface{}) {
20     if v, ok := a.(Describer); ok {
21         fmt.Printf("v implements Describer(): %s
", v.Describe())
22     }
23 }
24 
25 func main() {
26     p := Person {
27         Name: "zhangsan",
28         Age:  25,
29     }
30 
31     findType(p) //v implements Describer(): zhangsan is 25 years old
32 }
example

(11). 指针类型和值类型的区别

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Describer interface {  
 6     Describe()
 7 }
 8 type Person struct {  
 9     name string
10     age  int
11 }
12 
13 func (p Person) Describe() { // 使用值接受者实现  
14     fmt.Printf("%s is %d years old
", p.name, p.age)
15 }
16 
17 type Address struct {
18     state   string
19     country string
20 }
21 
22 func (a *Address) Describe() { // 使用指针接受者实现
23     fmt.Printf("State %s Country %s", a.state, a.country)
24 }
25 
26 // func (a Address) Describe() { // 使用指针接受者实现
27 //     fmt.Printf("State %s Country %s", a.state, a.country)
28 // }
29 
30 func main() {  
31     var d1 Describer
32     p1 := Person{"Sam", 25}
33     d1 = p1
34     d1.Describe()
35     p2 := Person{"James", 32}
36     d1 = &p2
37     d1.Describe()
38 
39     var d2 Describer
40     a := Address{"Washington", "USA"}
41 
42     /* cannot use a (type Address) as type Describer
43        in assignment: Address does not implement
44        Describer (Describe method has pointer
45        receiver)
46     */
47    
48    //出错原因其原因是:对于使用指针接受者的方法,用一个指针或者一个可取得地址的值来调用
49    //都是合法的。但接口中存储的具体值(Concrete Value)并不能取到地址,因此在第 47 行,
50    //对于编译器无法自动获取 a 的地址,于是程序报错。
51     // d2 = a //error 但是如果将22-24替换为26-28,则d2 = a和d2 = &a都可以
52 
53     d2 = &a // OK
54     
55     d2.Describe()
56 
57 }
example

(12). 变量slice和接口slice之间赋值操作,for range 

var a []int
var b []interface{}
b = a

(13). 接口的零值

    接口的零值是 nil。对于值为 nil 的接口,其底层值(Underlying Value)和具体类型(Concrete Type)都为 nil。

    对于值为 nil 的接口,由于没有底层值和具体类型,当我们试图调用它的方法时,程序会产生 panic 异常。

 1 package main
 2 
 3 import "fmt"
 4 
 5 type Describer interface {  
 6     Describe()
 7 }
 8 
 9 func main() {  
10     var d1 Describer
11     if d1 == nil {
12         fmt.Printf("d1 is nil and has type %T value %v
", d1, d1)
13     }
14     
15     //d1.Describe() //panic: runtime error: invalid memory address or nil pointer dereference
16 }
example

练习:实现一个通用的链表类(待完善)

 1 package main
 2 
 3 import "fmt"
 4 
 5 type LinkNode struct {
 6     data interface{}
 7     next *LinkNode
 8 }
 9 
10 type Link struct {
11     head *LinkNode
12     tail *LinkNode
13 }
14 
15 func (p *Link) InsertHead(data interface{}) {
16     node := &LinkNode{
17         data: data,
18         next: nil,
19     }
20 
21     if p.tail == nil && p.head == nil {
22         p.tail = node
23         p.head = node
24         return
25     }
26 
27     node.next = p.head
28     p.head = node
29 }
30 
31 func (p *Link) InsertTail(data interface{}) {
32     node := &LinkNode{
33         data: data,
34         next: nil,
35     }
36 
37     if p.tail == nil && p.head == nil {
38         p.tail = node
39         p.head = node
40         return
41     }
42 
43     p.tail.next = node
44     p.tail = node
45 }
46 
47 func (p *Link) Trans() {
48     q := p.head
49     for q != nil {
50         fmt.Println(q.data)
51         q = q.next
52     }
53 }
link.go
 1 package main
 2 
 3 import "fmt"
 4 
 5 func main() {
 6 
 7     var link Link
 8     for i := 0; i < 10; i++ {
 9         //intLink.InsertHead(i)
10         link.InsertTail(fmt.Sprintf("str %d", i))
11     }
12 
13     link.Trans()
14 }
main.go

通过下面的例子体会接口的作用:

 1 package main
 2 
 3 import (  
 4     "fmt"
 5 )
 6 
 7 type SalaryCalculator interface {  
 8     CalculateSalary() int
 9 }
10 
11 type Permanent struct {  
12     empId    int
13     basicpay int
14     pf       int
15 }
16 
17 type Contract struct {  
18     empId  int
19     basicpay int
20 }
21 
22 //salary of permanent employee is sum of basic pay and pf
23 func (p Permanent) CalculateSalary() int {  
24     return p.basicpay + p.pf
25 }
26 
27 //salary of contract employee is the basic pay alone
28 func (c Contract) CalculateSalary() int {  
29     return c.basicpay
30 }
31 
32 /*
33 total expense is calculated by iterating though the SalaryCalculator slice and summing  
34 the salaries of the individual employees  
35 */
36 func totalExpense(s []SalaryCalculator) {  
37     expense := 0
38     for _, v := range s {
39         expense = expense + v.CalculateSalary()
40     }
41     fmt.Printf("Total Expense Per Month $%d", expense)
42 }
43 
44 func main() {  
45     pemp1 := Permanent{1, 5000, 20}
46     pemp2 := Permanent{2, 6000, 30}
47     cemp1 := Contract{3, 3000}
48     employees := []SalaryCalculator{pemp1, pemp2, cemp1}
49     totalExpense(employees)
50 
51 }
52 
53 //假如公司增加了一种新的员工类型 Freelancer,它有着不同的薪资结构。Freelancer只需传递到 totalExpense 的切片参数中,无需 totalExpense 方法本身进行修改。只要 Freelancer 也实现了 SalaryCalculator 接口,totalExpense 就能够实现其功能。
接口作用

用go实现一个图书管理系统:
    1. 实现一个图书管理系统,具有以下功能:
        a. 书籍录入功能,书籍信息包括书名、副本数、作者、出版日期
        b. 书籍查询功能,按照书名、作者、出版日期等条件检索
        c. 学生信息管理功能,管理每个学生的姓名、年级、身份证、性别、借了什么书等信息
        d. 借书功能,学生可以查询想要的书籍,进行借出
        e. 书籍管理功能,可以看到每种书被哪些人借出了

参考文献:

  • https://blog.csdn.net/zyc88888/article/details/80307008 (Go 方法与函数区别)
  • https://studygolang.com/articles/12266 (Go 系列教程 - 接口)
  • https://studygolang.com/articles/12264 (Go 系列教程)
原文地址:https://www.cnblogs.com/xuejiale/p/10381408.html