0
点赞
收藏
分享

微信扫一扫

20220714GO语音-函数与方法

0. 课程内容 
1. 函数特点
2. 函数声明
3. 参数
3.1. 函数参数
3.2. 返回值
4. 匿名与闭包
4.1. 匿名函数
4.2. 闭包
5. 延迟调用(defer)
5.1. 介绍defer
5.2. defer与结构体
5.3. defer参数传递

1. 函数特点

无需声明原型。 
支持不定 变参。
支持多返回值。
支持命名返回参数。
支持匿名函数和闭包。
函数也是一种类型,一个函数可以赋值给变量。
不支持 嵌套 (nested) 一个包不能有两个名字一样的函数。
不支持 重载 (overload)
不支持 默认参数 (default parameter)

2. 函数声明

函数声明包含一个函数名,参数列表, 返回值列表和函数体。如果函数没有返回值,则返回列表可以省略。函数从第一条语句开始执行,直到执行return语句或者执行函数的最后一条语句。函数可以没有参数或接受多 个参数.

语法

func function_name( [parameter list] ) [return_types] { 
函数体
}

例子

func main() { 
a, b := test(1, 2, "sum")
fmt.Println("a,b : ", a, b)
dome()
}
func test(x, y int, s string) (int, string) {
// 类型相同的相邻参数,参数类型可合并。 多返回值必须用括号。
n := x + y
return n, s
}
func dome() {
fmt.Println("dome")
}

注意类型在变量名之后 。

当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他都可以省略。函数可以返回任意数量的返回值。使用关键字 func 定义函数,左大括号依旧不能另起一行。

package main 
import "fmt"
func test(fn func() int) int {
return fn()
}
// 定义函数类型。
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)
}

3. 参数

3.1. 函数参数

函数定义时指出,函数定义时有参数,该变量可称为函数的形参。形参就像定义在函数体内的局部变量。

但当调用函数,传递过来的变量就是函数的实参,函数可以通过两种方式来传递参数:

值传递:指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。

func swap(x, y int) int { 
// ... ...
}

引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。

package main 
import (
"fmt"
)
/* 定义相互交换值的函数 */
func swap(x, y *int) {
var temp int
temp = *x /* 保存 x 的值 */
*x = *y /* 将 y 值赋给 x */
*y = temp /* 将 temp 值赋给 y*/
}
func main() {
var a, b int = 1, 2
/*
调用 swap() 函数
&a 指向 a 指针,a 变量的地址
&b 指向 b 指针,b 变量的地址
*/
swap(&a, &b)
fmt.Println(a, b)
}

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

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

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

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

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

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

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

实例:

package main 
import "fmt"
func main() {
fmt.Println(test(1, 2, 3, 4))
}
func test(args ...int) string {
return fmt.Sprintf("args : %d", args)
}

注意:其中args是一个slice,我们可以通过arg[index]依次访问所有参数,通过len(arg)来判断传递参数的个数.

任意类型的不定参数: 就是函数的参数和每个参数的类型都不是固定的。

用interface{}传递任意类型数据是Go语言的惯例用法,而且interface{}是类型安全的。

func myfunc(args ...interface{}) { 
}

3.2. 返回值

"_"标识符,用来忽略函数的某个返回值

Go 的返回值可以被命名,并且就像在函数体开头声明的变量那样使用。返回值的名称应当具有一定的意义,可以作为文档使用。没有参数的 return 语句返回各个返回变量的当前值。这种用法被称作“裸”返回。直接返

回语句仅应当用在像下面这样的短函数中。在长的函数中它们会影响代码的可读性。

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

package main 
import (
"fmt"
)
func add(a, b int) (c int) {
c = a + b
return
}
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)
}

4. 匿名与闭包

4.1. 匿名函数

在Go里面,函数可以像普通变量一样被传递或使用,Go语言支持随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成。匿名函数的优越性在于可以直接使用函数内的变量,不必申明。

n := func(a int) int { 
a++
return a
}
fmt.Println(n)

碰巧匿名函数与上了切片

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

4.2. 闭包

一个令人熟悉的闭包

<?php 
function test() {
return function () {
var_dump("闭包");
};
}
test()();
?>

go闭包操作

package main 
import (
"fmt"
)
func main() {
test()()
}
func test() func() {
return func() {
fmt.Println("test func ()")
}
}

需要值得注意的是go中的作用域问题我们执行如下PHP代码

<?php 
function test() {
$i = 0;
return function () use ($i){
++$i;
echo $i."\n";
};
}
$c = test();
$c();
$c();
$c();
?>

效果

D:\phpStudy\PHPTutorial\WWW\go\src\shineyork\03>php index.php 
1
1
1
1

而在go中的情况

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

效果

D:\phpStudy\PHPTutorial\WWW\go\src\shineyork\03>go run shineyork\03

我们可以看一下对应的指针

func a() func() { 
i := 0
fmt.Printf("i (%p) = %d \n", &i, i)
b := func() {
i++
fmt.Printf("func i (%p) = %d \n", &i, i)
fmt.Println(i)
}
return b
}
func main() {
c := a()
c()
}

效果

D:\phpStudy\PHPTutorial\WWW\go\src\shineyork\03>go run shineyork\03 
i (0xc0000100a0) = 0
func i (0xc0000100a0) = 1
1

解释:

当函数a()的内部函数b()被函数a()外的一个变量引用的时候,就创建了一个闭包。 在上面的例子中,由于闭包的存在使得函数a()返回后,a中的i始终存在,这样每次执行c(),i都是自加1后的值。 从上面可以看出闭包的

作用就是在a()执行完并返回后,闭包使得Javascript的垃圾回收机制GC不会收回a()所占用的资源,因为a()的内部函数b()的执行需要依赖a()中的变量i。

