Go -- 函数基础

  • Go语言中支持函数、匿名函数和闭包,并且函数在Go语言中属于“一等公民”。

一、函数基础

1、常见的定义

格式:

func 函数名(参数)(返回值){
    函数体
}
  • 函数名:由字母、数字、下划线组成。但函数名的第一个字母不能是数字。在同一个包内,函数名也称不能重名(包的概念详见后文)。
  • 参数:参数由参数变量和参数变量的类型组成,多个参数之间使用,分隔。
  • 返回值:返回值由返回值变量和其变量类型组成,也可以只写返回值的类型,多个返回值必须用()包裹,并用,分隔。
  • 函数体:实现指定功能的代码块。

示例:

// 1、标准的函数定义语法
func sum(a int, b int) (ret int) {
	ret = a + b    // 因为声明的返回值名称,因此可以直接用=号
	return         // 声明了返回值名称ret  ,return后可以不加参数,默认返回ret
}

// 2、返回值可以命名,也可以不用命名(命名返回值,就相当于在函数中声明了一个变量,可以在函数内直接使用,否则需要:=的方式来声明)
func sum1(a int, b int) int {
	return a + b
}

// 3、函数可以没有返回值
func sum2(a int, b int) {
	fmt.Println(a + b)
}

// 4、没有参数没有返回值
func sum3() {
	fmt.Println("sum3")
}

// 5、没有参数但是有返回值
func sum4() int {
	return 2
}

2、函数的调用

  • 定义了函数之后,我们可以通过 函数名() 的方式调用函数。 例如我们调用上面定义的两个函数
  • 注意,调用有返回值的函数时,可以不接收其返回值。
func main() {
	sayHello()
	ret := intSum(10, 20)
	fmt.Println(ret)
}

3、函数的参数

3.1、参数类型的简写

  • 当参数中连续两个以上参数的类型一致时,我们可以将前面的参数的类型省略
// 1、参数类型的简写,当参数中连续两个以上参数的类型一致时,我们可以将前面的参数的类型省略
func sum6(x, y int, a, b, c string) int {
	fmt.Println(a, b, c)
	return x + y
}

3.2、可变长参数

  • 可变长参数(变量名 …类型)这种方式传入,可变长参数返回的是slice切片类型数据
  • 可变长参数可以传入多个参数,也可以不传参
  • 但是注意:可变长参数必须放在函数参数的最后一个
// 2、可变长参数(变量名 ...类型)这种方式传入,可变长参数返回的是slice切片类型数据
func f1(a string, b ...int) {
	fmt.Println(a, b)
}

func main() {
	f1("小明", 1, 2, 3, 4, 5)
}

4、函数返回值

4.1、返回值可以命名,也可以不用命名

// 1、返回值可以命名,也可以不用命名(命名返回值,就相当于在函数中声明了一个变量,可以在函数内直接使用,否则需要:=的方式来声明)
func sum1(a int, b int) int {
	return a + b
}

4.2、函数可以没有返回值

  • Go语言中通过 return 关键字向外输出返回值。
  • Go语言中函数支持多返回值,函数如果有多个返回值时必须用()将所有返回值包裹起来。
// 2、函数可以没有返回值
func sum2(a int, b int) {
	fmt.Println(a + b)
}

4.3、多返回值

  • 多个返回值时,要么都不命名,要么就都命名
func calc(x, y int) (int, int) {
	sum := x + y
	sub := x - y
	return sum, sub
}
func calc(x, y int) (sum int, sub int) {
	sum = x + y
	sub = x - y
	return
}

4.4、返回值补充

  • 当我们的一个函数返回值类型为slice时,nil可以看做是一个有效的slice,没必要显示返回一个长度为0的切片。
func someFunc(x string) []int {
	if x == "" {
		return nil // 没必要返回[]int{}
	}
}

补充: Go语言中不支持默认参数

二、函数进阶

1、变量作用域

1.1、全局变量

  • 全局变量是定义在函数外部的变量,它在程序整个运行周期内都有效。 在函数中可以访问到全局变量。
var s = "函数外的全局变量" // 在函数外用var关键字定义的变量,包内全局有效

func Hunwen() {
	fmt.Println(s)
}

func main() {
	Hunwen()
}

1.2、局部变量

  • 局部变量又分为两种:
  • 1、函数内定义的变量无法在该函数外使用
  • 2、函数内if条件判断、for循环、switch等语句内定义的变量在语句外部也无法调用
func f1() {
	var s1 = "abc"
	fmt.Println(s1)
}

func main() {
	fmt.Println(s1)    // 在函数f1中定义的变量在函数外无法被调用
}

