美文网首页
go基础编程day5函数function和结构struct

go基础编程day5函数function和结构struct

作者: james_chang | 来源:发表于2018-06-21 18:16 被阅读0次

    函数function

    • go函数不支持嵌套、重载和默认参数

    • 但支持以下特性:
      无需声明原型、不定长度变参、多返回值、命名返回值参数、匿名函数、闭包

    • 定义函数使用关键字func,且左大括号不能另起一行

    • 函数也可以作为一种类型使用

    package main
    
    import "fmt"
    
    func main() {
        A(1,2,3,4,5,6,7)
    }
    
    // 不定长变参必须放在最后一个参数,传入的为slice
    func A(a ...int) {
        fmt.Println(a)
    }
    

    这里有个必须清楚的事情,这里传入的不定长参数,虽然传入之后为slice,但是跟实际传入一个silce还是存在不同,传入的不定长参数为值的copy,在函数中并不能真正修改原来的值,但是传入slice是地址的copy,是可以真正修改传入的slice

    那么如何真正修改传入的值类型:传递指针, 就是相当于传递地址,相当于地址的copy

    package main
    
    import "fmt"
    
    // 重点理解!!
    func main() {
        a := 1
        fmt.Println(&a)
        // 要想真正修改该值,就直接传递指针
        A(&a)
        fmt.Println(a)
    }
    
    // 这里接收的参数是一个指针所以是指针类型,类型前加*
    // 这个时候的a是个地址,想修改值就需要通过加*取到值,再进行修改
    func A(a *int) {
        //
        *a = 2
        fmt.Println(*a)
    }
    
    package main
    
    import (
        "fmt"
    )
    
    func main() {
        a := A
        // A的复制品,A的类型
        a()
    }
    
    func A() {
        fmt.Println("func A")
    }
    

    转换一下变为匿名函数

    package main
    
    import (
        "fmt"
    )
    
    func main() {
        // 这里为这个函数命名,所以函数体去掉命名就可以了
        // 这里称为匿名函数
        a := func(){
            fmt.Println("func A")
        }
        a()
    }
    

    闭包函数

    package main
    
    import "fmt"
    
    // 闭包
    func main() {
        f:=closure(10)
        fmt.Println(f(1)) // 11
        fmt.Println(f(2)) // 12
    }
    
    // 返回值是一个函数,这个函数还接收函数,
    func closure(x int)(func(int) int) {
        return func(i int) int {
            // 这个内层函数中的x可以直接取到外层函数的x
            return x + i
        }
    }
    

    如何证明内层函数的x是外层函数的x

    package main
    
    import "fmt"
    
    // 闭包
    func main() {
        f:=closure(10)
        fmt.Println(f(1)) // 11
        fmt.Println(f(2)) // 12
    }
    
    // 返回值是一个函数,这个函数还接收函数,
    func closure(x int)(func(int) int) {
        fmt.Printf("%p\n", &x) // 0xc420014050
        return func(i int) int {
            fmt.Printf("%p\n", &x) // 0xc420014050
            // 这个内层函数中的x可以直接取到外层函数的x
            return x + i
        }
    }
    

    是同一个x

    defer

    • 执行方式类似其它语言中的析构函数,在函数体执行结束后,按照调用顺序的相反顺序逐个执行

    • 即使函数发生严重错误也会执行

    • 支持匿名函数的调用

    • 通常用于资源清理、文件关闭、解锁以及记录时间扽操作

    • 通过与匿名函数配合可在return之后修改函数计算结果

    • 如果函数体内某个变量作为defer时匿名函数的参数,则在定义defer时即已经获得了拷贝,否则则是引用某个变量的地址

    • go没有异常机制,但有panic/recover模式来处理错误

    • panic可以在任何地方引发,但recover只有在defer调用的函数中有效

    package main
    
    import "fmt"
    
    func main() {
        // 倒序输出
        for i := 0; i < 3; i++ {
            defer fmt.Println(i) // 输出2 1 0
        }
    }
    

    但是上面是值copy,一旦存在地址引用的时候就会出现不同的情况

    package main
    
    import "fmt"
    
    func main() {
        // 将输出i放在匿名函数中,不再直接打印
        for i := 0; i < 3; i++ {
            // 原因是闭包,之前是值的copy,如今是地址的引用,
            // 程序结束后i为3,所以三次输出都为3
            defer func() {
                fmt.Println(i) // 3 3 3
            }()
        }
    }
    

    存在地址引用的时候就要看defer的时候程序输出是最终地址中的值还是copy的值

    go中处理异常机制

    package main
    
    import "fmt"
    
    // 引发一个panic,进行recover
    func main() {
        // 依次执行三个函数
        A()
        B()
        C()
    }
    
    func A(){
        fmt.Println("func A")
    }
    
    func B()  {
        // 先从执行顺序,先panic触发错误,之后利用defer recover恢复
        // 所以错误不会输出
        // 如果panic放在前面,defer不会执行,
        // defer放在前面,提前注册,就提前知道defer存在,panic之后会执行defer
        defer func() {
            if err:= recover(); err!=nil{
                fmt.Println("recover in B")
            }
        }()
        panic("panic in B")
    }
    
    func C()  {
        fmt.Println("func C")
    }
    
    /*
    func A
    recover in B
    func C
    */
    

    分析:

    package main
    
    import (
        "fmt"
    )
    
    // 分析
    func main() {
        // array存4个func
        var fs = [4]func(){}
    
        for i := 0; i < 4; i++ {
            defer fmt.Println("defer i = ", i)
            defer func() { fmt.Println("defer_closure i = ", i) }()
            fs[i] = func() { fmt.Println("closure i = ", i) }
        }
        for _, f := range fs {
                f()
        }
    }
    
    /*
    首先循环4次,定义数组中四个函数,
    之后下一个循环,迭代array,函数调用,闭包调用地址
    closure i =  4
    closure i =  4
    closure i =  4
    closure i =  4
    defer_closure i =  4
    defer i =  3
    defer_closure i =  4
    defer i =  2
    defer_closure i =  4
    defer i =  1
    defer_closure i =  4
    defer i =  0
    */
    // 注意defer一定是函数执行完之后才开始的
    

    结构struct

    • go中的struct与C中的struct非常相似,并且go没有class
    • 使用type <Name> struct{}定义结构,名称遵循可见性规则
    • 支持指向自身的指针类型成员
    • 支持匿名结构,可用作成员或定义成员变量
    • 匿名结构也可以用于map的值
    • 可以使用字面值对结构进行初始化
    • 允许直接通过指针来读写结构成员
    • 相同类型的成员可进行直接拷贝赋值
    • 支持==与!=比较运算符,但不支持>或<
    • 支持匿名字段,本质上是定义了以某个类型名为名称的字段
    • 嵌入结构作为匿名字段看起来像继承,但不是继承
    • 可以使用匿名字段指针

    声明结构

    package main
    
    import "fmt"
    
    type person struct {
        Name string
        Age  int
    }
    
    func main() {
        // 两种方法赋值
        a := person{
            Name: "james",
        }
        a.Age = 19
        fmt.Println(a) // {james 19}
    }
    

    结构的传递属于值copy

    package main
    
    import "fmt"
    
    type person struct {
        Name string
        Age  int
    }
    
    func main() {
        // 两种方法赋值
        a := person{
            Name: "james",
            Age:19,
        }
        A(a)
        fmt.Println(a) // {james 19} 还是19,并没有修改
    }
    
    func A(per person) {
        per.Age = 13
        fmt.Println("A", per) // A {james 13}
    }
    

    传递指针来解决

    package main
    
    import "fmt"
    
    type person struct {
        Name string
        Age  int
    }
    
    func main() {
        a := person{
            Name: "james",
            Age:19,
        }
        A(&a)
        fmt.Println(a) // {james 13} 实现真正改变
    }
    
    func A(per *person) {
        // 虽然是个指针,但是对他操作的时候不许要加*了
        per.Age = 13
        fmt.Println("A", *per) // A {james 13}
    }
    

    更简便的方法在初始化赋值的时候直接取地址

            // a成为了指向这个结构的地址,不需要每次传值的时候取地址
        a := &person{
            Name: "james",
            Age:19,
        }
    

    匿名结构

    package main
    
    import "fmt"
    
    type person struct {
        Name string
        Age  int
        Contact struct {
            Phone, City string
        }
    }
    
    func main() {
        a := &person{
            Name:"james",
            Age:19,
        }
        // 内层匿名结构赋值
        a.Contact.Phone = "12324324324"
        a.Contact.City = "beijing"
        fmt.Println(a)
    }
    

    匿名字段

    package main
    
    import "fmt"
    
    type person struct {
        string
        int
    }
    
    func main() {
        a := &person{
            "james",
            19,
        }
        fmt.Println(a)
        var b *person
        b = a
        fmt.Println(b)
    }
    

    嵌入结构

    package main
    
    import "fmt"
    
    type person struct {
        Sex int
    }
    
    type teacher struct {
        person
        Name string
        Age  int
    }
    
    type student struct {
        person
        Name string
        Age  int
    }
    
    func main() {
        // 嵌入结构的初始化
        a := teacher{Name: "james", Age: 19, person: person{Sex: 1}}
        b := student{Name: "mrsliu", Age: 30, person: person{Sex: 0}}
        fmt.Println(a, b)
    }
    
    package main
    
    import "fmt"
    
    type person struct {
        Sex int
    }
    
    type teacher struct {
        person
        Name string
        Age  int
    }
    
    type student struct {
        person
        Name string
        Age  int
    }
    
    func main() {
        // 嵌入结构的初始化
        a := teacher{Name: "james", Age: 19, person: person{Sex: 1}}
        b := student{Name: "mrsliu", Age: 30, person: person{Sex: 0}}
        fmt.Println(a, b)
        a.Name = "jj"
        a.Age = 10
        // 两种修改内层结构的方式
        a.person.Sex = 100
        fmt.Println(a)
        a.Sex = 200
        fmt.Println(a)
    }
    

    当嵌套的结构与外层出现重名字段的时候会有一个查找顺序,
    如果在外层查找到这个字段名就不会再找内层的,如果外层没有才会找内层嵌套
    当然也可以使用 outName.inName.sameName指定输出嵌套的内层的同名字段

    type human struct {
        Name string
    }
    
    type man struct {
        human
        Name string
    }
    
    func main() {
        a := man{
            Name:  "james",
            human: human{Name: "jhon"},
        }
        fmt.Println(a)
        fmt.Println(a.human.Name, a.Name)
    // {{jhon} james}
    // jhon james
    
    }
    

    但是这个查找关系不适用于嵌套的内层有两个同名字段,这个时候就会抛出错误

    type A struct {
        B
        C
    }
    
    type B struct {
        Name string
    }
    
    type C struct {
        Name string
    }
    
    func main() {
        a := A{
            B: B{Name: "B"},
            C: C{Name: "C"},
        }
        fmt.Println(a)
        fmt.Println(a.Name, a.B.Name, a.C.Name)
        //  ambiguous selector a.Name
    }
    

    相关文章

      网友评论

          本文标题:go基础编程day5函数function和结构struct

          本文链接:https://www.haomeiwen.com/subject/phycyftx.html