Go学习(六):函数

目录

函数定义

参数

返回值

匿名函数

闭包、递归

 javascript的闭包

 Go的闭包

 递归

延迟调用(defer)

defer特性:

defer用途:

异常处理

单元测试

压力测试


函数定义

    • 无需声明原型。
    • 支持不定 变参。
    • 支持多返回值。
    • 支持命名返回参数。 
    • 支持匿名函数和闭包。
    • 函数也是一种类型,一个函数可以赋值给变量。

    • 不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
    • 不支持 重载 (overload) 
    • 不支持 默认参数 (default parameter)。

 函数是第一类对象,可作为参数传递。建议将复杂签名定义为函数类型,以便于阅读??(我感觉好难看懂)

package main

import "fmt"

func test(fn func() int) int {
    return fn()
}
// 定义函数类型。
// 没有函数体的函数声明,这表示该函数不是以Go实现的。这样的声明定义了函数标识符
type FormatFunc func(s string, x, y int) string 

func format(fn FormatFunc, s string, x, y int) string {
    return fn(s, x, y)
}

func main() {
    s1 := test(func() int { return 100 }) // 直接将匿名函数当参数。

    s2 := format(func(s string, x, y int) string {
        return fmt.Sprintf(s, x, y)
    }, "%d, %d", 10, 20)

    println(s1, s2)
}
 100 10, 20

参数

在默认情况下,Go 语言使用的是值传递,即在调用过程中不会影响到实际参数。

注意1:无论是值传递,还是引用传递,传递给函数的都是变量的副本,不过,值传递是值的拷贝。引用传递是地址的拷贝,一般来说,地址拷贝更为高效。而值拷贝取决于拷贝的对象大小,对象越大,则性能越低。

注意2:map、slice、chan、指针、interface默认以引用的方式传递。

不定参数传值 就是函数的参数不是固定的,后面的类型是固定的。(可变参数)

Golang 可变参数本质上就是 slice。只能有一个,且必须是最后一个。

在参数赋值时可以不用用一个一个的赋值,可以直接传递一个数组或者切片,特别注意的是在参数后加上“…”即可。

  func myfunc(args ...int) {    //0个或多个参数
  }

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

  func add(a int, b int, args…int) int {    //2个或多个参数
  }
//其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.

  //函数的参数和每个参数的类型都不是固定的
  func myfunc(args ...interface{}) {
  }

使用 slice 对象做变参时,必须展开。(slice...) 

package main

import (
    "fmt"
)

func test(s string, n ...int) string {
    var x int
    for _, i := range n {
        x += i
    }

    return fmt.Sprintf(s, x)
}

func main() {
    s := []int{1, 2, 3}
    res := test("sum: %d", s...)    // slice... 展开slice
    println(res)
}

返回值

没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。仅应当用在像下面这样的短函数中

package main

import (
    "fmt"
)

func add(a, b int) (c int) {
    c = a + b
    return  //返回c,命名返回参数可被同名局部变量遮蔽,此时需要显式返回。
}

func calc(a, b int) (sum int, avg int) {
    sum = a + b
    avg = (a + b) / 2

    return
}

func main() {
    var a, b int = 1, 2
    c := add(a, b)
    sum, avg := calc(a, b)
    fmt.Println(a, b, c, sum, avg)
}

Golang返回值不能用容器对象接收多返回值。只能用多个变量,或 "_" 忽略。 

多返回值可直接作为其他函数调用实参

package main

func test() (int, int) {
    return 1, 2
}

func add(x, y int) int {
    return x + y
}

func sum(n ...int) int {
    var x int
    for _, i := range n {
        x += i
    }

    return x
}

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

 命名返回参数允许 defer 延迟调用通过闭包读取和修改。

package main

//没看过defer不理解
func add(x, y int) (z int) {
    defer func() {
        z += 100
    }()

    z = x + y
    return
}
//显式 return 返回前,会先修改命名返回参数。
func add2(x, y int) (z int) {
    defer func() {
        println(z) // 输出: 203
    }()

    z = x + y
    return z + 200 // 执行顺序: (z = z + 200) -> (call defer) -> (return)
}

func main() {
    //输出:103
    println(add(1, 2)) 
    //输出:
    //203
    //203
    println(add2(1, 2)) 
}