在这里插入图片描述

  • 如果局部变量和全局变量重名,优先访问局部变量。
var s1 = "函数外的全局变量" // 在函数外用var关键字定义的变量,包内全局有效

func f1() {
	var s1 = "abc"
	fmt.Println(s1)  // abc
}
  • if条件判断、for循环、switch语句上定义变量。
func f1(a, b int) {
	fmt.Println(a, b)
	if a > b {
		Z := 10
		fmt.Println(Z)    // 在函数内的if、for、switch等语句中定义的变量,在语句外无法调用
	}
	fmt.Println(z)        // 在这个位置无法使用变量z
}

func main() {
	f1(2, 3)
}

在这里插入图片描述

2、高阶函数

  • 高阶函数分为函数作为参数和函数作为返回值两部分。
  • 函数其实也可以看作是一种类型

示例

func A() {
	fmt.Println("abc")
}

func B() int {
	return 2
}
func C(x, y int) int {
	return x + y
}

func main() {
	a := A
	fmt.Printf("%T\n", a)      // a 是 func() 类型
	b := B
	fmt.Printf("%T\n", b)      // b 是 func()int 类型
	c := C
	fmt.Printf("%T\n", c)      // c 是 func(int, int) int 类型
	}
  • 函数也可以作为参数传入
func B() int {
	return 2
}

// 函数也可以作为参数传入
func D(f func() int) {
	ret := f()
	fmt.Println(ret)
}
func main() {
	D(B)     // 返回结果2,将函数B作为参数传入,传入的函数B必须满足函数D的参数类型
}
  • 函数可以作为返回值
func f1() int {
	return 2
}

func f2(a func() int) func(int, int) int {  // 将func(int, int) int 这个类型函数作为返回值
	ret := func(x, y int) int {
		return x + y + a()
	}
	return ret
}
func main() {
	f3 := f2(f1)   // f3 接收 f2 返回的函数
	fmt.Println(f3(1, 2))   // 输出结果为5
}

3、匿名函数和闭包

3.1、匿名函数

  • 没有命名的函数

示例:

// func直接定义一个匿名函数,可以用变量接收
var f1 = func(a, b int) {
	fmt.Println(a + b)
}

func main() {
	f1(1, 2)
}
  • 一般情况下,我们是不会在全局定义匿名函数的,一般在命名函数内使用
    • 在命名的函数内,无法再声明一个命名的函数,但是匿名函数可以

示例:

func demo(a,b int){
	// 1、可以在函数内定义匿名函数,用变量接收后可以多次调用
	f1 := func (x,y int) {
		fmt.Println( x + y)
	}
	f1(1,2)
	// 2、如果只是使用一次,可以简写成立即执行函数。即在函数定义后加上()调用
	func (x string)  {
		fmt.Println(x)
	}("abc")
}

3.2 闭包

3.2.1、闭包的定义
  • 闭包指的是一个函数和与其相关的引用环境组合而成的实体,简单来说,“闭包=函数+引用环境”
  • 闭包是一个函数,这个函数包含了它外部作用域的变量,“闭包=函数+外部变量的引用”

示例1:


func adder() func(int) int {
	var x int
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var ret = adder() // 此时的ret其实就是adder返回的匿名函数,这个其实就是个闭包
	ret1 := ret(100)  // 相当于将100传参进adder内的匿名函数
	fmt.Println(ret1) // 100,打印的结果其实就是x
}
3.2.2、闭包的进阶
  • 如下演示:
    • 变量f是一个函数并且它引用了其外部作用域中的x变量,此时f就是一个闭包。 在f的生命周期内,变量x也一直有效。
    • 内部匿名函数中的参数优先在函数体内部寻找,没有则一层层往外找

进阶示例1:

func adder(x int) func(int) int {
	return func(y int) int {
		x += y
		return x
	}
}
func main() {
	var ret = adder(100) // 将100传入adder函数,此时的ret其实就是adder返回的匿名函数且内部变量x的值为adder传入的100
	ret1 := ret(200)     // 相当于将200传参进adder内的匿名函数
	fmt.Println(ret1)    // 300,打印的结果其实就是x
}

闭包进阶示例2:

func makeSuffixFunc(suffix string) func(string) string {
	return func(name string) string {
		if !strings.HasSuffix(name, suffix) {  // strings.HasSuffix()方法用作字符串后缀判断,如果参数1是以参数2为后缀的,那么就返回true
			return name + suffix
		}
		return name
	}
}

