-
Notifications
You must be signed in to change notification settings - Fork 0
/
02、函数
282 lines (192 loc) · 8.04 KB
/
02、函数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
函数的定义
func <函数名> ([[<形式参数1>] <形参1类型>][, [<形式参数2>] <形参2类型>]... ) ([[返回值名1] <返回值1类型>][, [返回值名2] <返回值2类型>]...){ [执行语句块] }
1、 函数的申明由 func 关键字指定
2、func 关键字后跟随该函数的 函数名
2.1、一个函数可以没有名称,没有名称的函数被称为 匿名函数
3、使用括号引起需要传入的参数
3.0、所有参数均以值拷贝的方式传入函数
3.1、若没有参数,也需要用空的括号表示
eg.
func myfunc() {}
3.2、形式参数不接受默认参数设置
3.3、形式参数在执行函数头是就已经初始化完毕了
3.4、形式参数名后必须指明其类型
eg.
func myfunc(a int) {}
3.5、可只注明形参类型而舍弃形参名,但不可两者混用
eg.
func myfunc(int) {}
3.6、多个形式参数用逗号隔开
eg.
func myfunc(a int, b int, c float64) {}
3.7、多个同相邻的同类型形式参数可以缩写类型
eg.
func myfunc(a,b int, c float64) {}
3.8、不需要赋予形参的传入值,可以用 _ 表示
eg.
func myfunc(a,_ int, c float54) {}
3.9、在形参的最后可以使用 ...[<类型>] 的方式定义不定长度的参数,也就是变参
这些参数将作为该类型的 切片 传入函数中
一个函数只能在参数的最后定义一个 不定长参数
eg.
func myfunc(a int, l ...int) {}
3.9.1、若变参不定义类型,则使用 interface{}
4、一个函数可以没有返回值
4.0.0、一个没有返回值的函数也可以使用 return 语句
它表示函数在这里返回,不执行剩余的代码
4.0、若一个函数有具有名称的返回值,则该返回值在函数头执行时就被初始化了
4.1、若函数只有一个返回值,且返回值没有名称,则可以省略括号
eg.
func myfunc(a int) string {}
4.2、一个函数可以返回多个值,多个值用括号括起来
eg.
func myfunc(a int) (string, bool) {}
4.3、返回值可以有名字,若返回值有名字,则在使用 return 语句的时候,可以省略返回变量名
eg.
func myfunc(a int32) (name string, ok bool) {
name = string(a)
ok = true
return
}
5、函数本身也是一种类型
函数的 传入值的类型、数量、位置 和 返回值的类型、数量、位置 共同决定了一个函数的类型
eg.
func f1(a,b int) (c int) // 这是一种类型为 func(int,int)int 的函数
func f2(a float64) // 这是一种类型为 func(float64) 的函数
6、大括号内是函数的执行语句
6.1、一个函数可以没有大括号,或者只有一个空的大括号
6.2、一个函数可以含有 renturn 语句返回一些值,也可以没有任何返回值
6.3、若函数头已经声明而来函数的返回值名称,则只需在语句块中对对应的变量赋值,在 return 处不需要指明它们
注意, Go 语言中,函数不允许嵌套声明,但函数可以作为值传入另一个函数中
eg.
func a(){
b := func(){ // 声明匿名函数,赋给变量 b
fmt.Println("x")
}
b() // 调用变量 b 所指向的匿名函数
}
函数的递归
Go 语言的函数递归和其他语言的递归类似
eg.
package main
import (
"fmt"
)
func main() {
re(0)
}
func re(i int) {
if i == 10 {
return // 这个 return 只会在 i = 10 的那个 re 函数里执行一次
}
re(i + 1)
fmt.Println(i) // 由于 i = 10 时,return 处在 fmt.Println 之前,所以没有打印出 10
} // 其他的 re 函数返回 都是因为执行到了语句尾部
函数内变量的作用域
1、函数内变量的作用域在函数本身之内
2、同名的本地变量会覆盖全局变量
3、一个函数的本地变量会成为内部函数新加入的全局变量
4、外部函数不能直接访问内部函数的本地变量
defer 语句 -- 延迟语句
defer <函数 或 表达式>
一个 延迟语句 中的函数或表达时 会延迟至 周围的函数返回之后 才执行
周围的函数返回包括
1、函数执行了 return 语句
2、函数执行到了结构块尾端
3、函数所在的 goroutine 遇到了 panic
一个延迟语句在被执行之时,仅会保存 被 延迟的 函数 或 表达式 的 传入值 和 参数,而不会真的执行其中的语句
直到 延迟语句 周围的函数都返回之后 才会以 后进先出(LIFO) 的方式 逆序执行 延迟语句 中的函数
defer 语句的常见用途
1、由于 defer 语句即使在 goroutine 遇到 panic 后依旧会被执行
可以用于让 goroutine 从 panic 中恢复
eg.
func main() {
defer func() { // 让 recover 处在 main 函数的 panic 之中
if err := recover(); err != nil {
fmt.Println("Recoverd from:", err)
}
}() // defer 内地额函数必须要被调用
panic("Ouch!") // goroutine 不会因为这个 panic 而终止
}
2、由于 defer 语句会在周围函数返回时执行,且会在 panic 后执行,
可以用于关闭已经打开的文件
eg.
func main() {
f, err := os.Open(fileName)
if err != nil {
fmt.Println(err)
return
}
defer func() {
f.Close()
fmt.Println("File", f, "Closed")
}()
}
3、由于 defer 语句在周围语句都返回后执行(包括 return 语句)
可以用于修改 函数的 已命名的返回值
func main() {
fmt.Println(A())
}
func A() (a int) { // 一定要先命名
defer func() { // 在 return 语句之后执行
a++
}()
return 0
}
函数作为变量的值
函数可以作为一个整体传入赋值给一个变量
eg.
f := func(){fmt.Println("A")} // 匿名函数赋值
f() // 执行匿名函数
函数的回调
用调用某个指向函数的指针,来执行被指针指向的实际函数的方式,就叫函数的回调
那个实际的函数,在这种情况下,就叫做回调函数
eg.
func add(a, b int) int { // 定义了一个实际操作的函数
return a + b
}
func minus(a, b int) int { // 定义了第二个实际操作的函数
return a - b
}
func run(a, b int, algorithm func(int, int) int) int { // 定义了一个中间函数,这里类似一个接口
return algorithm(a, b) // 调用(回调)那个实际操作的函数,并传入实际操作的函数所需要的值
}
func main() {
fmt.Println(run(1, 2, add)) // 传入 add 函数,以及 add 函数需要的两个值,你不需要管 add 是怎么操作的,只需要使用即可
fmt.Println(run(1, 2, minus)) // 传入 minus 函数,以及 minus 函数需要的两个值,你不需要管 minus 是怎么操作的,只需要使用即可
}
恐慌(Panic)和恢复(Recover)
Go 语言中没有 异常/处理机制
转而使用的是 Panic/Recover 机制
Panic
是一个内建函数,作用是中断原有的函数执行流程,进入特殊的 Panic 流程中
在 Panic 流程中,defer 的函数可以正常的执行
若 Panic 没有被 Recover 处理,则原有函数会返回它被调用的地方,并像执行了 panic 函数一样
除非被任何一个 Recover 捕获,否则,这个这个流程会一直向上层传递,直到所有的 goroutine 返回
Panic 可以被 panic 函数产生,也可以由 runtime 时的错误产生
Recover
是一个内建函数,可以捕获 panic ,并让 recover 执行之后的流程按照正常流程执行
recover() 必须在 defer 中才有用
eg.
func main() {
fmt.Println("Good Morning") // 提示程序开始运行
test() // panic & recover 测试函数
fmt.Println("All Done") // 检查 panic 是否真的被捕获了
}
func test() {
fmt.Println("let's begin") // 提示 test 函数开始执行
defer func() { recover() }() // 记录一个 匿名函数,其中调用了 recover 函数
panic("Ouch!") // 产生一个 panic
fmt.Println("Done") // 检查执行状态
}
程序打印以下内容
Good Morning
let's begin
All Done
分析
打印 Good Morning 表示程序开始执行
打印 let's begin 表示 test 函数开始执行
没有打印 Ouch! 由于 panic 本身并打印出任何信息
没有打印 Done 由于 panic 让 test 函数返回了
打印了 All Done 表示 主程序执行了最后的语句
也就是 panic 没有扩散至 主程序中,也就是被 recover 捕获并恢复了