目录
函数定义
• 无需声明原型。
• 支持不定 变参。
• 支持多返回值。
• 支持命名返回参数。
• 支持匿名函数和闭包。
• 函数也是一种类型,一个函数可以赋值给变量。
• 不支持 嵌套 (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捕获这个异常,然后正常处理。
暂时先不学