目录

go函数详解

go函数详解

1.简介

函数是组织好的、可重复使用的,用于执行指定任务的代码块,为了完成某一个功能的程序指令的集合,称为函数。go语言中支持:函数、匿名函数和闭包。

2.函数的定义

func 函数名 (形参列表) (返回值列表){

函数体

return 返回值列表

}

其中:

  • 函数名:由字母、数字、下划线组成。但函数名第一个字母不能是数字。在同一个包内,函数名也不能重名。
  • 形参列表:参数由参数变量和参数变量的类型组成,做个参数之前是用逗号分割。
  • 返回值:返回值由返回之变量和其类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用逗号分隔。
  • 函数体:实现指定功能的代码块。

3.基本用法

3.1函数的基本用法

函数的参数和返回值都是可选的,例如我们可以实现一个即不需要参数也不需要返回值的函数:

func sayHello() {
	fmt.Println("sayhello")
}

3.2求两个数的和

func sumFn(a int, b int) int {
	return a + b
}

调用:

func main() {
	sum := sumFn(10, 13)
	fmt.Println(sum)
}

注意:调用的函数有返回值时,可以不接受其返回值。

3.3函数简写(求两个数的差)

函数的参数中如果相邻变量的类型相同,则可以省略类型,例如下面代码中,subFn函数有两个参数,这两个参数的类型相同,因此可以省略a的类型,因为b后面有类型说明,a参数也是该类型。

func subFn(a, b int) int {
	return a - b
}

3.4可变参数

可变参数是指函数的参数数量是不固定,go语言中的可变参数通过在参数后面加…来标识的。

注意:可变参数通常要作为函数的最后一个参数。可变参数是一个切片。

func changFn(a int, x ...int) int {
	fmt.Printf("%v--%T\n", x, x)
	var sum = a
	for _, v := range x {
		sum += v
	}
	return sum
}

调用

func main() {
	changSum := changFn(1, 2, 3, 4, 5, 6)
	fmt.Println(changSum)
}

结果:

[2 3 4 5 6]--[]int
21

3.5函数返回值

go语言中通过return关键字向外输出返回值。上面代码已经体验函数单个返回值的用法了。

go语言中的函数还支持多返回值,函数如果有多个返回值时必须用()将所有的返回值包裹起来。

func moreFn(a, b int) (int, int) {
	sum := a + b
	sub := a - b
	return sum, sub
}
func main() {	 
	msum, msub := moreFn(20, 13)
	fmt.Println(msum, msub)
}

还支持返回值命名,函数定义时可以给返回值命名,并在函数体中直接使用这些变量,最后通过return关键字返回。例如:

func moreFn2(a, b int) (sum, sub int) {
	sum = a + b
	sub = a - b
	return
}
func main() {
	msum, msub := moreFn2(20, 17)
	fmt.Println(msum, msub)
}

4.函数变量作用域

全局变量:全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。全局作用域。

局部变量:局部变量是在函数内部定义的变量,函数内定义的变量无法在函数外部使用。局部变量。

注意:如果全局变量和局部变量重名了,优先访问局部变量。

var a = "全局变量"

func run() {
	var b="局部变量"
	fmt.Println("run--a=", a)
	fmt.Println("run--b=",b)


}
func main() {
	run()
	fmt.Println("main--a=", a)
	//i是局部变量,只能在for内部使用
	for i := 0; i < 10; i++ {
		fmt.Println(i)
		
	}
}

5.练习

5.1封装整数类型的切片排序方法

要求:选择排序,从小到大。

func sortIntAsc(slice []int) []int {
	for i := 0; i < len(slice); i++ {
		for j := i + 1; j < len(slice); j++ {
			if slice[i] > slice[j] {
				slice[i], slice[j] = slice[j], slice[i]
			}
		}
	}
	return slice
}
func main() {
	sliceA := []int{23, 1, 5, 67, 13, 22}
	fmt.Println(sortIntAsc(sliceA))
	fmt.Println(sliceA)
}

结果:因为切片是引用类型的数据,所以两次结果是一致的。

[1 5 13 22 23 67]
[1 5 13 22 23 67]

5.2要求把map按照key的顺序进行打印

例如:

var m1 map[string]string
m1 = make(map[string]string)
m1["username"] = "张三"
m1["age"] = "18"
m1["height"] = "1.8"
m1["sex"] = "男"

打印结果:age=>18height=>1.8sex=>男username=>张三

