golang 函数

go语言中的函数特性

  • go语言中有3种函数:普通函数、匿名函数(没有名称的函数)、方法(定义在struct上 的函数)。
  • go语言中不允许函数重载(overload),也就是说不允许函数同名。
  • go语言中的函数不能嵌套函数,但可以嵌套匿名函数。
  • 函数是一个值,可以将函数赋值给变量,使得这个变量也成为函数。
  • 函数可以作为参数传递给另一个函数。
  • 函数的返回值可以是一个函数。
  • 函数调用的时候,如果有参数传递给函数,则先拷贝参数的副本,再将副本传递给函数。
  • 函数参数可以没有名称。
  • 基本数据类型和数组默认都是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。

函数的基本语法

func 函数名(形参列表)(返回值类型列表){
执行语句..
return + 返回值列表
}

  • 函数名:
    遵循标识符命名规范,驼峰命名,见名知意,首字母不能是数字。
    当首字母大写时,该函数可以被本包文件和其它包文件使用。
    当首学母小写只能被本包文件使用,其它包文件不能使用。
  • 形参列表:
    形参个数可以是一个参数,可以是n个参数,可以是0个参数。形参的作用为接收外来的数据。
  • 返回值类型列表:
    返回值个数为0:不用写返回值类型列表。
    返回值个数为1:括号内写一个返回类型,如(int),括号也可以不写直接写返回类型,如 int。
    返回值个数为多个:括号内写每个返回值的类型,以逗号隔开,如(int32,int8),接收也必须接收多个返回值,如果不需要接收可以_忽略。
package main

import "fmt"

func test(n1 int, n2 int) int { //定义自定义函数test,指定需要传入2个参数n1,n2(形参),参数类型为int,返回的值类型也为int
	var n3 int
	n3 = n1 + n2
	return n3 //定义返回n3
}

func main() {
	n4 := test(4, 5) //调用自定义函数test,传入第一个参数为4,第二个参数为5,(4,5为实参),定义n4接收函数test的返回值
	fmt.Printf("n4的值为:%v\n", n4)
	n5 := 11
	n6 := 50
	n7 := test(n5, n6)
	fmt.Printf("n7的值为:%v\n", n7) //将两个已经赋值的变量传入自定义函数test
}

执行结果

n4的值为:9
n7的值为:61

可变参数

使用...表示参数个数是可变数量的,函数内部处理可变参数的时候,将可变参数当做切片来处理。

package main

import "fmt"

func test2(num ...int) {
	for key, val := range num { //遍历可变参数
		fmt.Printf("num[%v]的值为:%v\n", key, val)
	}
}

func main() {
	fmt.Println("不传参数调用test2函数")
	test2()
	fmt.Println("传一个参数调用test2函数")
	test2(1)
	fmt.Println("传多个参数调用test2函数")
	test2(2, 3, 4, 5, 6)
}

执行结果

不传参数调用test2函数
传一个参数调用test2函数
num[0]的值为:1        
传多个参数调用test2函数
num[0]的值为:2        
num[1]的值为:3        
num[2]的值为:4        
num[3]的值为:5        
num[4]的值为:6 

函数的内存

当main函数中的n1,n2执行test1函数重新赋值时,只是在test1函数的内存空间中改变了n1,n2的值,跟main函数中的n1,n2的值无关,所以执行完test1函数时,n1,n2的值没有发生改变。

package main

import "fmt"

func test1(n1 int, n2 int) {
	n1 = 10 //重新给n1赋值
	n2 = 20 //重新给n2赋值
	fmt.Printf("test1函数中,n1的内存地址为:%p,n2的内存地址为%p\n", &n1, &n2)
}

func main() {
	var n1 int = 5
	var n2 int = 10
	fmt.Printf("n1的值为%v,n2的值为%v\n", n1, n2)
	fmt.Printf("n1的内存地址为:%p,n2的内存地址为%p\n", &n1, &n2)
	test1(n1, n2)
	fmt.Printf("执行test函数后,n1的值为%v,n2的值为%v\n", n1, n2)
}

执行结果

n1的值为5,n2的值为10
n1的内存地址为:0xc00001c0b8,n2的内存地址为0xc00001c0d0
test1函数中,n1的内存地址为:0xc00001c0d8,n2的内存地址为0xc00001c100
执行test函数后,n1的值为5,n2的值为10 

通过指针修改函数中变量的值。

main函数调用test1函数时,直接将变量的内存地址传入到test1函数,test1函数中,直接按照传入的地址指向的值进行修改,此时main函数中的n1,n2的地址还是之前的内存地址,在test1函数执行时已将内存地址指向的值修改了,所有重新输出的n1,n2的值为test1函数中的值。