在给定函数被多次调用的过程中,这些私有变量能够保持其持久性。变量的作用域仅限于包含它们的函数,因此无法从其它程序代码部分进行访问。不过,变量的生存期是可以很长,在一次函数调用期间所创建所生成

的值在下次函数调用时仍然存在。正因为这一特点,闭包可以用来完成信息隐藏,并进而应用于需要状态表达的某些编程范型中。 下面来想象另一种情况,如果a()返回的不是函数b(),情况就完全不同了。因为a()执行

完后,b()没有被返回给a()的外界,只是被a()所引用,而此时a()也只会被b()引 用,因此函数a()和b()互相引用但又不被外界打扰(被外界引用),函数a和b就会被GC回收。所以直接调用a();是页面并没有信息输出。

下面来说闭包的另一要素引用环境。c()跟c2()引用的是不同的环境,在调用i++时修改的不是同一个i,因此两次的输出都是1。函数a()每进入一次,就形成了一个新的环境,对应的闭包中,函数都是同一个函数,环境却

是引用不同的环境。这和c()和c()的调用顺序都是无关的。

5. 延迟调用(defer)

5.1. 介绍defer

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

go语言 defer

go 语言的defer功能强大,对于资源管理非常方便,但是如果没用好,也会有陷阱。

defer 是先进后出

这个很自然,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。

测试

func main() { 
def()
}
func def() {
defer func() {
fmt.Println("延迟输出")
}()
fmt.Println("正常输出")
}

效果

C:\Users\shineyork>go run shineyork/03

正常输出

延迟输出

5.2. defer与结构体

这个大家用的都很频繁,但是go语言编程举了一个可能一不小心会犯错的例子.

package main 
import "fmt"
type Test struct {
name string
}
func (t *Test) Test() {
fmt.Println(t.name, "test")
}
func main() {
ts := []Test{{"a"}, {"b"}, {"c"}}
for _, t := range ts {
defer t.Test()
}
}

输出结果:

C:\Users\shineyork>go run shineyork/03 
c test
c test
c test

这个输出并不会像我们预计的输出c b a,而是输出c c c可是按照前面的go spec中的说明,应该输出c b a才对啊.

那我们换一种方式来调用一下.

func (t *Test) Test() { 
fmt.Println(t.name, "test")
}
func Demo(t Test) {
t.Test()
}
func main() {
ts := []Test{{"a"}, {"b"}, {"c"}}
for _, t := range ts {
defer Demo(t)
}
}

输出

C:\Users\shineyork>go run shineyork/03 
c test
b test
a test

这个时候输出的就是c b a当然,如果你不想多写一个函数,也很简单,可以像下面这样,同样会输出c b a 看似多此一举的声明

func (t *Test) Test() { 
fmt.Println(t.name, "test")
}
func main() {
ts := []Test{{"a"}, {"b"}, {"c"}}
for _, t := range ts {
t1 := t
defer t1.Test()
}
}

输出

C:\Users\shineyork>go run shineyork/03 
c test
b test
a test

通过以上例子,结合

Each time a "defer" statement executes, the function value and parameters to the call are evaluated as usualand saved anew but the actual function is not invoked.

这句话。可以得出下面的结论:

多个 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

5.3. defer参数传递

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

6. 异常处理

Golang 没有结构化异常,使用 panic 抛出错误,recover 捕获错误。

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

panic:
1、内置函数
2、假如函数F中书写了panic语句,会终止其后要执行的代码,在panic所在函数F内如果存在要执行的defer函数列表,按照defer的逆序执行
3、返回函数F的调用者G,在G中,调用函数F语句之后的代码不会执行,假如函数G中存在要执行的defer函数列表,按照defer的逆序执行
4、直到goroutine整个退出,并报告错误

recover: 
1、内置函数
2、用来控制一个goroutine的panicking行为,捕获panic,从而影响应用的行为
3、一般的调用建议
a). 在defer函数中,通过recever来终止一个goroutine的panicking过程,从而恢复正常代码的执行
b). 可以获取通过panic传递的error

注意:

1.利用recover处理panic指令,defer 必须放在 panic 之前定义,另外 recover 只有在 defer 调用的函数中才有效。否则当panic时,recover无法捕获到panic,无法防止panic扩散。

2.recover 处理异常后,逻辑并不会恢复到 panic 那个点去,函数跑到 defer 之后的那个点。

3.多个 defer 会形成 defer 栈,后定义的 defer 语句会被最先调用。

示例

package main 
func main() {
defer func() {
if err := recover(); err != nil {
println(err.(string)) // 将 interface{} 转型为具体类型。
}
}()
panic("panic error hello world!")
}

效果

C:\Users\shineyork>go run shineyork/03 
panic err o!

底层总recover、panic都是interface{}类型,所以可以跑出任何异常对象

func panic(v interface{}) 
func recover() interface{}

如果defer也需要抛出异常咋办?

func main() { 
defer func() {
if err := recover(); err != nil {
println(err.(string)) // 将 interface{} 转型为具体类型。
}
}()
defer func() {
panic("this is defer func!")
}()
panic("panic error hello world!")
}

效果

C:\Users\shineyork>go run shineyork/03

this is defer func!

我们也可以实现一个go类型try

/**
* fun 正常的代码执行的方法
*
* handler 出现异常执行的操作
*/
func Try(fun func(), handler func(interface{})) {
defer func() {
if err := recover(); err != nil {
handler(err)
}
}()
fun()
}
func main() {
Try(func() {
panic("test try panic")
}, func(err interface{}) {
fmt.Println(err)
})
}

举报

相关推荐

0 条评论