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

函数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 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)