函数

golang函数简介

函数是go语言中的一级公民,我们把所有的功能单元都定义在函数中,可以重复使用。函数包含函数的名称,参数列表和返回值类型,这些构成了函数的签名。

golang中函数的特性

  • go语言中有3种函数:普通函数,匿名函数,方法(定义在struct上的函数)。
  • go语言中不允许函数重载(overload),也就是说不允许函数同名。
  • go语言中的函数不能嵌套函数,但可以嵌套匿名函数。
  • 函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
  • 函数可以作为参数传递给另一个函数。
  • 函数的返回值可以是一个函数。
  • 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。(值传递)
  • 函数参数可以没有名称。

golang中函数的定义和调用

函数在使用之前必须先定义,可以调用函数来完成某个任务。函数可以重复使用,从而达到代码重用。

语法

func function_name([parameter list])(return_types){
    //函数体
}
  • func:声明函数
  • function_name:函数名
  • [parameter list]:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给函数,这个值被称为实际参数。参数列表指定的是参数类型,顺序及参数个数。参数是可选的,也就是说函数也可以不包含参数。
  • return_types:函数返回值的类型。
  • 函数体:函数的逻辑代码部分
//定义一个两数之和
func sum(a int,b int)(res int){
    res=a+b
    return res
}

golang函数的返回值

函数可以有0个或多个返回值,返回值需要指定数据类型,返回值通过return关键字来指定。

return可以有参数,也可以没有参数,这些返回值可以有名称,也可以没有名称。go中的函数可以有多个返回值。

  • return关键字中指定了参数时,返回值可以不用名称。如果return省略参数,则返回值部分必须带名称。
  • 当返回值有名称时,必须使用括号包围,逗号分隔,即使只有一个返回值。
  • 但即使返回值命名了,return中也可以强制指定其他返回值名称,也就是说return的优先级更高。
  • 命名的返回值是预先声明好的,在函数内部可以直接使用,无需再次声明。命名返回值的名称不能和函数参数名称相同,否则报错提示变量重复声明。
  • return中可以有表达式,但不能出现赋值表达式,这其他语言可能有所不同。例如可以:return a+b,但是不能:return a=b+c
package main

import "fmt"

//没有返回值
func foo1() {
	print("没有参数,也没有返回值!\n")
}

//有参数,没有返回值
func foo2(name string) {
	print("有一个参数:")
	fmt.Printf("name: %v\n", name)
}

//有参数,有返回值 且给返回值命名了
func foo3(name string, age int) (name2 string, age2 int) {
	name2 = name
	age2 = age
	return name2, age2
	// return    如果return不指定返回值,则默认返回上面定义的返回值
}

// 有参数,有返回值,但没有给返回值命名 这个时候就必须需要return来指定返回值
func foo4(name string, age int) (string, int) {
	return name, age

}

func main() {

	foo1()
	foo2("Tom")
	name2, age2 := foo3("Tom", 18)
	fmt.Printf("name2: %v\n", name2)
	fmt.Printf("age2: %v\n", age2)
	name, _ := foo4("Tom", 18)  //丢弃age
	fmt.Printf("name: %v\n", name)

}

运行结果

没有参数,也没有返回值!
有一个参数:name: Tom
name2: Tom
age2: 18
name: Tom

tips:

  • go中经常会使用其中一个返回值作为函数是否执行成功,是否有错误信息的判断条件。例如return value,exists;return value,ok(map中就是这样使用的);return value,err等
  • 当函数的返回值过多时,例如有4个以上的返回值,应该将这些返回值收集到容器中,然后以返回容器的方式去返回。
  • 当函数有多个返回值时,如果其中某个或几个返回值不想使用,可以通过下划线_来丢弃这些返回值。

golang函数的参数

go语言函数可以有0个或多个参数,参数需要指定数据类型。

声明函数时的参数列表叫做形参,调用时传递的参数叫做实参。

go语言是通过传值的方式传参的,意味着传递给函数的是拷贝后的副本,所以函数内部访问,修改的也是这个副本。(但是map,slice,interface,channel这些数据类型本身就是指针类型,所以就算是拷贝传值也是拷贝的指针,拷贝后的参数任然指向底层数据结构,所以修改它们可能会影响外部数据结构的值。)

go语言可以使用变长参数,有时候不能确定参数的个数时,可以使用变长参数,可以在函数定义语句的参数部分使用args…type的方式。这时会将…代表的参数全部保存到一个名为args的slice中,并且这些参数的数据类型都是type。

package main

import "fmt"

func test(a int) {
	a = 200
	fmt.Printf("里面的a: %v\n", a)
}