package main

import "fmt"

func test1(n1 *int, n2 *int) {
	*n1 = 10 //将n1地址对应的值进行赋值
	*n2 = 20 //将n2地址对应的值进行赋值
	fmt.Printf("test1函数中,n1的值为:%p,n2的值为%p\n", n1, n2)
}

func main() {
	var n1 int = 5
	var n2 int = 10
	fmt.Printf("n1的值为%v,n2的值为%v\n", n1, n2)
	fmt.Printf("n1的内存地址为:%p,n2的内存地址为%p\n", &n1, &n2)
	test1(&n1, &n2) //将n1,n2的内存地址传入test1函数
	fmt.Printf("执行test函数后,n1的值为%v,n2的值为%v\n", n1, n2)
}

执行结果

n1的值为5,n2的值为10
n1的内存地址为:0xc00001c0b8,n2的内存地址为0xc00001c0d0
test1函数中,n1的值为:0xc00001c0b8,n2的值为0xc00001c0d0
执行test函数后,n1的值为10,n2的值为20  

函数也是一种数据类型,可以赋值给一个变量,则该变量就是一个函数类型的变量了。通过该变量可以对函数调用。

package main

import "fmt"

func test(num1 int) {
	fmt.Println(num1)
}

func main() {
	f := test //将test函数赋值给f
	fmt.Printf("f的数据类型为:%T,test的数据类型为%T\n", f, test)
	f(8) //通过f调用test函数
}

执行结果

f的数据类型为:func(int),test的数据类型为func(int)
8

函数可以作为形参,并且调用

package main

import "fmt"

func test(num1 int) {
	fmt.Println(num1)
}

func test2(num2 int, fun func(int)) { //传入两个参数为,第一个为int类型,第二个为函数类型
	fmt.Printf("num2的值为%v,fun的值为%v\n", num2, fun)
}

func main() {
	f := test //将test函数赋值给f
	fmt.Printf("f的数据类型为:%T,test的数据类型为%T\n", f, test)
	test2(10, test) //直接传入函数名称
	test2(11, f)    //传入函数类型的变量
}

执行结果

f的数据类型为:func(int),test的数据类型为func(int)
num2的值为10,fun的值为0x60b120
num2的值为11,fun的值为0x60b120

type定义函数类型

type自定义数据类型

基本语法: type 自定义数据类型名(别名) 数据类型
例如:type aint int,则aint类型等价于int类型。
var num1 aint 等于 var num1 int

package main

import "fmt"

func main() {
	type aint int //将int类型取别名为aint
	var num1 aint = 10
	var num2 int = 20
	fmt.Printf("num1的值为:%v,num1的类型为:%T\n", num1, num1) //虽然是别名,但是在编译的时候还是认为不是一种数据类型
	sum := num2 + int(num1)                            //需要将num1类型aint转成int才能操作
	fmt.Printf("num1+num2的值为:%v", sum)
}

执行结果

num1的值为:10,num1的类型为:main.aint
num1+num2的值为:30

type自定义函数类型

基本语法: type 自定义函数名(别名) 函数类型
例如:type afunc func(int),则afunc类型等价于func(int)类型。

package main

import "fmt"

func test(num1 int) {
	fmt.Println(num1)
}

type afunc func(int) //定义函数别名

func test2(num2 int, fun afunc) { //传入两个参数为,第一个为int类型,第二个为函数类型
	fmt.Printf("num2的值为%v,fun的值为%v\n", num2, fun)
}

func main() {
	f := test //将test函数赋值给f
	fmt.Printf("f的数据类型为:%T,test的数据类型为%T,test2的数据类型为%T\n", f, test, test2)
	test2(10, test) //直接传入函数名称
	test2(11, f)    //传入函数类型的变量
}

执行结果

f的数据类型为:func(int),test的数据类型为func(int),test2的数据类型为func(int, mai
n.afunc)
num2的值为10,fun的值为0xb6b120
num2的值为11,fun的值为0xb6b120

对函数返回值命名

return返回值的类型和函数定义的返回值类型对应,顺序不能变

package main

import "fmt"

func test1(num1 int, str1 string) (int, string) { //返回的第一个值为int类型,第二个值的类型为string类型
	n1 := num1
	s1 := str1
	return n1, s1 //返回的第一个值必须也是int类型,第二个值也得是string类型
}

func main() {
	a, b := test1(10, "hello")
	fmt.Printf("a的值为:%v,b的值为:%v\n", a, b)
}

执行结果

