目 录CONTENT

文章目录

Go学习系列11-函数

cplinux98
2022-11-19 / 0 评论 / 0 点赞 / 577 阅读 / 2,856 字 / 正在检测是否收录...

函数

函数构成了代码执行的逻辑结构,在Go语言中,函数是最基本的代码块

函数的主要目的是:将一个需要很多行代码的复杂问题,分解为一系列简单的任务来解决,其中同一个任务可以被多次执行使用

Go语言里面有3种类型的函数

  • 普通的带有名字的函数
  • 匿名函数或lambda函数
  • 方法

普通函数声明(定义)

func 函数名(形式参数列表)(返回值列表){
    函数体
    返回语句
}
  • 函数名
    • 函数名的定义和变量规则相同
    • 大写开头的函数名才可以“导出”,才可以被其他包使用
  • 形式参数列表
    • 描述了函数的参数名称和参数类型
    • 这些参数作为局部变量使用
    • 使用函数时传入的叫实参,定义函数时是形参
    • 实参是按值传递,函数接收到的是实参的副本
    • 如果实参是引用类型:切片、字典、函数、指针、通道等,使用形参时可用改变实参的值
  • 返回值列表
    • 描述了函数返回值变量名称和变量类型
    • 如果一个函数返回无名变量或者没有返回值,返回值列表的括号可以省略
    • go支持多返回值,多返回值能方便地获得函数执行后的多个返回参数,Go语言经常使用多返回值中的最后一个返回参数返回函数执行中可能发生的错误

使用函数

  • 使用时必须按照实参的声明顺序传参
  • 实参通过值传递的方式进行传递,因此函数的形参是实参的拷贝,对形参进行修改不会影响实参

返回值

同一种类型的返回值

如果返回值是同一种类型,则用括号将多个返回值类型括起来,用逗号分隔每个返回值的类型

func test2()(int, int){
    return 1, 2
}

纯类型的返回值对于代码可读性不是很友好,特别是在同类型的返回值出现时,无法区分每个返回参数的意义

带有变量名的返回值

Go语言支持对返回值进行命名,这样返回值就和参数一样拥有参数变量名和类型。

命名的返回值变量的默认值为类型的默认值,例如int为0,string为空字符串,bool为false,指针为nil等。

func test2()(a, b int) {
	a = 1
	b = 2
	return 
}
  • 命名的返回值函数体需要显式return
  • 返回值不需要初始化(在返回列表中已经初始化过了),可以直接使用

返回值的接收

可以不接收返回值

test()

可以按照位置接收返回值,但接收的变量数量要和函数返回的数量相等

x, y := test()

不想要的返回值可以使用_丢弃

x, _ := test2()

函数示例(1)

定义一个函数,输入秒数,转换成对应的天数、小时数、分钟数

package main

import (
	"fmt"
)

const (
	SecondsPerMinute = 60
	SecondsPerHour   = SecondsPerMinute * 60
	SecondsPerDay    = SecondsPerHour * 24
)

func resolveTime(seconds int) (day int, hour int, minute int) {
	day = seconds / SecondsPerDay
	hour = seconds / SecondsPerHour
	minute = seconds / SecondsPerMinute
	return
}

func main() {
	// 将返回值作为打印参数
	fmt.Println(resolveTime(1000))
	// 只获取消息喝分钟
	_, hour, minute := resolveTime(180000)
	fmt.Println(hour, minute)
	// 只获取天
	day, _, _ := resolveTime(90000)
	fmt.Println(day)
}
//0 0 16
//50 3000
//1

不同的形参方式

普通参数列表

指的是给函数传递的参数的个数是确定好的,形参个数=实参个数

普通参数定义形参时, 可以将两个同类型的形参使用一个类型定义

func hello(a, b string, c float64, d, e int){}

但这样不够清晰,是不建议这样定义的,最好是这样

func hello(a string, b string, c float64, d int, e int){}

不定参数列表(固定类型)

相当于python中的args

