这是我参与「第三届青训营 -后端场」笔记创作活动的第5篇笔记。
一、函数的声明与使用
说明
函数是执行特定任务的代码块。
- go语言至少有一个main函数
语法格式:
func funcName(parametername type1, parametername type2) (output1 type1, output2 type2) {
//这里是处理逻辑代码
//返回多个值
return value1, value2
}
- func:函数由 func 开始声明
- funcName:函数名称,函数名和参数列表一起构成了函数签名。
- parametername type:参数列表,参数就像一个占位符,当函数被调用时,你可以将值传递给参数,这个值被称为实际参数。参数列表指定的是参数类型、顺序、及参数个数。参数是可选的,也就是说函数也可以不包含参数。
- output1 type1, output2 type2:返回类型,函数返回一列值。return_types 是该列值的数据类型。有些功能不需要返回值,这种情况下 return_types 不是必须的。
- 上面返回值声明了两个变量output1和output2,如果你不想声明也可以,直接就两个类型。
- 如果只有一个返回值且不声明返回值变量,那么你可以省略包括返回值的括号(即一个返回值可以不声明返回类型)
- 函数体:函数定义的代码集合。
使用
/**
一、函数的定义
*/
func test01(str string) string { //若是有return,那么就一定要有返回类型
return str
}
// 取得一个最大值
func max(num1 int, num2 int) int {
if num1 > num2 {
return num1
}
return num2
}
func Test_11_01(t *testing.T) {
fmt.Println(test01("123")) //"123"
fmt.Println(max(1,5)) //5
}
二、函数的参数(可变参与参数传递)
可变参
/**
二、函数可变参、参数传递
*/
//1、可变参函数定义
func test2(arg ...int) {
//_ , i:这种写法可以忽略掉index索引位置
for _ , value := range arg {
fmt.Print(value, " ")
}
}
func Test_11_02(t *testing.T) {
//不允许如:nums := [2]int{1,2}
//只允许一个接着一个逗号
test2(1,2,3) //1 2 3
}
参数传递:包含值传递、引用传递
//2、参数传递
//2.1、参数传递(值传递)。
//go语言中可以这样定义函数
test3 := func(x float64) float64 {
return x
}
fmt.Println(test3(5.0)) //5
//2.2、引用传递:整型变量也可以传递过去地址
test4 := func(a *int) int {
fmt.Println("函数中的a地址:", a) //函数中的a地址: 0xc00000a360
*a += 1 //修改a的值
return *a
}
var a = 10
fmt.Println("&a: ", &a) //&a: 0xc00000a360
//调用函数时使用&变量,表示传递地址
fmt.Println(test4(&a)) //11
fmt.Println(a) //11
引用传递传地址好处:
1、传指针使得多个函数能操作同一个对象。
2、传指针比较轻量级 (8bytes) ,只是传内存地址,我们可以用指针传递体积大的结构体。如果用参数值传递的话, 在每次copy上面就会花费相对较多的系统开销(内存和时间)。所以当你要传递大的结构体的时候,用指针是一个明智的选择。
3、Go语言中slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(注:若函数需改变slice的长度,则仍需要取地址传递指针)
三、函数的返回值与空白标识符
示例:
/**
三、函数的返回值
*/
//1、返回多个值
func test3(A, B int) (add int, Multiplied int) { //多个返回值要用()包裹
add = A + B
Multiplied = A * B
return add, Multiplied
}
func Test_11_03(t *testing.T) {
//空白标识符
add,_ := test3(1,2 )
fmt.Println(add) //3
}
知识点:
①一个函数可以没有返回值,也可以有一个返回值,也可以有返回多个值。
②空白标识符:_是Go中的空白标识符。它可以代替任何类型的任何值。让我们看看这个空白标识符的用法。
比如rectProps函数返回的结果是面积和周长,如果我们只要面积,不要周长,就可以使用空白标识符。
四、函数变量作用域
作用域:变量可以使用的范围。
局部变量:一个函数内部定义的变量,就叫做局部变量。变量在哪里定义,就只能在哪个范围使用,超出这个范围,我们认为变量就被销毁了。
全局变量:一个函数外部定义的变量,就叫做全局变量。所有的函数都可以使用,而且共享这一份数据
五、认识init与main方法
参考文章:函数
区别:前者在一个包中可多次出现,后者只能出现一次
- init 函数可在package main中,可在其他package中,可在同一个package中出现多次。
- main 函数只能在package main中。
共同点:两个函数在定义时不能有任何的参数和返回值。
建议:用户在一个package中每个文件只写一个init函数。
一旦在main方法中导入带有init的包,那么在编译时就会都导入进来,接着在运行时根据导入的顺序进行初始化,等到所有的包都加载完毕才会对main包中的init进行初始化,最后执行main()方法:
若是多个包导入同一个包,最终该包也只会导入一次,其中的init也只会执行一次!
测试:
lib1.go
package lib1
import (
"fmt"
_ "go-basiclearn/src/main/lib2" //匿名导入,因为下面没有调用该包中的方法,所以采用匿名
)
func init() {
fmt.Println("lib1")
}
lib2.go
package lib2
import "fmt"
func init() {
fmt.Println("lib2")
}
libmain.go
package main
import (
"fmt"
_ "go-basiclearn/src/main/lib1"
_ "go-basiclearn/src/main/lib2" //二次导入,其实早lib2早点lib1中就已经导入执行了!
)
func init() {
fmt.Println("libmain")
}
func main() {
fmt.Println("libmain.go main...")
}
五、defer调用顺序
介绍
描述:一个函数在执行最后结束之前触发的一种机制。
应用:关闭文件。
特点:①一个函数中允许多个defer。②defer本质是压栈的形式,出来的应该是后入栈的
实战:根据多种情况来展示
例子1:函数运行结束时执行
package main
import "fmt"
func main() {
defer fmt.Println("defer1")
fmt.Println("1")
fmt.Println("2")
}
例子2:多个defer,压栈过程,在下面的先出栈执行
package main
import "fmt"
func main() {
defer fmt.Println("defer1")
defer fmt.Println("defer2")
fmt.Println("1")
fmt.Println("2")
}
例子3:defer 调用多个函数
package main
import "fmt"
func main() {
defer func1()
defer func2()
defer func3()
}
func func3() {
fmt.Println(3)
}
func func2() {
fmt.Println(2)
}
func func1() {
fmt.Println(1)
}
例子4:在函数方法中defer与return谁先执行
结论:return早于defer执行
package main
import "fmt"
func deferFun() int{
fmt.Println("deferFun")
return 0
}
func returnFun() int {
fmt.Println("returnFun")
return 0
}
func func1()int {
defer deferFun()
return returnFun()
}
func main() {
fmt.Println(func1())
}