a的值为:10,b的值为:hello

对函数返回值命名,return顺序随意,顺序不用对应函数返回值

package main

import "fmt"

func test1(num1 int, str1 string) (a int, b string) { //对函数的返回值命名了,所以return无需指定
	a = num1
	b = str1
	return 
}

func main() {
	a, b := test1(10, "hello")
	fmt.Printf("a的值为:%v,b的值为:%v\n", a, b)
}

执行结果

a的值为:10,b的值为:hello

init函数

每一个源文件都可以包含一个 init 函数,该函数会在 main 函数执行前,被 Go 运行框架调用,也就是说 init 会在 main 函数前被调用。

注意事项与细节:
如果一个文件同时包含全局变量定义,init 函数和 main 函数,则执行的流程全局变量定义->init 函数->main 函数

package main

import "fmt"

var n int = test10()

func test10() int {
	fmt.Println("执行test10函数")
	return 100
}

func main() {
	fmt.Println("执行main函数")
	fmt.Println(n)
}

func init() {
	fmt.Println("执行init函数")
}

执行结果

执行test10函数
执行init函数
执行main函数
100

注意:如果两个文件中都含有 变量定义,init 函数时,从引入的import开始,按照之前的顺序:变量定义 -> init函数 -> (进入main包)变量定义 -> init函数 -> main函数...

匿名函数

golang支持匿名函数,如果某些函数仅想在特定位置使用一次,那么可以考虑使用匿名函数,匿名函数也可以实现多次调用。

  • 可以在函数内部定义匿名函数,但是不能定义命名函数。
  • 匿名函数可以直接调用,保存到变量,作为参数,作为返回值。
  • 除了没有名字之外,匿名函数和命名函数没有什么不同。
  • 将局部变量赋值匿名函数,匿名函数可以在函数内部多次调用。
  • 将全局变量赋值匿名函数,匿名函数可以在程序中多次调用。
package main

import "fmt"

var res03 = func(n1 int, n2 int) int { //定义匿名函数,赋值给全局变量,调用这个全局变量等价于调用这个匿名函数
	return n1 + n2
}

func main() {
	res01 := func(n1 int, n2 int) int { //定义匿名函数,同时调用
		return n1 + n2
	}(10, 20) //将n1=10,n2=20传入匿名函数
	fmt.Printf("res01的值为:%v\n", res01)

	res02 := func(n1 int, n2 int) int { //定义匿名函数赋值给res02,res02等价于这个匿名函数
		return n1 + n2
	}
	sum1 := res02(30, 40) //调用匿名函数
	num1 := 100
	num2 := 140
	sum2 := res02(num1, num2) //调用匿名函数
	fmt.Printf("sum1的值为:%v\n", sum1)
	fmt.Printf("sum2的值为:%v\n", sum2)

	sum3 := res03(200, 210) //调用匿名函数
	fmt.Printf("sum3的值为:%v\n", sum3)
}

执行结果

res01的值为:30
sum1的值为:70 
sum2的值为:240
sum3的值为:410

闭包

闭包可以理解成定义在一个函数内部的函数,闭包指的是一个函数和与其相关的引用环境组合而成的实体。简单来说,闭包=函数+引用环境
闭包的特点:

  • 返回的是一个匿名函数,但是这个匿名函数引用到函数外的变量/参数 ,因此这个匿名函数就和变量/参数形成一个整体,构成闭包。
  • 闭包中使用的变量/参数会一直保存在内存中,所以会一直使用---》意味着闭包不可滥用(对内存消耗大)
package main

import "fmt"

func get() func(int) int { //get函数返回一个函数类型,返回的函数类型的形参为int,返回的函数返回一个int类型
	var n1 int = 0
	a := func(n2 int) int {
		n1 += n2  //在匿名函数中调用get函数中的局部变量n1
		return n1 //匿名返回的返回,int类型
	}
	return a //get函数的返回,get函数里边嵌套一个匿名函数,返回里边的函数就形成了闭包。
}

func main() {
	f := get()         //将get函数赋值给f
	fmt.Println(f(10)) //f(n1)等于调用了get函数中的匿名函数,并给匿名函数传了n1,n1=10
	fmt.Println(f(20)) //因为变量n1常驻内存,每一次调用都会修改n1的值,使其加n2。n1=10+20=30
	fmt.Println(f(30)) //n1=30+30=60
}

执行结果
n1只在第一次调用初始化了(var n1 int = 0),后面的调用中n1都是上一次n1+n2的值。

10
30
60
posted @ 2023-02-21 11:17  XIN-0808  阅读(34)  评论(0编辑  收藏  举报