zoukankan      html  css  js  c++  java
  • 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 系列教程)
  • 相关阅读:
    项目不能imports同名命名空间
    表格分页
    关于Web Post信息的编码
    JJHIS构想
    asp.net客户端传参的小结
    注销类业务的处理
    枚举类型的一些用法总结
    MO功能使能情况
    在内嵌页面得到某个内嵌页面所在的frame
    不同包里的全局变量定义方式及系统配置处理
  • 原文地址:https://www.cnblogs.com/xuejiale/p/10381408.html
Copyright © 2011-2022 走看看