//两数之和
func sum(a int, b int) (c int) {
	c = a + b
	return
}

//...接收无数个参数
func foo(name string, age int, args ...string) {
	fmt.Printf("name: %v\n", name)
	fmt.Printf("age: %v\n", age)
	for _, v := range args {
		fmt.Printf("v: %v\n", v)
	}
}
func main() {

	a := 100
	test(a)
	fmt.Printf("外面的a=%v\n", a)

	res := sum(1, 2)
	fmt.Printf("res: %v\n", res)
	foo("Tom", 18, "Hello", "My", "name", "is", "Tom")

}

运行结果

里面的a: 200
外面的a=100
res: 3
name: Tom
age: 18
v: Hello
v: My
v: name
v: is
v: Tom

golang函数类型与函数变量

可以使用type关键字来定义一个函数类型,语法如下:

type fun func(par_type1,par_type2...) res_type1...
  • fun:自己定义的函数类型名
  • par_type1,par_type2…:表示各个参数的类型
  • res_type1…:表示各个返回值的类型
package main

import "fmt"

func sum(a int, b int) (c int) {
	c = a + b
	return
}

func main() {

	//自己声明函数的类型,然后再将函数赋值给一个变量
	type my_func func(int, int) int
	var my_sum my_func
	my_sum = sum
	res := my_sum(1, 2)
	fmt.Printf("res: %v\n", res)

	//直接通过短变量的形式将一个函数赋值给一个变量
	c := sum
	res2 := c(1, 2)
	fmt.Printf("res2: %v\n", res2)

}

运行结果

res: 3
res2: 3

高阶函数

go语言的函数,可以作为函数的参数,传递给另外一个函数,同时也可以作为函数的返回值返回。

函数作为参数和返回值

package main

import "fmt"

func sum(a int, b int) (c int) {
	c = a + b
	return
}

func sub(a int, b int) (c int) {
	c = a - b
	return
}

func test(a int, b int, f func(int, int) int) {
	res := f(a, b)
	fmt.Printf("%v+%v=res: %v\n", a, b, res)
}

func cal(flag string) func(int, int) int {
	switch flag {
	case "+":
		return sum
	case "-":
		return sub
	default:
		return sum
	}

}

func main() {

	//函数作为参数传给函数
	test(10, 20, sum)

	//函数作为返回值
	f_sum := cal("+")
	res := f_sum(1, 2)
	fmt.Printf("res: %v\n", res)

	f_sub := cal("-")
	res2 := f_sub(1, 2)
	fmt.Printf("res2: %v\n", res2)
}

运行结果

10+20=res: 30
res: 3
res2: -1

匿名函数

go语言函数不能嵌套,但是在函数内部可以定义匿名函数,实现一些简单的功能。所谓匿名函数,就是没有名称的函数。

语法

func ([参数列表])([返回值列表]){
    //函数体
}
package main

import "fmt"

func main() {

	//将匿名函数赋值给一个变量
	say_hello := func(name string, age int) {
		fmt.Printf("My name is  %v,And i',m %v old.\n", name, age)
	}
	say_hello("Tom", 18)

	//直接调用匿名函数
	func(words string) {
		fmt.Printf("words: %v\n", words)
	}("Hi,我是一个匿名函数!")

}

运行结果

My name is  Tom,And i',m 18 old.
words: Hi,我是一个匿名函数!

闭包

闭包就是延伸了函数作用于的函数,使得函数可以访问到函数体外的非全局变量。

package main

import "fmt"

//求平均值的闭包
func make_avger() func(int) float64 {

	s := make([]int, 0) 
    //对于下面的匿名函数来说,切片s就相当于函数体外的非全局变量
    //并且调用make_avager方法后,在全局中是访问不到切片s的
  

	return func(num int) float64 {
		s = append(s, num)
		sum := 0
		for _, v := range s {
			sum += v
		}
		return float64(sum / len(s))
	}

}
func main() {

	avg := make_avger()
	for i := 0; i < 10; i++ {
		fmt.Printf("%.2f\n", avg(i))

	}
}

运行结果

0.00
0.00
1.00
1.00
2.00
2.00
3.00
3.00
4.00
4.00

递归

函数内部调用函数自己的函数称为递归函数(同闭包一样,递归不是go语言特有的,而是go语言具备实现递归的条件)。

package main

//使用递归实现斐波那契数列
func fib(n int) int {
	if n == 1 || n == 2 {
		return 1
	} else {
		return fib(n-1) + fib(n-2)
	}
}

func main() {
	res := fib(10)
	print(res)
}

运行结果

102334155

同步更新于个人博客系统:golang学习笔记系列之函数