func sortMap(m map[string]string) string {
	var slice []string
	for key, _ := range m {
		slice = append(slice, key)
	}
	sort.Strings(slice)
	var str string
	for _, s := range slice {
		str += fmt.Sprintf("%v=>%v", s, m[s])
	}
	return str
}
func main() {
	var m1 map[string]string
	m1 = make(map[string]string)
	m1["username"] = "张三"
	m1["age"] = "18"
	m1["height"] = "1.8"
	m1["sex"] = "男"
	str := sortMap(m1)
	fmt.Println(str)
}

6.函数类型与变量

定义函数类型:我们可以使用type关键字来定义一个函数类型,具体格式如下

type 类型名称 func(参数类型,参数类型。。。) 返回值类型

例如

type  calculation func(int,int) int

上面语句定义了一个calculation类型,它是一种函数类型,这种函数类型接受两个int类型的参数并且返回一个int类型的返回值。

package main

import "fmt"

// 定义函数类型
type calculation func(int, int) int

func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}
func main() {
	var c calculation
	c = add
	fmt.Printf("%T\n", c)
	fmt.Println(c(2, 4))
	f := sub
	fmt.Printf("%T\n", f)
	fmt.Println(f(10, 2))
}

结果:

main.calculation
6
func(int, int) int
8

由上面结果可知,add和sub函数都满足接收两个int类型的参数并且都返回一个int类型的值,所以可以把赋值给calculation类型的变量。

7.把函数作为参数

package main

import "fmt"

// 定义函数类型
type calculation func(int, int) int

func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}
func fn1(a, b int, do calculation) int {
	return do(a, b)
}
func fn2(a, b int, do func(int, int) int) int {
	return do(a, b)
}
func main() {
	fmt.Println(fn1(1, 2, add))
	fmt.Println(fn2(12, 2, sub))
}

结果:

3
10

8.把函数当作返回值

package main

import "fmt"

// 定义函数类型
type calculation func(int, int) int

func add(a, b int) int {
	return a + b
}
func sub(a, b int) int {
	return a - b
}
func cal(s string) calculation {
	switch s {
	case "+":
		return add
	case "-":
		return sub
	case "*":
		return func(i int, i2 int) int {
			return i * i2
		}
	default:
		return nil

	}
}
func main() {
	sum := cal("+")
	fmt.Println(sum(1, 2))
	fmt.Println(cal("-")(12, 10))
	fmt.Println(cal("*")(3, 10))
}

结果:

3
2
30

9.匿名函数

函数当然还可以作为返回值,但是在Go语言中函数内部不能再像之前那样定义函数了,只 能定义匿名函数。匿名函数就是没有函数名的函数,匿名函数的定义格式如下:

func(参数)(返回值){ 函数体 }

匿名函数因为没有函数名,所以没办法像普通函数那样调用,所以匿名函数需要保存到某个 变量或者作为立即执行函数:

package main

import "fmt"

func main() {
	func() {
		fmt.Println("hello word")
	}()
	//将匿名函数保存到变量
	add := func(x, y int) {
		fmt.Println(x + y)
	}
	add(10, 20) //通过变量调用匿名函数
	//自执行函数:匿名函数定义完加()直接执行
	func(x, y int) {
		fmt.Println(x + y)
	}(10, 20)

}

结果:

hello word
30
30

10.闭包

10.1简介

闭包:

1.闭包是指有权访问另一个函数作用域中的函数。

2.创建闭包的常见的方式就是在一个函数内部创建另一个函数,通过另一个函数访问这个函数的局部变量。

注意:由于闭包里作用域返回的局部变量资源不会立即销毁回收,搜易可能会占用更多的内存,过度使用闭包会导致性能下降,建议在非常有必要的时候才使用闭包。

全局变量特点:

1.常驻内存

2.全局污染

局部变量的特点:

1.不常住内存

2.不污染全局

闭包:

1.可以让一个变量常驻内存

2.可以提让一个变量不污染全局

闭包可以理解成“定义在一个函数内部的函数“。在本质上,闭包是将函数内部和函数外部 连接起来的桥梁。或者说是函数和其引用环境的组合体。首先我们来看一个例子:

package main

import "fmt"
//写法:闭包的写法 函数里面桥套一个函数 最后返回里面的函数
func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var f = adder()
	fmt.Println(f(10)) //10
	fmt.Println(f(20)) //30
	fmt.Println(f(30)) //60
	f1 := adder()
	fmt.Println(f1(40)) //40
	fmt.Println(f1(50)) //90
}

结果:

10
30
60
40
90

由结果可知:变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。在f的生 命周期内,变量x也一直有效。