func main() {
	jpgFunc := makeSuffixFunc(".jpg")("test")
	txtFunc := makeSuffixFunc(".txt")
	fmt.Println(jpgFunc)         //test.jpg
	fmt.Println(txtFunc("test")) //test.txt
}

闭包进阶示例3:

func calc(base int) (func(int) int, func(int) int) {
	add := func(i int) int {
		base += i
		return base
	}

	sub := func(i int) int {
		base -= i
		return base
	}
	return add, sub
}

func main() {
	add, sub := calc(10)
	fmt.Println(add(1), sub(2)) //11 9
}

3.2.3、应用场景演示

应用场景演示如下:小明写了函数f1,提供出一个接口。小红写了函数f2,需要用f1去调用,但是如下示例中,由于f1接收参数与f2的类型不一致,所以无法接收,针对这种场景,就可以使用闭包

// 小明写了一个接口函数f1
func f1(f func()) {
	fmt.Println("this is f1")
	f()
}

// 小红写了功能函数f2,需要被f1调用,但是此时是无法直接作为参数传入的
func f2(x, y int) {
	fmt.Println("this is f2")
	fmt.Println(x + y)
}

// 可以定义一个函数f3,对函数f2进行包装
func f3(f func(int, int), x, y int) func() {
	tmp := func() {
		fmt.Println("this is f3")
		f(x, y) // 这里的x和y在匿名函数内部没找到,就会去外层找,通过f3参数传入
	}
	return tmp // 返回的是 func() 类型函数,满足了 f1 的参数类型
}

func main() {
	f3(f2, 1, 2) // 这样return的函数满足了f1的参数类型,且f3内部实质上只是调用了f2
	f1(f3(f2, 1, 2))
}

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

5、defer语句

5.1、了解defer语句

  • Go语言中的 defer 语句会将其后面跟随的语句进行延迟处理。
  • 在defer归属的函数即将返回时,将延迟处理的语句按defer定义的逆序进行执行 (即先被defer的语句最后被执行 且 最后被defer的语句,最先被执行)
  • 注意:由于defer语句延迟调用的特性,所以defer语句能非常方便的处理资源释放问题。比如:资源清理、文件关闭、解锁及记录时间等。

示例:

func f1(a, b, c int) {
	fmt.Println("start")
	defer fmt.Println(a)  // 最前面的defer,最后执行
	defer fmt.Println(b)
	defer fmt.Println(c)   // 最下面的defer,最先输出
	fmt.Println("end")     // 非defer的语句一定会在defer语句前执行
}

func main() {
	f1(1, 2, 3)
}

输出结果

start
end
3
2
1

5.2、defer执行时机

  • 在Go语言的函数中return语句在底层并不是原子操作,它分为给返回值赋值和RET指令两步。而defer语句执行的时机就在返回值赋值操作后,RET指令执行前。

流程图如下:

在这里插入图片描述

  • 在没有defer语句的函数中,函数 return 返回值有两个步骤,例如return x
    • 1、先将变量x的值赋值给返回值
    • 2、执行底层的RET执行,进行返回
  • 在有defer语句的函数中,defer语句会在 return 的中间步骤运行
    • 1、先将 变量x 的值赋值给 返回值
    • 2、执行defer语句
    • 3、执行RET指令

5.3、defer经典案例

  • 看代码,写结果
func f1() int {
	x := 5
	defer func() {
		x++
	}()
	return x   // 返回值时5
}

func f2() (x int) {
	defer func() {
		x++
	}()
	return 5  // 返回的结果是6。先x=5,然后执行defer语句x++,最后return 6。
}

func f3() (y int) {
	x := 5
	defer func() {
		x++
	}()
	return x   // 返回5。首先返回值 = y = x = 5,然后x++,但是返回值y依旧是5了
}
func f4() (x int) {
	defer func(x int) {
		x++
	}(x)
	return 5 // 返回的是5,defer声明的参数x,和传入的x不是同一个作用域
}

// 传入一个x的指针到匿名函数
func f5() (x int) {
	defer func(x *int) {
		fmt.Println(x)
		*x++
	}(&x)
	return 5
}

func main() {
	fmt.Println(f1())
	fmt.Println(f2())
	fmt.Println(f3())
	fmt.Println(f4())
	fmt.Println(f5())
}

5.4、defer的又一特性

  • defer语句后面跟着的函数参数是按照正常的执行顺序传入的,只是函数执行是遵守defer的规则

常见的关于defer的面试题

func calc(index string, a, b int) int {
	ret := a + b
	fmt.Println(index, a, b, ret)
	return ret
}