当我们不知道会传入几个参数时,可以使用...来设置不定长参数列表,此时“形参们”在函数体内“可以看作”是一个数组切片类型([]type),此时函数可以接收0个或多个实参

func hello(args ...int){
    for i := 0; i < len(args); i++ {
        fmt.Print(args[i])
    }
    fmt.Println("")
}

如果有定参也有不定参时,不定参要放在最后面

func hello(a int, b int, args ...int){
    
}

观察类型

func main1(a ...int) {
	fmt.Printf("%T\n", main1)
}
func main2(a []int) {
	fmt.Printf("%T\n", main2)
}

// func(...int)
// func([]int)

任意类型的可变参数

之前都是约束类型为int,如果希望传进来的实参是任意类型,可以使用interface

func hello(a string, args ...interface{}){}

示例

package main

import (
	"fmt"
)

func MyPrintf(args ...interface{}) {
	for _, arg := range args {
		switch arg.(type) {
		case int:
			fmt.Println(arg, "is an int value.")
		case string:
			fmt.Println(arg, "is a string value.")
		case int64:
			fmt.Println(arg, "is an int64 value.")
		default:
			fmt.Println(arg, "is an unknown type.")
		}
	}
}

func main() {
	var v1 int = 1
	var v2 int64 = 234
	var v3 string = "hello"
	var v4 float32 = 1.234
	MyPrintf(v1, v2, v3, v4)
}
//1 is an int value.
//234 is an int64 value.
//hello is a string value.
//1.234 is an unknown type.

使用单个实参传递给可变参数

找个意思就和python中的*args差不多,将实参数解构传入

func MyPrint2(alist ...interface{}){
    for _, a := range alist{
        fmt.Println(a)
    }
}

func main(){
    var mlist := []int{1, 3, 5, 7}
    MyPrint2(mlist...)
}

引用类型作为实参

当函数使用引用类型作为实参时,在函数内部是可以对实参进行修改的,这个使用的时候要特别注意

func ShowName2(names []string) {
	for index, name := range names {
		if name == "e" {
			names[index] = "ff"
		}
		fmt.Println("这是" + name)
	}
}

func main() {
	foods := []string{"a", "b", "c", "d", "e", "g"}
	ShowName2(foods)
	fmt.Println(foods)
}

这是a
这是b
这是c
这是d
这是e
这是g
[a b c d ff g]

函数变量

我们可以把函数赋给一个变量,然后执行变量就等于执行函数

func fire(){
    fmt.Println("file")
}
func main(){
    var f func()  // 定义为函数类型
    f = fire
    f()
}

定义函数变量

type Hi func(num string) string  // 定义一个函数类型,此时是没有函数名称的,是关键字func
func HelloPeople(num string) string{  // 根据不同需求,返回不同的值
    return num + "客人你好"
}

func HelloGod(num string) string{
    return num + "贵宾您好"
}

func SayHello(num string, hi Hi){  // 定义一个统一调用的函数
    result := hi(num)
    fmt.Println(result)
}

func main(){
    target := "God"
    // 根据判断target,最终使用同一个函数去调用不同的执行函数
    if target == "God" {
        SayHello("3", HelloGod)
    } else {
        SayHello("3", HelloPeople)
    }
}

匿名函数

匿名就是没有名字

func (num string) string{
    return num + "hello"
}

示例

package main

import "fmt"

type Hi func(num string) string // 定义一个函数类型,此时是没有函数名称的,是关键字func

func SayHello(num string, hi Hi) { // 定义一个统一调用的函数
	result := hi(num)
	fmt.Println(result)
}

func main() {
	target := "God"
	// 根据判断target,最终使用同一个函数去调用不同的执行函数
	if target == "God" {
		SayHello("3", func(num string) string {
			return num + "客人你好"
		})
	} else {
		SayHello("3", func(num string) string {
			return num + "贵宾您好"
		})
	}
}

闭包

