函数
函数
定义函数
1
2
3
4
func 函数名字 (参数列表) (返回值列表){
// 函数体
return 返回值列表
}
注意:
- 函数名首字母小写为私有,大写为公有;
- 参数列表可以有0-多个,多参数使用逗号分隔,不支持默认参数;
- 返回值列表返回值类型可以不用写变量名
- 如果只有一个返回值且不声明类型,可以省略返回值列表与括号
- 如果有返回值,函数内必须有return
init函数和mian函数
init函数:init 函数可在package main中,可在其他package中,可在同一个package中出现多次。
main函数: main 函数只能在package main中
执行顺序
- 程序的初始化和执行都起始于main包(注意是main包,不是面函数)
- 先按包的顺序依次自动执行他们的init函数
- 再执行main包init函数, 然后继续执行main函数
示例:
Lib1.go
1
2
3
4
5
6
7
package InitLib1
import "fmt"
func init() {
fmt.Println("lib1")
}
Lib2.go
1
2
3
4
5
6
7
package InitLib2
import "fmt"
func init() {
fmt.Println("lib2")
}
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package main
import (
"fmt"
_"GolangStudy/5-init/lib1"
_"GolangStudy/5-init/lib2"
)
func init() {
fmt.Println("libmain init")
}
func main() {
fmt.Println("libmian main")
}
运行结果
1
2
3
4
lib1
lib2
libmain init
libmian main
改动一个地方,Lib1包导入Lib2,main包不管
1
2
3
4
5
6
7
8
9
10
package InitLib1
import (
"fmt"
_ "GolangTraining/InitLib2"
)
func init() {
fmt.Println("lib1")
}
运行结果
1
2
3
4
lib2
lib1
libmain init
libmian main
常用函数用法
注意:
①:Go函数不支持重载
②:一个包中不能有两个名字一样的函数
③:当两个或多个连续的函数命名参数是同一类型,则除了最后一个类型之外,其他的类型可以省略
④:函数可以返回任意数量的返回值
⑤:使用关键字 func 定义函数,左大括号依旧不能另起一行
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 函数多参无返回值
func func_name(a,b int, c string){}
// 函数无参无返回值
func func_name(){}
// 单个返回值
func func_name(s string) string{}
// 多个返回值
func func_name (s string) (string,int){}
// 命名返回参数
func func_name(s string) (result string){
...
result=1
return
}
// 可变参数,可变参数只能做为函数参数存在,并且是最后一个参数,本质上是slice
func func_name(s string,args ...int){}
// 匿名函数,调用:f(1,2)
f := func(x,y int) int {
return x + y
}
一、闭包
闭包是引用了自由变量的函数,被引用的自由变量和函数一同存在,即使己经离开了自由变量的环境也不会被释放或者删除,在闭包中可以继续使用这个自由变量。
闭包( Closure)在某些编程语言中也被称为 Lambda表达式(如Java)
函数+引用环境=闭包
在闭包中可以修改引用的变量:
1
2
3
4
5
6
str := "hello"
foo := func(){ // 声明一个匿名函数
str = "world"
}
foo() // 调用匿名函数,修改str值
fmt.Print(str) // world
闭包案例一 简单示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
func fn1(a int) func(i int) int {
return func(i int) int {
print(&a, a)
return a
}
}
func main() {
f := fn1(1) //输出地址
g := fn1(2) //输出地址
fmt.Println(f(1)) //输出1
fmt.Println(f(1)) //输出1
fmt.Println(g(2)) //输出2
fmt.Println(g(2)) //输出2
}
闭包案例二 实现累加器
1
2
3
4
5
6
7
8
9
10
11
12
13
func Accumulate(value int) func() int {
return func() int { // 返回一个闭包
value++
return value
}
}
func main() {
accAdd := Accumulate(1)
fmt.Println(accAdd()) // 2
fmt.Println(accAdd()) // 3
}
二、 可变参数 :crossed_swords:
语法
1
2
3
4
5
6
// 声明
func 函数名(固定参 类型, 可变参 ...类型) 返回值 { ... }
// 调用时
单个可传值:f(a, b, c)
已有切片可以使用...:slice...(必须加 ... 且类型匹配)
示例1
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
func name(query interface{}, args ...interface{}) {
// 获取query参数
queryStr := query.(string)
println("Query:", queryStr)
// 获取可变参数args
println("参数个数:", len(args))
// 遍历所有参数
for i, _ := range args {
s := args[0].(string)
println("参数", i, ":", s)
n := args[1].(int)
println("参数", i, ":", n)
}
}
func main() {
query := "status = ? AND type = ?"
args := []interface{}{"active", 100}
name(query, args...)
}
/**
Query: status = ? AND type = ?
参数个数: 2
参数 0 : active
参数 0 : 100
参数 1 : active
参数 1 : 100
**/
示例2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
func List( pageNumber, pageSize int, query interface{}, args ...interface{}) {
// ...
err := c.db.WithContext(ctx).Where(query, args...).Limit(pageSize).Offset(offset).Find(out).Error
// ...
}
func main() {
query := "status = ? AND type = ?"
args := []interface{}{"active", "type1"}
// 调用 List 方法 可变参数传参得这样写args...
err := client.List(ctx, &results, 1, 10, query, args...)
if err != nil {
fmt.Printf("Failed to list data: %v\n", err)
return
}
}
示例3
1
2
3
4
5
6
7
8
9
10
11
12
func (c *Client) ProxyToGate(ctx context.Context, shopInfo models.ShopInfoT, args map[string]string, rsp interface{}, originData ...*string) error{
}
// 不传 originData 参数
err := client.ProxyToGate(ctx, shopInfo, args, rsp)
// 传入一个 *string 参数
err := client.ProxyToGate(ctx, shopInfo, args, rsp, &someString)
// 传入多个 *string 参数
err := client.ProxyToGate(ctx, shopInfo, args, rsp, &str1, &str2)
三、立即执行的函数
跟JS类似,go中也可以让函数立即执行,并且不需要像JS一样在函数之前添加 ;
1
2
3
4
5
func(values...int) {
for _, v := range values {
fmt.Println(v)
}
}(1,2,3)
1
2
3
1
2
3
四、返回多个值的函数
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
package main
import "fmt"
//返回单个返回值,匿名的
func foo1(a string, b int) int {
fmt.Println("a = ", a)
fmt.Println("b = ", b)
c := 100
return c
}
//返回多个返回值,匿名的
func foo2(a string, b int) (int, int) {
fmt.Println("a = ", a)
fmt.Println("b = ", b)
return 666, 777
}
//返回多个返回值, 有形参名称的
//func foo3(a string, b int) (r1, r2 int) 或者
func foo3(a string, b int) (r1 int, r2 int) {
fmt.Println("---- foo3 ----")
fmt.Println("a = ", a)
fmt.Println("b = ", b)
//r1 r2 属于foo3的形参, 初始化默认的值是0
//r1 r2 作用域空间 是foo3 整个函数体的{}空间
fmt.Println("r1 = ", r1)
fmt.Println("r2 = ", r2)
//给有名称的返回值变量赋值
r1 = 1000
r2 = 2000
return
}
func main() {
c := foo1("abc", 555)
fmt.Println("c = ", c)
ret1, ret2 := foo2("haha", 999)
fmt.Println("ret1 = ", ret1, " ret2 = ", ret2)
ret1, ret2 = foo3("foo3", 333)
fmt.Println("ret1 = ", ret1, " ret2 = ", ret2)
}
// ---- foo1 ----
// a = abc
// b = 555
// c = 100
// ---- foo2 ----
// a = haha
// b = 999
// ret1 = 666 ret2 = 777
// ---- foo3 ----
// a = foo3
// b = 333
// r1 = 0
// r2 = 0
// ret1 = 1000 ret2 = 2000
五、值传递与引用传递
按值传递
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func swap(x int, y int) {
var temp int
temp = x
x = y
y = temp
}
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %d\n", a ) // 100
fmt.Printf("交换前 b 的值为 : %d\n", b ) // 200
/* 通过调用函数来交换值 */
swap(a, b)
fmt.Printf("交换后 a 的值 : %d\n", a ) // 100
fmt.Printf("交换后 b 的值 : %d\n", b ) // 200
}
程序中使用的是值传递,所以两个值并没有实现交换。
按引用传递
包括参数类型
1
1. 指针(*T)2. 切片(slice)3. 字典(map)4. 通道(channel)5. 接口(interface)6. 函数值(func)
示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func swap(x *int, y *int) {
var temp int
temp = *x
*x = *y
*y = temp
}
func main() {
/* 定义局部变量 */
var a int = 100
var b int = 200
fmt.Printf("交换前 a 的值为 : %d\n", a ) // 100
fmt.Printf("交换前 b 的值为 : %d\n", b ) // 200
/* 通过调用函数来交换值 */
swap(&a, &b)
fmt.Printf("交换后 a 的值 : %d\n", a ) // 200
fmt.Printf("交换后 b 的值 : %d\n", b ) // 100
}
六、延迟语句
使用 defer 关键字可以使函数中的某一条语句延迟执行,也就是在函数结束的时候才执行
defer作用:
- 释放占用的资源(锁和文件)
- 捕捉处理异常
- 输出日志
基本使用
| Bad | Good |
|---|---|
p.Lock() if p.count < 10 { p.Unlock() return p.count } p.count++ newCount := p.count p.Unlock() return newCount // 当有多个 return 分支时,很容易遗忘 unlock | p.Lock() defer p.Unlock() if p.count < 10 { return p.count } p.count++ return p.count // 更可读 |
比如在读写文件时,可以将关闭文件写在前面,以防止遗忘关闭文件的操作:
1
2
3
4
5
6
7
8
func readFile(path string) ([]byte, error) {
file, err := os.Open(path)
if err != nil {
return nil, err
}
defer file.Close()
return ioutil.ReadAll(file)
}
如果一个函数中有多个defer语句,它们会以LIFO(后进先出)的顺序执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
package main
import "fmt"
func main() {
//写入defer关键字
defer fmt.Println("main end1")
defer fmt.Println("main end2")
fmt.Println("main::hello go 1")
fmt.Println("main::hello go 2")
}
defer匿名函数
当一个函数中存在多个defer语句时,可以将其放置于匿名函数中,它们携带的表达式语句的执行顺序一定是它们的出现顺序的倒序。
1
2
3
4
5
6
7
8
func main() {
fmt.Println(1)
defer func() {
fmt.Println(2)
fmt.Println(3)
}()
fmt.Println("end")
}
循环之中的defer
当defer放于循环之中,其输出值将反向输出。
1
2
3
4
5
6
7
8
9
10
11
func main() {
f := func(i int) int {
// 1 2 3 4
fmt.Printf("%d ",i)
return i * 10
}
for i := 1; i < 5; i++ {
defer fmt.Printf("%d ", f(i)) // 40 30 20 10
}
}
输出:
1
1 2 3 4 40 30 20 10
recover错误拦截
运行时panic异常一旦被引发就会导致程序崩溃。
Go语言提供了专用于“拦截”运行时panic的内建函数“recover”。它可以是当前的程序从运行时panic的状态中恢复并重新获得流程控制权。
注意:recover只有在defer调用的函数中有效。
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
package main
import "fmt"
func Demo(i int) {
//定义10个元素的数组
var arr [10]int
// 捕获处理panic
//错误拦截要在产生错误前设置
defer func() {
//设置recover拦截错误信息
err := recover()
//产生panic异常 打印错误信息
if err != nil {
fmt.Println(err)
}
}()
// 触发panic
//根据函数参数为数组元素赋值
//如果i的值超过数组下标 会报错误:数组下标越界
arr[i] = 10
}
func main() {
Demo(10)
//产生错误后 程序继续
fmt.Println("程序继续执行...")
}
运行结果
1
2
runtime error: index out of range
程序继续执行...
七、递归函数
阶乘
1
2
3
4
5
6
7
8
9
10
11
12
func Factorial(n uint64)(result uint64) {
if n > 0 {
result = n * Factorial(n-1)
return result
}
return 1
}
func main() {
var i int = 5
fmt.Printf("%d 的阶乘是 %d\n", i, Factorial(uint64(i)))
}
斐波那契数列
1
2
3
4
5
6
7
8
9
10
11
12
13
func fibonacci(n int) int {
if n < 2 {
return n
}
return fibonacci(n-2) + fibonacci(n-1)
}
func main() {
var i int
for i = 0; i < 10; i++ {
fmt.Printf("%d\t", fibonacci(i))
}
}
八、匿名函数 :crossed_swords:
匿名函数的优越性在于可以直接使用函数内的变量,不必声明
Golang匿名函数可赋值给变量,做为结构字段,或者在 channel 里传送。
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
func main() {
a := 3
// f1 即为匿名函数
f1 := func(num int) {
fmt.Println(num)
}
f1(a)
// 匿名函数自调
func() {
fmt.Println(a)
}()
//匿名函数实战:取最大值,最小值
x, y := func(i, j int) (max, min int) {
if i > j {
max = i
min = j
} else {
max = j
min = i
}
return
}(10, 20)
fmt.Println(x + ' ' + y)
}
1
2
3
3
3
62
九、函数表达式
使用函数表达式实现三目运算
1
2
3
4
5
6
7
8
9
10
11
// 函数表达式,实现三目运算
// 格式:func() returnType {...}()
i := 1
j := 2
k := func() int {
if i > j {
return i
}
return j
}()
fmt.Println(k)
带参数的函数表达式:
1
2
3
4
5
6
h := func(a, b int) int {
if a > b {
return a
}
return b
}(i, j)
十、函数类型 :crossed_swords:
1
2
3
4
5
6
7
//定义一个函数类型
type MyMath func(int, int) int
//定义的函数类型作为参数使用
func Test(f MyMath, a , b int) int{
return f(a,b)
}
通常可以把函数类型当做一种引用类型,实际函数类型变量和函数名都可以当做指针变量,只想函数代码开始的位置,没有初始化的函数默认值是nil。
自定义函数类型
1
2
3
4
5
6
7
8
9
10
11
// 方式1:普通函数声明
func FuncJdMagicCard(ctx context.Context, body jdModel.Body) (string, error) {
// 函数体
}
// 方式2:先声明变量,再赋值
var FuncJdMagicCard func(ctx context.Context, body jdModel.Body) (string, error)
FuncJdMagicCard = func(ctx context.Context, body jdModel.Body) (string, error) {
// 函数体
}
示例
让业务代码通过字符串 key 在运行时拿到对应版本的高阶函数,避免一堆 if/switch,也利于后续横向扩展新模板
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
// 自定义函数类型1
type cardConverter func(ctx context.Context, body jdModel.Body) (string, error)
// 自定义函数类型2
type cardConverterV2 func(ctx context.Context, body jdModel.Body) ([]*msgAdapterPb.CardMsgData, error)
var templateToCardMap map[string]cardConverter
var templateToCardMapV2 map[string]cardConverterV2
//
func init() {
templateToCardMap = make(map[string]cardConverter)
templateToCardMap[string(JdOrderCheckTypeCard)] = FuncJdOrderCheckCard
templateToCardMapV2 = make(map[string]cardConverterV2)
templateToCardMapV2[string(JdOrderCheckTypeCard)] = FuncJdOrderCheckCardV2
}
// 函数类型1
var FuncJdOrderCheckCard = func(ctx context.Context, body jdModel.Body) (string, error) {
}
// 函数类型2
var FuncJdOrderCheckCardV2 = func(ctx context.Context, body jdModel.Body) ([]*msgAdapterPb.CardMsgData, error)
}