11.defer语句

go语言中的defer语句会将其后面跟随的语句进行延迟处理。在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行,也就是说,先被defer的语句最后被执行。在函数中,经常会用到释放一些资源,比如数据库链接和文件的读写等。

注意 :当go执行到一个defer时,不会立即执行defer后的语句,而将defer后的语句压入到一个栈中,然后执行函数下一个语句。当函数执行完毕后,在从栈中按照先入后出的方式出栈,执行。

案例一

package main

import "fmt"

func sum(a, b int) int {
	defer fmt.Println("a=", a)
	defer fmt.Println("b=", b)
	res := a + b
	fmt.Println("res=", res)
	return res
}
func main() {
	fmt.Println(sum(10, 20))
}

结果:由结果可以看出先被defer定义的语句后执行,符合逆序执行的策略。

res= 30
b= 20
a= 10
30

案例二:注意:在defer将语句放入到栈时,也将相关的值拷贝同时入栈。

package main

import "fmt"

func sum(a, b int) int {
	defer fmt.Println("use defer--a=", a)
	defer fmt.Println("use defer--b=", b)
	a++
	b++
	fmt.Println("not defer--a=", a)
	fmt.Println("not defer--b=", b)
	res := a + b
	fmt.Println("res=", res)
	return res
}
func main() {
	fmt.Println(sum(10, 20))
}

结果:

not defer--a= 11
not defer--b= 21
res= 32
use defer--b= 20
use defer--a= 10
32

案例三:注意defer注册要延迟执行的函数时该函数所有的参数都要确定其值。

package main

import "fmt"

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}
/*
	注册顺序:
	    defer calc("AA", x, calc("A", x, y))
	    defer calc("BB", x, calc("B", x, y))
	执行顺序:
	    defer calc("AA", x, calc("A", x, y))
		defer calc("BB", x, calc("B", x, y))
*/
func main() {
	x := 1
	y := 2
	//在defer将语句放入到栈时,也将相关的值拷贝同时入栈。
	//defer注册要延迟执行的函数时该函数所有的参数都要确定其值。
	/*
		所以执行时 x=1  y=2
		calc("A", x, y)也要确定其值,所以要首先执行
		calc("A", 1, 2)=3   结果:A 1 2 3
		calc("AA", 1, 3)=4
        整体执行顺序
        1.x=1 y=2 a:=calc("A", x, y)
        2.x=10 y=2 b:=calc("B", x, y)
        3.x=10 y=2 calc("BB", x, b)
        4.x=1 y=2 calc("AA", x, a)

	*/
	defer calc("AA", x, calc("A", x, y))
	x = 10
	//x=10  y=2
	defer calc("BB", x, calc("B", x, y))
	y = 20
}

结果:

A 1 2 3
B 10 2 12
BB 10 12 22
AA 1 3 4

12.内置函数panic/recover

Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover模式来处理错误。panic 可以在任何地方引发,但recover只有在defer调用的函数中有效。

示例:

package main

import "fmt"

func fn3(a []int) {

	fmt.Println(a[10])
}
func main() {
	fn3([]int{1, 2, 3})

	fmt.Println("main")
}

结果:下标越界了,不能执行下面代码。如果想要继续执行下面的代码,可以抛出异常处理。

panic: runtime error: index out of range [10] with length 3

goroutine 1 [running]:
main.fn3(...)
	D:/goproject/demo2/demo/func8/main.go:7
main.main()
	D:/goproject/demo2/demo/func8/main.go:10 +0x1d

解决方案:程序运行期间引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过 recover将程序恢复回来,继续往后执行。

package main

import "fmt"

func fn3(a []int) {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()
	fmt.Println(a[10])
}
func main() {
	fn3([]int{1, 2, 3})

	fmt.Println("main")
}

结果:

runtime error: index out of range [10] with length 3
main

自定义异常处理:

package main

import (
	"errors"
	"fmt"
)

func readFile(fileName string) error {
	if fileName == "main.go" {
		return nil
	}
	return errors.New("读取文件错误")
}
func fn3() {
	defer func() {
		err := recover()
		if err != nil {

			fmt.Println("抛出异常给管理员发送邮件")
		}
	}()
	var err = readFile("xxx.go")
	if err != nil {
		panic(err)
	}
	fmt.Println("继续执行")
}
func main() {
	fn3()
}

结果:

抛出异常给管理员发送邮件

注意:

  1. recover()必须搭配defer使用。

  2. defer一定要在可能引发panic的语句之前定义。