闭包是包含自由变量的代码块,变量不在这个代码块或者全局上下文中定义,而是在定义代码块的环境中定义,要执行的代码块(因为自由变量包含在代码块中,所以这些自由变量及它们引用的对象没有被释放)为自由变量提供绑定的环境。

简单理解闭包

  • 什么时候会出现闭包
    • 一个函数返回了另一个函数
    • 返回函数外部定义了变量,且在返回函数内部使用了该变量
  • 闭包解决了什么问题
    • 实现了变量的缓存
  • 闭包会导致什么问题
    • 因为会缓存变量,对内存消耗比较大,不能滥用

代码示例

type Discount func() float64                           // 折扣
type CheckSum func(name string, price float64) float64 // 计算总价

func PayOrder(discount Discount) CheckSum {
	var total float64  // 外部函数的变量
	return func(name string, price float64) float64 {  // 被返回的函数
		fmt.Println("菜品:" + name + "单价: " + strconv.FormatFloat(price, 'f', -1, 64))

		// 往总价里面加单品的价格
		total = total + price  // 在内部函数的变量引用了外部函数的变量

		// 处理折扣
		if discount == nil {
			return total
		}
		return total * discount()
	}
}

func main() {
	f := PayOrder(func() float64 {
		return 0.8
	}) // 返回一个CheckSum 函数,所以f是可以接收参数的
	
	result := f("红烧肉", 88)
	fmt.Println(result)
	result = f("清蒸鱼", 98)
	fmt.Println(result)
	result = f("溜大虾", 98)
	fmt.Println(result)
	result = f("蒸螃蟹", 98)
	fmt.Println(result)
	result = f("蒜蓉粉丝扇贝", 98)
	fmt.Println(result)
}

延迟调用

defer 关键词可以实现延迟调用,

defer 关键点

  • 一个函数内出现多个defer时,最后的defer最先执行(压栈,后进先出)
  • 典型的应用场景是释放资源,关闭文件读取,释放链接等
  • defer后可以接func,func内部如果发生panic,会把panic暂时搁置,其他defer执行完成之后再来执行这个panic
  • defer后接的执行语句,内部相关变量在注册defer时,就已经被copy或计算了
func f() {
	i := 0
	defer fmt.Println(i)
	i++
}

func main() {
	f()  // 0
}

panic

package main

import "fmt"

func main() {
	foods := []string{"a", "b"}
	fmt.Println(foods[2])
}

运行后,会出现下面的错误提示

panic: runtime error: index out of range [2] with length 2
# pinic后面接的是哪个包发出的错误提示
goroutine 1 [running]:  # 哪个编号的goroutine
main.main()  # 哪个函数
        C:/Users/49273/Documents/Study/go-study/project/宕机.go:7 +0x1b  # 具体报错文件和内容位置

我们可以根据报错内容,来找到问题所在,解决问题。

当然也可以单独调用panic函数,就像python中的raise一样

panic("问题发生")

recover

recover可以恢复panic,就像python里面的try 语句

如果在defer语句中调用了recover,并且定义该defer语句的函数发生了panic,那么recover会使程序从painc中恢复,并返回panic信息,导致panic异常的函数不会继续运行,但是能正常返回。若在未发生panic时调用recover则recover会返回nil。如果发生了panic,但没有recover,那么程序会终止运行。

package main

import "fmt"

func start() {
	fmt.Println("程序开始运行")
}

func taste() {
	foods := []string{"红烧肉", "清蒸鱼", "砒霜", "溜达鸡", "鲍鱼粥"}
	defer func() {
		if err := recover(); err != nil {
			fmt.Println(err)
		}
		fmt.Println("defer 调用结束")
	}()
	// 开始试吃
	for index, food := range foods {
		if food == "砒霜" {
			panicString := fmt.Sprintf("第%d道菜有毒!", index)
			panic(panicString)
		}
	}
}

func end() {
	fmt.Println("程序结束运行")
}

func main() {
	start()
	taste()
	end()
}
0

评论区