05-Go方法、接口、泛型

发布时间 2023-04-04 18:54:59作者: Edmond辉仔

1 方法

//方法 
  1.是特殊的函数,可以自动传值 ---> 对象(go中就是结构体)来调用,自动把对象(当前结构体的实例)传过来

  2.在func关键字 和 方法名 中间加入了一个特殊的接收器类型
    接收器可以是结构体类型或者是非结构体类型
    接收器是可以在方法的内部访问的
  
  3.方法是绑定给结构体的 ---> 如何绑定


  4.方法如何给非结构体类型绑定  ---> 将 非结构体类型 重命名,再绑定
    
  eg:
    type Myint int8

    func (p *Myint) add(){
        (*p) +=1
    }


//总结:go中没有面向对象的概念,但是能够实现面向对象
  1.结构体            --->  类的一半,只有属性
  2.结构体 + 匿名字段  --->  类的继承
  3.结构体 + 方法      --->  类的另一半,类中的方法   实例化结构体后,可以自动传值(当前对象)



package main

import "fmt"


//定义了一个结构体
type Person1 struct {
    name string
    age  int
}


//给结构体绑定方法   
func (p Person1) PrintName() { 
    fmt.Println(p.name)
}


// (p Person1)   值类型接收器,接受的是当前结构体的实例(当前对象) 的copy一份
func (p Person1) ChangeName(name string) {
    p.name = name   //修改该结构体的实例 属性,不会影响原来结构体实例
    fmt.Println(p)
}

// (p *Person1)  指针类型接收器,接受的是当前结构体的实例(当前对象) 的指针 
                               就是python中的self,是原值的引用
func (p *Person1) ChangeAge(age int) {
    p.age = age    //修改该结构体的实例 属性,会影响原来结构体实例
    fmt.Println(p)
}


//定义的是打印name的函数
func PrintName(p Person1) {

}



func main() {
    //1 使用方法
    var p Person1=Person1{"lqz",19}
    p.PrintName()

    
    //2 为什么我们已经有函数了还需要方法呢
        区别在于:使用方式 和 自动传值
    
    PrintName(p)  //函数调用,需要手动传参
      

    //3 指针接收器与值接收器 ---> 取别在于修改会不会影响原来的对象
    var p Person1 = Person1{"lqz", 19}
    
    p.ChangeName("彭于晏")
    fmt.Println("修改后的:",p)  //{"lqz", 19}  没有改

    
    //5 接收器使用场景 ---> 如果想改原来的值,用指针类型接收器
    var p Person1 = Person1{"lqz", 19}
    
    p.ChangeAge(99)
    fmt.Println("修改后的:",p) //{"lqz", 99}  修改


    //5 匿名字段的方法 ---> 方法提升
    p:=Person2{"lqz",18,Hobby2{"篮球",1}}
    fmt.Println(p)
    
    //p.hobby.printName()
    p.printName()  //方法提升了

}


//5 匿名字段的方法 ---> 方法提升
type Hobby2 struct {
    hobbyName string
    hobbyId int
}

type Person2 struct {
    name string
    age int
    //hobby Hobby2
    Hobby2  //匿名字段
}

func (h Hobby2) printName()  {
    fmt.Println(h.hobbyName)
}

2 接口

//接口:   就是python中的多态
  1.一系列方法的集合,约束子类的行为

  2.go也是鸭子类型,是非侵入式接口


//侵入式接口和非侵入式接口
  非侵入式接口:删除接口后,基于该接口的结构体不会报错   eg: go

  侵入式接口:删除接口后,基于该接口的类会报错   eg:java



package main

import (
    "fmt"
)

//定义一个鸭子接口
type Duck interface {
    run()
    speak()
}

//定义一个空接口 ---> 所有类型都实现了空接口
type Empty interface {
}


//定义普通鸭结构体
type PDuck struct {
    age  int
    name string
}

//定义唐老鸭结构体
type TDuck struct {
    age  int
    name string
    wife string
}


//让普通鸭实现Duck接口 ---> 只要实现了接口中所有方法,就叫实现该接口
func (p PDuck) run() {
    fmt.Println("普通鸭子,走路歪歪扭扭")
}
func (p PDuck) speak() {
    fmt.Println("普通鸭子,嘎嘎叫")
}


//让唐老鸭实现Duck接口 ---> 只要实现了接口中所有方法,就叫实现该接口
func (p TDuck) run() {
    fmt.Println("人走路")
}
func (p TDuck) speak() {
    fmt.Println("人叫")
}




func main() {
    var p PDuck = PDuck{1, "肉鸭一号"}
    var t TDuck = TDuck{5, "唐老鸭", "母唐老鸭"}
    
    //唐老鸭和普通鸭,都可以赋值给鸭子接口类型,就可以都当做 鸭子接口 去操作
    var d Duck
    d = p
    d = t
    
    d.run()
    d.speak()

    
    //1 类型断言  d.(类型): 判断该接口是否 是这个类型,并返回该类型对应的值
                     作用:可以通过 接口类型,获取到原本类型的实例
                     返回值: 获取该对象、布尔值
    
    if v,ok := d.(TDuck); ok{
        fmt.Println(v.name)
    }else {
        fmt.Println("断言失败,不是唐老鸭类型")
    }

        
    //2 空接口类型  所有类型都可以赋值给空接口
    var a Empty=1
    
    a="lqz"
    a=[]int{1,2,3}
   
    //3 匿名空接口  
    interface{}
    
    
    //5 类型选择  就是 类型断言 + Switch + 空接口 的结合
    a := "lqz"
    a := []int{1, 2, 3}
    test(a)
    
    t := TDuck{wife: "李易峰"}
    test(t)
}

    
//5 类型选择  作用:可以通过 接口类型,获取到原本类型的实例

//func test(i interface{}){    //函数参数 是 匿名空接口
func test(i Empty) {
    switch v:=i.(type) {   // i.(type)可以返回i对应类型的值,但.(type)只能在switch内使用
    case int:
        fmt.Println("是int")
    case string:
        fmt.Println("是字符串")
    case TDuck:
        fmt.Println(v.wife)
        fmt.Println("是唐老鸭类型")
    default:
        fmt.Println("不知道什么类型")
    }
}


3 泛型

//泛型编程
  通过引入 类型形参 和 类型实参 这两个概念
  让一个函数获得了处理多种不同类型数据的能力


//理解案例:
  定义一个int类型的add函数,但是不能处理int以外的类型
  
  泛型就是将add函数的 参数类型不固定,等到实际传入参数时,再给定具体类型
  从而实现同一个函数,处理 除类型以外的 相同逻辑的功能 

  python 没有泛型一说,但是可以通过 注释等提示,实现人为约束的 泛型
  因为python本身就是动态类型,变量没有固定类型,可以指向所有类型
  是一切皆对象,都是对象的引用
  传入什么对象,就是什么对象的类型使用


//使用场景
  如果你经常要分别为不同的类型写完全相同逻辑的代码,那么使用泛型将是最合适的选择


//具体实现   参考 https://segmentfault.com/a/1190000041634906