func main() {
	x := 1
	y := 2
	defer calc("AA", x, calc("A", x, y))  // defer calc("AA", 1, 3)
	x = 10
	defer calc("BB", x, calc("B", x, y))  // defer calc("BB", 10, 12)
	y = 20
}

返回结果

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

三、内置函数初了解

1、内置函数介绍

内置函数介绍
close主要用来关闭channel
len用来求长度,比如string、array、slice、map、channel
new用来分配内存,主要用来分配值类型,比如int、struct。返回的是指针
make用来分配内存,主要用来分配引用类型,比如chan、map、slice
append用来追加元素到数组、slice中
panic 和 recover用来做错误处理

2、panic/recover

  • Go语言中目前(Go1.12)是没有异常机制,但是使用panic/recover模式来处理错误。
  • panic可以在任何地方引发,但recover只有在defer调用的函数中有效。
  • 应用场景:例如程序引发了一个错误,导致程序奔溃,但是程序仍在运行,资源仍旧被占用,panic和recover就是为了处理这种情况
  • 注意点:
    • recover()必须搭配defer使用。
    • defer一定要在可能引发panic的语句之前定义。
    • recover()在特定的场景下可以使用,正常情况下,该panic的地方,还是应该panic,不要跳过

示例:利用 panic 来触发异常,直接退出程序

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	// 假设这里进行数据库连接
	defer func() {
		fmt.Println("释放数据库连接")
	}()
	panic("出现严重错误!!") // 连接失败,程序崩溃退出
	fmt.Println("func b")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

输出结果:运行了函数A,然后运行函数B时,触发异常后退出,函数C就不执行了

func A
释放数据库连接
panic: 出现严重错误!!

goroutine 1 [running]:
main.funcB(...)
        E:/GoProject/src/gitee.com/LTP/main.go:12
main.main()
        E:/GoProject/src/gitee.com/LTP/main.go:20 +0x66
exit status 2
  • 程序运行期间funcB中引发了panic导致程序崩溃,异常退出了。这个时候我们就可以通过recover将程序恢复回来,继续往后执行(即 recover 的作用就是尝试恢复下程序)。

示例:

func funcA() {
	fmt.Println("func A")
}

func funcB() {
	// 假设函数检测刚连接数据库出错
	defer func() {
		err := recover() // 这个recover()方法,可以拿到panic报出的错误,定义的recover()办法后,程序就会继续向下执行
		fmt.Println(err) // "出现严重错误!!", 打印的结果就是panic报出的异常
		//如果程序出出现了panic错误,可以通过recover恢复过来
		if err != nil {
			fmt.Println("recover in B")  // 这里定义尝试解决的办法代码,尝试让程序恢复
		}
		fmt.Println("释放数据库连接")
	}()
	panic("出现严重错误!!") // 程序崩溃退出
	fmt.Println("func b")
}

func funcC() {
	fmt.Println("func C")
}
func main() {
	funcA()
	funcB()
	funcC()
}

执行结果:

func A
出现严重错误!!
recover in B
释放数据库连接
func C

小试牛刀

/*
你有50枚金币,需要分配给以下几个人:Matthew,Sarah,Augustus,Heidi,Emilie,Peter,Giana,Adriano,Aaron,Elizabeth。
分配规则如下:
a. 名字中每包含1个'e'或'E'分1枚金币
b. 名字中每包含1个'i'或'I'分2枚金币
c. 名字中每包含1个'o'或'O'分3枚金币
d: 名字中每包含1个'u'或'U'分4枚金币
写一个程序,计算每个用户分到多少金币,以及最后剩余多少金币?
程序结构如下,请实现 ‘dispatchCoin’ 函数
*/

var (
	coins = 50
	users = []string{
		"Matthew", "Sarah", "Augustus", "Heidi", "Emilie", "Peter", "Giana", "Adriano", "Aaron", "Elizabeth",
	}
	distribution = make(map[string]int, len(users))
)

func main() {
	left := dispatchCoin()
	fmt.Println("剩下:", left)
}

实现方式:

func dispatchCoin() (count int) {
	count = 50
	for i := 0; i < len(users); i++ {
		for key := range scoringFormula {
			for _, v := range users[i] {
				if key == v {
					count -= int(scoringFormula[key])
					distribution[users[i]] += int(scoringFormula[key])
				}
			}
		}
	}
	fmt.Println(distribution)
	return count
}

func main() {
	left := dispatchCoin()
	fmt.Println("剩下:", left)
}

// map[Aaron:3 Adriano:5 Augustus:12 Elizabeth:4 Emilie:6 Giana:2 Heidi:5 Matthew:1 Peter:2]
// 剩下: 10
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值