文章

函数

函数

定义函数

1
2
3
4
func 函数名字 (参数列表) (返回值列表{
	// 函数体
	return 返回值列表
}

注意:

  • 函数名首字母小写为私有,大写为公有;
  • 参数列表可以有0-多个,多参数使用逗号分隔,不支持默认参数;
  • 返回值列表返回值类型可以不用写变量名
  • 如果只有一个返回值且不声明类型,可以省略返回值列表与括号
  • 如果有返回值,函数内必须有return 

init函数和mian函数

init函数:init 函数可在package main中,可在其他package中,可在同一个package中出现多次。

main函数: main 函数只能在package main中

执行顺序

  1. 程序的初始化和执行都起始于main包(注意是main包,不是面函数)
  2. 先按包的顺序依次自动执行他们的init函数
  3. 再执行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
  1. image-20251023223503175程序的初始化和执行都起始于main包(注意是main包,不是main函数)
  2. 先按包的顺序依次自动执行他们的init函数
  3. 再执行main包init函数, 然后继续执行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. 指针*T2. 切片slice3. 字典map4. 通道channel5. 接口interface6. 函数值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作用:

  • 释放占用的资源(锁和文件)
  • 捕捉处理异常
  • 输出日志

基本使用

BadGood
p.Lock() if p.count < 10 { p.Unlock() return p.count } p.count++ newCount := p.count p.Unlock() return newCount // 当有多个 return 分支时,很容易遗忘 unlockp.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) 	
}

Go 关键字 defer 使用详解(使用场景易错分析) - 五岁博客

© 2024- lfj