Go的函数学习笔记 | 青训营笔记

123 阅读5分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第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()方法:

image-20220430224654798

若是多个包导入同一个包,最终该包也只会导入一次,其中的init也只会执行一次!

测试

image-20220430224754130

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...")
}

image-20220430224845904

五、defer调用顺序

介绍

描述:一个函数在执行最后结束之前触发的一种机制。

应用:关闭文件。

特点:①一个函数中允许多个defer。②defer本质是压栈的形式,出来的应该是后入栈的

实战:根据多种情况来展示

例子1:函数运行结束时执行

package main

import "fmt"

func main() {
	defer fmt.Println("defer1")

	fmt.Println("1")
	fmt.Println("2")
}

image-20220430225508261

例子2:多个defer,压栈过程,在下面的先出栈执行

package main

import "fmt"

func main() {
	defer fmt.Println("defer1")
	defer fmt.Println("defer2")

	fmt.Println("1")
	fmt.Println("2")
}

image-20220430225545385

例子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)
}

image-20220430225756167

例子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())
}

image-20220430230207184