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