• go基础第三篇:结构体、函数、方法、接口


    struct

    结构体struct就相当于java中的类class,用于定义属性和方法。

    定义一个Person,有string型的Name和int型的Age两个属性:

    type Person struct {
        Name string
        Age  int
    }

    创建一个Person实例:

    第一种方式:

    var p Person = Person{}

    这时,所有属性值都是零值。

    直接打点给属性赋值:

    p.Name = "zhangsan"

    p.Age = 18

    var p Person = Person{"zhangsan", 18}

    一个个把属性值列出来,要求一个都不能少。

    第二种方式:

    var p Person = Person{Name: "zhansan", Age: 18}

    这时,Name属性值为zhangsan,Age属性值为18

    把属性名与值一对对列出来,可以只列需要的属性,其他的属性值为零值。注意,属性名没有用双引号括住。

    第三种方式:

    var pptr *Person  = new(Person)

    注意,与上面两种方式不同,用new关键字生成的是指向Person实例的指针。var p Person = new(Person)会报编译错误,提示Cannot use 'new(Person)' (type *Person) as type Person。

    我们可以通过struct指针操作属性,效果和通过struct实例一样。

    如pptr.Name = "lisi"

    fmt.Println(pptr.Name)

    定义一个Employee,有Person型的p和int型的salary两个属性:

    type Employee struct {
        p      Person
        salary int
    }

    创建一个Employee实例:

    var e Employee = Employee{}

    var e Employee = Employee{p: Person{}, salary: 10000}

    var eptr *Employee = new(Employee)

    如果属性类型是自定义的struct的话,属性名可以省略,如下:

    type Employee struct {
        Person
        salary int
    }

    此时创建一个Employee实例:

    var e Employee = Employee{}

    var e Employee = Employee{Person: Person{}, salary: 10000}

    这里由于属性名省略了,所以花括号中的key只能是我们自定义的struct了。

    var eptr *Employee = new(Employee)

    通过e访问Name、Age属性:

    在属性名不省略时,只能通过e打点获取Person实例,然后Person实例再打点操作Name、Age属性,示例如下:

    func main() {
        var e Employee = Employee{p: Person{}, salary: 10000}
        e.p.Name = "zhangsan"
        fmt.Println(e)
    }

    在属性名省略时,我们既可以通过e打点获取Person实例,然后Person实例再打点操作Name、Age属性,也可以直接e打点操作Name、Age属性,这就有点像java的继承了。示例如下:

    func main() {
        var e Employee = Employee{Person: Person{}, salary: 10000}
        e.Person.Name = "zhangsan"
        e.Age = 30
        fmt.Println(e)
    }

    函数

    在go中,函数是一等公民。

    函数可以有不定长入参,可以有多个返回值,可以赋值给变量,可以作为函数的入参和出参。

    函数定义:func func_name(i int, s string) int {}

    定义函数f0,有一个int型入参、无出参:

    func f0(i int) {
    
    }

    定义函数f1,有一个int型入参、一个string型入参,一个int型出参:

    func f1(i int, s string) int {
        return 0
    }

    定义函数f2,有两个int型入参,一个string型出参:

    func f2(i, j int) string {
        return ""
    }

    如果多个入参类型一样的话,可以省略前面几个参数的类型关键字,而只保留最后一个参数的类型关键字。

    定义函数f3,有两个int型入参,一个string型出参,一个int型出参:

    func f3(i, j int) (string, int) {
        return "", 0
    }

    多个出参的话,要用括号包起来。

    定义函数f4,有不定长个int型入参,一个int型出参:

    func f4(p ...int) int {
        return len(p)
    }

    定义函数f5,有一个string型入参和不定长个int型入参,一个int型出参:

    func f5(s string, sl ...int) int {
        return len(s) + len(sl)
    }

    函数赋值给变量

    func main() {
        var f func(int, string) int = f1
        f(1, "a")
    }

    变量的类型可以通过fmt.Println(reflect.TypeOf(f))打印出来看。

    函数作为函数的入参和出参

    func S(f func(i int) int) func(s string) string {
        return func(s string) string {
            return strconv.Itoa(f(len(s)))
        }
    }
    
    func main() {
        var p = S(func(i int) int {
            return i + 10
        })("100")
        fmt.Println(p)
    }

    我们在定义函数时,如果入参是一个struct实例,则底层会复制一个struct实例作为入参,函数对实例的改动不会影响原来的struct实例。所以,入参最好是指向struct实例的指针,这样就不用复制了,函数对实例的改动也会体现到原来的struct实例上。示例如下:

    func changeName(e Person) {
        e.Name = "zhangsan"
    }
    
    func changeNamePtr(e *Person) {
        e.Name = "zhangsan"
    }
    
    func main() {
        var p Person = Person{}
        changeName(p)
        fmt.Println(p)
    
        var ptr *Person = new(Person)
        changeNamePtr(ptr)
        fmt.Println(ptr)
    }

    方法

    方法和函数长得差不多,区别是方法定义时func后面跟的不是func_name,而是括号,括号里面是struct类型变量,只有这个struct类型实例或者指针才能调用这个方法,之后才是func_name。示例如下:

    func (p *Person) exchange(p0 *Person) {
        p.Name = p0.Name
        p0.Age = p.Age
    }
    
    func main() {
        var p Person = Person{Name: "zhansgan", Age: 18}
        var p0 Person = Person{Name: "lisi", Age: 10}
        p.exchange(&p0)
        fmt.Printf("%+v
    ", p)
        fmt.Printf("%+v
    ", p0)
    }

    func (p *Person) exchange(p0 *Person) {}

    第一个括号中的p *Person表示本方法调用者只能是Person实例或者指向Person实例的指针,且是引用传递,如果在方法中改变了调用者的属性,会在方法外体现。

    第二个括号中的p0 *Person表示方法入参是引用传递,如果在方法中改变了入参的属性,会在方法外体现。

    方法的继承

    还是以上面的Person、Employee举例。

    假设Person有个changeName方法,定义如下:

    func (pptr *Person) changeName(name string) {
        pptr.Name = name
    }

    假如在定义Employee时省略了Person类型属性的名称,则我们可以通过e直接打点调用changeName方法,示例1如下:

    func main() {
        var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
        e.changeName("lisi")
        fmt.Printf("%+v
    ", e)
    }

    假设Employee也有个changeName方法,则直接通过e打点调用changeName方法的话,调用的其实是Employee的changeName方法,而不是Person的changeName方法。示例2如下:

    func (pptr *Person) changeName(name string) {
        pptr.Name = name
    }
    
    func (eptr *Employee) changeName(name string) {
        eptr.Name = name + name
    }
    
    func main() {
        var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
        fmt.Printf("%+v
    ", e)
    }

    假如Employee有个change方法,在change方法中调用了changeName方法,那么e调用change方法时,执行的是Employee的changeName方法呢,还是Person的changeName方法呢?示例3如下:

    func (pptr *Person) changeName(name string) {
        pptr.Name = name
    }
    
    func (pptr *Person) change(name string) {
        pptr.changeName(name)
    }
    
    func (eptr *Employee) changeName(name string) {
        eptr.Name = name + name
    }
    
    func main() {
        var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
        e.change("lisi")
        fmt.Printf("%+v
    ", e)
    }

    实测执行的是Person的changeName方法。

    假如Employee也有个change方法,在change方法中调用了changeName方法,那么e调用change方法时,执行的是Employee的changeName方法呢,还是Person的changeName方法呢?示例4如下:

    func (pptr *Person) changeName(name string) {
        pptr.Name = name
    }
    
    func (pptr *Person) change(name string) {
        pptr.changeName(name)
    }
    
    func (eptr *Employee) changeName(name string) {
        eptr.Name = name + name
    }
    
    func (eptr *Employee) change(name string) {
        eptr.changeName(name)
    }
    
    func main() {
        var e Employee = Employee{Person: Person{Name: "wanglaoji"}}
        e.change("lisi")
        fmt.Printf("%+v
    ", e)
    }

    实测执行的是Employee的change方法和Employee的changeName方法。

    分析:

    在changeName和

    总结:

    在go中没有继承,不论是属性还是方法。都是太任性的省略搞的鬼。

    在go中没有静态方法的概念。

    接口

    定义格式:

    type RedPacketService interface {
        add(s string)
        delete(s string)
        update(s string)
        query(s string) string
    }

    go中没有implements或者相同作用的关键字。要实现某个接口,不是在定义struct的时候显式声明要实现某个接口,而是采用duck typing的方式,即只要实现了接口的所有方法,就认为这个struct实现了这个接口,就可以向上转型。示例如下:

    type Programmer interface {
        helloWorld()
    }
    
    type JavaProgrammer struct {
    }
    
    func (jp *JavaProgrammer) helloWorld() {
        fmt.Println("System.out.println("Hello World!");")
    }
    
    type GoProgrammer struct {
    }
    
    func (gp *GoProgrammer) helloWorld() {
        fmt.Println("fmt.Println("Hello World!")")
    }
    
    func main() {
        var p Programmer = new(JavaProgrammer)
        p.helloWorld()
    
        p = new(GoProgrammer)
        p.helloWorld()
    }

    Programmer接口只有一个helloWorld方法,JavaProgrammer实现了这个方法,且是pointer receiver实现,所以JavaProgrammer实现了Programmer接口,同理,GoProgrammer也实现了Programmer接口。

    假如再给Programmer接口添加一个hi方法,由于JavaProgrammer和GoProgrammer都没有实现这个方法,所以这两个struct都没有实现Programmer接口,所以把指向JavaProgrammer实例或GoProgrammer实例的指针赋值给Programmer类型变量时会报编译错误。

    interface{}

    interface{}可以用作任意类型的形参,示例如下:

    func PX(s interface{}) {
        if s0, ok := s.(int); ok {
            fmt.Println("int=", s0)
        } else if s0, ok := s.(string); ok {
            fmt.Println("string=", s0)
        } else if s0, ok := s.(bool); ok {
            fmt.Println("bool=", s0)
        } else {
            fmt.Println("unknown type")
        }
    }
    
    func main() {
        PX("1")
        PX(1)
        PX(false)
        PX(new(Programmer))
    }

    以上,s可以是任意类型变量。s.(int)有2个返回值,第一个返回值是s,第二个返回值是true或者false,如果s是int型变量,就是true,否则就是false。所以通过判断第二个返回值是否是true,就能判定s是否是int型变量。

    以上写法还可以通过switch来简化判断,如下:

    func PX(s interface{}) {
        switch s.(type) {
        case int:
            fmt.Println("int=", s)
        case string:
            fmt.Println("string=", s)
        case bool:
            fmt.Println("bool=", s)
        default:
            fmt.Println("unknown type")
        }
    }
    
    func main() {
        PX("1")
        PX(1)
        PX(false)
        PX(new(Programmer))
    }

    s.(type) 跟在switch后面没毛病,单独写就会报编译错误,估计是个特殊语法,编译器做了特殊支持。

  • 相关阅读:
    bzoj1626[Usaco2007 Dec]Building Roads 修建道路*
    bzoj1610[Usaco2008 Feb]Line连线游戏*
    bzoj1666[Usaco2006 Oct]Another Cow Number Game 奶牛的数字游戏*
    bzoj1679[Usaco2005 Jan]Moo Volume 牛的呼声*
    bzoj1606[Usaco2008 Dec]Hay For Sale 购买干草*
    bzoj1264[AHOI2006]基因匹配Match
    bzoj4518[Sdoi2016]征途
    bzoj2049[Sdoi2008]Cave 洞穴勘测
    bzoj4514[Sdoi2016]数字配对
    bzoj2429[HAOI2006]聪明的猴子
  • 原文地址:https://www.cnblogs.com/koushr/p/13364581.html
Copyright © 2020-2023  润新知