学习go语言编程之函数

发布时间 2023-08-12 23:49:33作者: nuccch

函数定义

函数的基本组成:关键字func,函数名,参数列表,返回值,函数体,返回语句。
示例如下:

func Add(a int, b int) (ret int, err error)  {
	if a < 0 || b < 0 {
		err = errors.New("should be non-negative numbers")
		return
	}
	return a + b, nil // 支持多重返回值
}

如果参数列表中若干个相邻的参数类型相同,可以省略前面参数的类型声明:

// 参数列表中a,b的类型相同,省略了a参数的类型声明
func Add(a, b int) (ret int, err error)  {
	// ...
}

如果返回值列表中多个返回值的类型相同,也可以使用同样的方式合并。

如果函数返回值只有一个,不用声明返回值变量名:

// 函数只有一个返回值,不用声明返回值变量名
func Add2(a, b int) int {
	// ...
}

函数调用

在调用函数前,需要导入函数所在的包:

import "mymath"// 假设Add被放在一个叫mymath的包中
c := mymath.Add(1, 2)

注意: Golang中函数名字的首字母大小写不仅仅是风格,更直接体现了该函数的可见性。
规则:小写字母开头的函数只在本包内可见,大写字母开头的函数才能被其他包使用。这个规则也适用于类型和变量的可见性。

不定参数

不定参数是指函数的参数个数为不定数量。

不定参数类型

将函数定义为接收不定参数类型:

func myFunc(args ...int) {
	for _, arg := range args {
		fmt.Print(arg, " ")
	}
	fmt.Println()
}

调用:

// 传递给函数的参数个数不固定
myFunc(1)
myFunc(1, 2)

形如...type格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数。

不定参数的传递

假设有一个变参函数myFun2

func myFun2(args ...int) {
	// ...
}

如下示例展示了如何向其传递变参:

func myFunc(args ...int) {
    // 按原样传递
	myFun2(args...)
    // 传递参数片段
	myFun2(args[1:]...)
}

任意类型的不定参数

如果希望传递任意类型的参数,可以指定类型为interface{}
示例:

// 不定参数类型
func myPrintf(args ...interface{})  {
	for _, arg := range args {
		switch arg.(type) {
		case int:
			fmt.Println(arg, "is an integer")
		case string:
			fmt.Println(arg, "is a string")
		case int64:
			fmt.Println(arg, "is a int64")
		default:
			fmt.Println(arg, "is an unknown type")
		}
	}
}

func main() {
	var v1 int = 1
	var v2 int64 = 2
	var v3 string = "hello"
	var v4 float32 = 1.23

    // 调用不定参数类型函数
	myPrintf(v1, v2, v3, v4)
}

输出:

1 is an integer
2 is a int64
hello is a string
1.23 is an unknown type

多返回值

Golang函数或者成员的方法可以有多个返回值,这个特性能够使我们写出比其他语言更优雅、更简洁的代码。
比如File.Read()函数就可以同时返回读取的字节数和错误信息,如果读取文件成功,则返回值中的n为读取的字节数,err为nil,否则err为具体的出错信息:

func (file *File) Read(b []byte) (n int, err Error)

还可以给返回值命名,就像函数的输入参数一样。
返回值被命名之后,它们的值在函数开始的时候被自动初始化为空。
在函数中执行不带任何参数的return语句时,会返回对应的返回值变量的值。

Golang并不需要强制命名返回值,但是命名后的返回值可以让代码更清晰,可读性更强,同时也可以用于文档。

如果调用方调用了一个具有多返回值的方法,但是却不想关心其中的某个返回值,可以简单地用一个下划线“_”来跳过这个返回值。
比如下面的代码表示调用者在读文件的时候不想关心Read()函数返回的错误码:

n, _ := f.Read(buf)

匿名函数和闭包

匿名函数是指不需要定义函数名的一种函数实现方式。

匿名函数

在Golang中,函数可以像普通变量一样被传递或使用,可以随时在代码里定义匿名函数。

匿名函数由一个不带函数名的函数声明和函数体组成,如下所示:

func(a, b int, z float64) bool { 
    return a*b <int(z) 
}

匿名函数可以直接赋值给一个变量或者直接执行:

// 定义匿名函数并赋值给变量
f := func(x, y int) int {
    return x + y
}

// 调用函数
f(1, 2)

// 匿名函数的花括号后面直接跟参数列表表示调用匿名函数
func(x, y int) int {
    return x + y
}(1,2)

闭包

Golang的匿名函数是一个闭包。
Golang中的闭包同样也会引用到函数外的变量,闭包的实现确保只要闭包还被使用,那么被闭包引用的变量会一直存在。

// 闭包
var j int = 5
a := func()  {
    var i int = 10
    fmt.Printf("i, j: %d, %d\n", i, j)
}
a()
j *= 2
a() // 在这里还会调用闭包函数,被它引用的外部变量j一直存在,所以其只会在两次闭包调用时不同

输出:

i, j: 10, 5   # 第一次调用闭包函数时,变量j的值未变化
i, j: 10, 10  # 第二次调用闭包函数时,变量j的值已经发生变化