匿名函数

Go 学习笔记(16)— 函数(02)[函数签名、有名函数、匿名函数、调用匿名函数、匿名函数赋值给变量、匿名函数做回调函数]_函数签名和匿名函数-CSDN博客

Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。

package main

func main() {
    // --- function variable ---
    fn := func() { println("Hello, World!") }
    fn()

    // --- function collection ---
    fns := [](func(x int) int){
        func(x int) int { return x + 1 },
        func(x int) int { return x + 2 },
    }
    println(fns[0](100))

    // --- function as field ---
    //什么写法,匿名结构体?匿名函数作为方法
    d := struct {
        fn func() string
    }{
        fn: func() string { return "Hello, World!" },
    }
    println(d.fn())

    // --- channel of function ---
    //还没学
    fc := make(chan func() string, 2)
    fc <- func() string { return "Hello, World!" }
    println((<-fc)())
}

闭包、递归

闭包=函数+引用环境

闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。

闭包可以用来完成信息隐藏,并进而应用于需要状态表达的某些编程范型中

 javascript的闭包

<!DOCTYPE html>
<html lang="zh">
<head>
    <title></title>
</head>
<body> 
</body>
</html>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.2.6/jquery.min.js" type="text/javascript"></script>
<script>
function a(){
    var i=0;
    function b(){
        console.log(++i);
        document.write("<h1>"+i+"</h1>");
    }
    return b;
}

$(function(){
    //函数a()的内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包
    var c=a();
    c();
    c();
    c();
    //不会收回a()所占用的资源,因为a()的内部函数b()的执行需要依赖a()中的变量i

    //如果a()返回的不是函数b(),情况就完全不同了。因为a()执行完后,b()没有被返回给a()的外界,只是被a()所引用,而此时a()也只会被b()引 用,因此函数a()和b()互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。所以直接调用a();是页面并没有信息输出。
    //a(); //不会有信息输出
    document.write("<h1>=============</h1>");
    //c()跟c2()引用的是不同的环境,在调用i++时修改的不是同一个i
    var c2=a();
    c2();
    c2();
});

</script>

 Go的闭包

package main

import (
    "fmt"
)

func a() func() int {
    i := 0
    b := func() int {
        i++
        fmt.Println(i)
        return i
    }
    return b
}

func main() {
    c := a()
    c()
    c()
    c()

    a() //不会输出i
}

//输出:
//1
//2
//3

闭包复制的是原对象指针,会有延迟引用现象。 

【精选】Golang 循环体中的闭包和go func变量取值问题[延迟绑定]_goroutine闭包赋值-CSDN博客

返回2个闭包 

package main

import "fmt"

// 返回2个函数类型的返回值
func test01(base int) (func(int) int, func(int) int) {
	// 定义2个函数,并返回
	// 相加
	add := func(i int) int {
		base += i
		return base
	}
	// 相减
	sub := func(i int) int {
		base -= i
		return base
	}
	// 返回
	return add, sub
}

func main() {
	//f1是加的函数,f2是减的函数
	f1, f2 := test01(10)
	// base一直是没有消

	fmt.Println(f1(1), f2(2))
	// 此时base是9

	fmt.Println(f1(3), f2(4))
}

//输出:
//11 9
//12 8

 递归

package main

import (
	"fmt"
)

func factorial(i int) int {
	if i <= 1 {
		return 1
	}
	return i * factorial(i-1)
}

func fibonaci(i int) int {
	if i == 0 {
		return 0
	} else if i == 1 {  //else if要写在同一行
		return 1
	}
	return fibonaci(i-1) + fibonaci(i-2)
}
func main() {
	//递归
	//1、数字阶乘
	var i int = 5
	fmt.Printf("Factorial of %d is %d\n", i, factorial(i))

	//2、斐波那契数列
	for j := 0; j < 10; j++ {
		fmt.Printf("%d\n", fibonaci(j))
	}

}

延迟调用(defer)

defer特性:
    1. 关键字 defer 用于注册延迟调用。
    2. 这些调用直到 return 前才被执。因此,可以用来做资源清理。
    3. 多个defer语句,按先进后出的方式执行。  栈?
    4. defer语句中的变量,在defer声明时就决定了。
defer用途:
    1. 关闭文件句柄
    2. 锁资源释放
    3. 数据库连接释放

