蹒跚学Go第二天-函数

函数

函数就是将一堆代码进行重用的一种机制。函数就是一段代码,一个函数就像一个专门做这件事的人,我们调用它来做一些事情,它可能需要我们提供一些数据给它,它执行完成后可能会有一些执行结果给我们。要求的数据就叫参数,返回的执行结果就是返回值。

函数入栈出栈的过程

基本语法

func 函数名(){
  函数体
}
  • 例子
func main()  {
    var num,num1 int
    for i := 0; i < 2 ; i++ {
        fmt.Printf("输入第%d数字:\n",i)
        fmt.Scanf("%d",&num)
        if i == 1{
            num1=num
        }
    }
    add(num,num1)
}

不定参数列表

在定义函数的时候根据需求指定参数的个数和类型,但是有时候如果无法确定参数的个数呢?

  • args后面跟了三个点,就是表示该参数可以接收0或多个整数值

  • args这个参数可以想象成是一个集合(类似数学中集合),可以存放多个值。

  • args有下角标如果用Python可以使用list来理解

例子一:

func add(args ...int)  {
    for i := 0; i <= len(args); i++ {
        fmt.Printf("当前多少次:%d\n",i)
    }
}

func main()  {
    add(1,2,3)
    add(1,2,34,4,5,12,12,123,3,4532,234,234,234,234234,234,234,234)

例子二:

  • range值管饱管够,args结束整个add函数结束

  • range会从集合中返回两个数,第一个是对应的坐标,赋值给了变量i,第二个就是对应的值,赋值了变量data

func add(args ...int)  {
    for i , data := range args {
        fmt.Println("编号:",i)
        fmt.Println("值:",data)
    }
}

func main()  {
    add(1,2,3)
}

注意项

  • 一定(只能)放在形参中的最后一个参数

  • 在对函数进行调用时,固定参数必须传值,不定参数可以根据需要来决定是否要传值

  • func add(args …int) int{} //0个或者多个参数

  • func add(a int,args …int) int{} //一个或者多个参数

  • args -> 是一个slice,可以通过args[index]依次访问所有的参数通len(args)来判断传参的个数

实例二

  • ( _ ),该下划线表示匿名变量,丢弃数据不进行处理,也就是任何赋予它的值都会被丢弃

函数嵌套调用

func add(args ...int)  {
    for i , data := range args {
        fmt.Println("编号:",i)
        fmt.Println("值:",data)
    }
}

func Value(args ...int)  {
    add(args[2:] ...) //从编号2位置开始截取,编号0-2的不截取
}

func main()  {
    Value(1,2,3,4,5,6)
}

函数返回值

func add(args ...int) (sum int) { //定义返回值的名称和类型
    for data := range args {
        sum += data
    }
    return  //如果不定义则需要reture xxx 给返回值
}

func main()  {
    sum:=add(1,2,3,4,5,6)
    fmt.Println("值:",sum)
}

输出 -> 值: 15

返回多个值

func add(args ...int) (sum,sum1 int) { //定义返回值的名称和类型
    for _,data := range args {
        sum += data
        sum1 = data
    }
    return  //如果不定义则需要reture xxx 给返回值
}

func main()  {
    sum,sum1:=add(1,2)
    fmt.Println("值:",sum,sum1)
}

输出 -> 值: 3  2

其他形式的函数

  • 函数可以作为变量使用

依据Go 语言类型系统的概念,NewFuncType为新定义的函数命名类型,FuncLiteral为函数字面量类型,FuncLiteral为函数类型NewFuncType的底层类型。当然也可以使用type在一个函数类型中再定义一个新的函数类型,这种用法在语法上是允许的,但很少这么使用。

  • 使用type Newrype oldType 语法定义一种新类型,这种类型都是命名类型,同理可以使用该方法定义一种新类型:函数命名类型,简称函数类型
func test()  {
    fmt.Println("不接受参数!")
}
func test1(a int,b int) int {
    return a+b
}
type FUNCTYPE func()  //函数的类型其实就是一个指针
type FUNKTYPE1 func(int,int) int   // 定义这种类型的函数为FUNKTYPE,返回值类型为int
func main()  {
    var a FUNCTYPE  //定义函数类型变量
    a = test  //赋值给a -> test 函数,如果不复制则提示内地址错误找不到指针
    a() //调用函数打印变量
    var b FUNKTYPE1
    b = test1 //将一个函数赋值给一个变量
    va := b(12,123)
    fmt.Println(va)
}

函数作用域

局部变量

  • 我们在main( )函数中定义变量a,在Test( )函数中也定义了变量a,但是两者之间互不影响,就是因为它们属于不同的函数,作用范围不一样,在内存中是两个存储区域。

全局变量

  • 在函数外部的变量称为全局变量

  • 既能在一个函数中使用,也能在其他的函数中使用,这样的变量就是全局变量. 也就是定义在函数外部的变量就是全局变量。全局变量在任何的地方都可以使用

总结

  • (1)在函数外边定义的变量叫做全局变量。

  • (2)全局变量能够在所有的函数中进行访问

  • (3) 如果全局变量的名字和局部变量的名字相同,那么使用的是局部变量的

匿名函数和闭包

匿名函数

在一个函数中再定义一个函数,那么可以使用匿名函数,所谓匿名函数就是没有名字的函数

  • 匿名函数不能独立存在,常作为函数参数、返回值,或者赋值给某个变量

  • 匿名函数可以直接显式初始化

  • 匿名函数的类型也是函数字面量类型func(int,int)int

func main()  {
    var num int = 9
    f := func() {
        num++
        fmt.Println("匿名函数:",num)
    }
    type FUCK func()
    //声明函数类型变量
    var f1 FUCK
    f1 = f
    f1()
    fmt.Println("main函数:",num )
}

匿名函数的调用方式

func main() {
    var num int = 9
    func() {
        num++
        fmt.Println("匿名函数:", num)
    }()//括号直接调用匿名函数
}

匿名函数的参数和返回值

func main() {
    var num int = 9
    //定义函数(参数)返回值类型{}并且X,Y变量自动推到接受
    x,y := func(a int,c string) (q int,w string) {
        num++
        fmt.Printf("参数一:%d\n参数二:%s\n",num,c)
        q = num*2
        w = c + "让我来帮助你"
        return
    }(num,"你好我是小娜")//直接执行函数并且传递两个参数值
    fmt.Printf("返回值一:%d\n返回值二:%s",x,y)
}

闭包

闭包是由函数及其相关引用环境组合而成的实体,一般通过在匿名函数中引用外部函数的局部变量或包全局变量构成。
闭包=函数+引用环境
闭包对闭包外的环境引入是直接引用,编译器检测到闭包,会将闭包引用的外部变量分配到堆上。
如果函数返回的闭包引用了该函数的局部变量(参数或函数内部变量)
简而言之:所谓的闭包是指有权访问另一个函数作用域中的变量的函数,就是在一个函数内部创建另一个函数
也可以这样理解闭包:虽然不能在一个函数里直接声明另一个函数,但是可以在一个函数中声明一个函数类型的变量,此时的函数称为闭包(closure)

  • (1)多次调用该函数,返回的多个闭包所引用的外部变量是多个副本,原因是每次调用函数都会为局部变量分配内存。

  • (2)用一个闭包函数多次,如果该闭包修改了其引用的外部变量,则每一次调用该闭包对该外部变量都有影响,因为闭包函数共享外部引用。

  • (3)使用闭包是为了减少全局变量,所以闭包引用全局变量不是好的编程方式

//可以通过匿名函数实现函数在栈区的本地化
func Test() func() int {//定义的Test函数返回值为一个函数
    var x int
    return func() int {
        x++
        return x
    }
}

func main() {
    f := Test()
    fmt.Println(f())
    fmt.Println(f())
    fmt.Println(f())
    fmt.Printf("返回值类型:%T",f)
}

闭包的价值

闭包最初的目的是减少全局变量,在函数调用的过程中隐式地传递共享变量,有其有用的一面;但是这种隐秘的共享变量的方式带来的坏处是不够直接,不够清晰,除非是非常有价值的地方,一般不建议使用闭包。
对象是附有行为的数据,而闭包是附有数据的行为,类在定义时已经显式地集中定义了行为,但是闭包中的数据没有显式地集中声明的地方,这种数据和行为耦合的模型不是一种推荐的编程模型,闭包仅仅是锦上添花的东西,不是不可缺少的。

延迟调用defer

defer关键字,可以注册多个延迟调用,这些调用以先进后出(FILO)的顺序在函数返回前被执行defer常用于保证一些资源最终一定能够得到回收和释放。

  • defer的应用场景:文件操作,先打开文件,执行读写操作,最后关闭文件。为了保证文件的关闭能够正确执行,可以使用defer.
//创建操作文件的函数,保证有返回值和错误返回
func CopyFile(desName,srcNAME string) (written int64,err error){
    src,err := os.Open(srcName)
    if err != nil{
        return
    }
    //保证即使出错也运行关闭文件,即延迟调用
    defer src.Close()
    des,err := os.Create(dstName)
    if err != nil{
        return
    }
    defer des.Close()
    //正常则返回结果
    return io.Copy(dst,src)
}

注意事项

  • defer 后面必须是函数或方法的调用,不能是语句,否则会报expression in defer must be function cal1错误。

  • defer函数的实参在注册时通过值拷贝传递进去。实参的值在defer注册时通过值拷贝传递进去,后续语句实参++并不会影响 defer语句最后的输出结果。

func main()  {
    defer func() {
        print("1\n")
    }()

    defer func() {
        print("2\n")
    }()
    print("3\n")
}
输出值:3   2   1
  • defer 语句必须先注册后才能执行,如果defer位于return之后,则defer因为没有注册,不会执行。
func main()  {
    defer func() {
        println("1")
    }()
    a:=0
    println(a)
    return
    defer func() {
        println("2")//没有输出2
    }()
}
输出的值:0  1
  • 主动调用os.Exit(int)退出进程时,defer将不再被执行(即使defer已经提前注册)
func main()  {
    defer func() {
        println("default")
    }()
    println("begin")
    os.Exit(1)
}
输出值: begin
  • defer语句的位置不当,有可能导致panic,一般defer语句放在错误检查语句之后。

defer也有明显的副作用:defer会推迟资源的释放,defer尽量不要放到循环语句里面,将大函数内部的defer语句单独拆分成一个小函数是一种很好的实践方式。另外,defer相对于普通的函数调用需要间接的数据结构的支持,相对于普通函数调用有一定的性能损耗。

func f1() (r int){
    defer func() {
        r++
    }()
    r=0
    return
}

func main()  {
    i:= f1()
    fmt.Println(i)
}
输出值:1

练习

func double(x int) int  {
    return  x+x
}

func triple(x int) (r int) {
    defer func() {
        r += x
    }()
    return double(x)//先执行返回的这个函数x=6,后执行defer r+=x,最后返回r=9
}
func main()  {
    println(triple(3))
}
输出值: 9

递归函数

函数在内部不调用其它的函数,而是自己本身的话,这个函数就是递归函数能不使用尽量不要使用太消耗内存资源

func test(a int) {
    if a ==0 {
        return
    }
    a--
    test(a)
    fmt.Println(a)
}

func main()  {
    test(5)
}
输出值:0   1   2   3   4
  • 算阶乘 n! = 1 * 2 * 3 * … * n
var sum int =1
func test(a int) {
    if a == 1{
        return
    }
    test(a-1)
    sum*=a
}

func main()  {
    test(5)
    println(sum)
    }

发表评论

电子邮件地址不会被公开。 必填项已用*标注