defer 碰上闭包

package main

import "fmt"

func main() {
    var whatever [5]struct{}
    for i := range whatever {
        defer func() { fmt.Println(i) }()
    }
}

//输出
//4
//4
//4
//4
//4

 defer后面的语句在执行的时候,函数调用的参数会被保存起来,但是不执行。也就是复制了一份。但是并没有说struct这里的this指针如何处理,通过这个例子可以看出go语言并没有把这个明确写出来的this指针当作参数来看待。

package main

import "fmt"

type Test struct {
    name string
}

func (t *Test) Close() {
    fmt.Println(t.name, " closed")
}
func main() {
    ts := []Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        defer t.Close() //从断点看这句直到函数结束才执行,当时的t是c
    }
}
    c  closed
    c  closed
    c  closed
package main

import "fmt"

type Test struct {
    name string
}

func (t *Test) Close() {
    fmt.Println(t.name, " closed")
}
func Close(t Test) {
    t.Close()
}
func main() {
    ts := []Test{{"a"}, {"b"}, {"c"}}
    for _, t := range ts {
        defer Close(t)  //每次循环都会执行
    }
    //效果相同
    //for _, t := range ts {
    //    t2 := t
    //    defer t2.Close()
    //}

}
    c  closed
    b  closed
    a  closed

多个 defer 注册,按 FILO 次序执行 ( 先进后出 )。哪怕函数或某个延迟调用发生错误,这些调用依旧会被执行。

package main

func test(x int) {
    defer println("a")
    defer println("b")

    defer func() {
        println(100 / x) // div0 异常未被捕获,逐步往外传递,最终终止进程。
    }()

    defer println("c")
}

func main() {
    test(0)
}
    c
    b
    a
    panic: runtime error: integer divide by zero

延迟调用参数在注册时求值或复制,可用指针或闭包 "延迟" 读取。

package main

func test() {
    x, y := 10, 20

    defer func(i int) {
        println("defer:", i, y) // y 闭包引用
    }(x) // x 被复制,这时候x还是10

    x += 10
    y += 100
    println("x =", x, "y =", y)
}

func main() {
    test()
}
//x = 20 y = 120
//defer: 10 120

 如果 defer 后面跟的不是一个 closure 最后执行的时候我们得到的并不是最新的值

package main

import (
    "errors"
    "fmt"
)

func foo(a, b int) (i int, err error) {
    defer fmt.Printf("first defer err %v\n", err)
    defer func(err error) { fmt.Printf("second defer err %v\n", err) }(err)
    defer func() { fmt.Printf("third defer err %v\n", err) }()
    if b == 0 {
        err = errors.New("divided by zero!")
        return
    }

    i = a / b
    return
}

func main() {
    foo(2, 0)
}
//third defer err divided by zero!
//second defer err <nil>
//first defer err <nil>
package main

import "fmt"

func foo() (i int) {

    i = 0
    defer func() {
        fmt.Println(i)
    }()

    return 2  //是闭包会更新i,且return将i的值修改为2了
}

func main() {
    foo()
}
//2
package main

import (
    "fmt"
)
//run() 的声明是没有问题,因为在test函数运行完成后它才会被调用。
func test() {
    var run func() = nil
    defer run()  // defer 函数会被执行且会因为值为 nil 而产生 panic 异常
    fmt.Println("runs")
}

func main() {
    defer func() {
        if err := recover(); err != nil {
            fmt.Println(err)
        }
    }()
    test()
}
//runs
//runtime error: invalid memory address or nil pointer dereference

 不忽视f.Close()的正确写法(别犯不检查错误)

package main

import "os"

func do() (err error) {
    f, err := os.Open("book.txt")
    if err != nil {
        return err
    }

    if f != nil {
        defer func() {
            if ferr := f.Close(); ferr != nil {
                err = ferr
            }
        }()
    }

    // ..code...

    return nil
}

func main() {
    do()
}

释放相同的资源

如果你尝试使用相同的变量释放不同的资源,那么这个操作可能无法正常执行。

 遇到问题再说

异常处理

异常的使用场景简单描述:Go中可以抛出一个panic的异常,然后在defer中通过recover捕获这个异常,然后正常处理。

暂时先不学

单元测试

压力测试

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值