函数是可以让我们将语句打包成一个单元,然后可以多次调用,其可以提高应用的模块性和代码的重复利用率。

Go是编译型语言,所以函数编写的顺序无关紧要;为了可读性,往往把main() 函数写在前面,其他函数按照一定逻辑顺序进行编写(例如函数被调用的顺序)。

函数声明

使用关键字func声明;一个函数包括:函数名、形式参数列表、返回值、及{}函数主体,当然这些都可以省略,但是(){}不能省。

func 函数名(参数列表) 返回值{
     主体
}

函数名

函数实质上也是一个引用型变量;所有命名规则和变量一样,函数名大小写对应包外是否可见,函数名也不能和常量或变量同名

const NAME  = "老王"
​
func NAME()  {} // NAME redeclared in this block previous declaration 

参数

参数是在函数主体可以直接使用的变量

func name(x string){
    fmt.Println(x) //打印x
}

参数个数没有限制,类型也没有限制,形式参数有点类型变量的声明,如下:

func name(x int){} // 一个int
func name1(x string) //一个string
func name2(x int,y string)//一个int,一个string
func name2(x , y string)//2个string

不定长参数

不定长参数是前面参数已确定,后面必须同类型的不确定长度用变量名 ... 类型表示,实质上是切片

func name(a... int){ } //不确定长度的int型 变量a 操作和切片一样
​
func name3(a string,b... int){}

形式参数不需要和普通变量一样必须要使用

func test(a string,b int){
    fmt.Println(b) // a 没有使用,能正常运行
}

调用

在需要调用的地方使用函数名和参数即可

package main
​
import (
    "fmt"
)
​
func main()  {
     test("哈哈哈",2) //此处调用
     test("嘿嘿嘿",3) //重复调用,使用不同的参数
}
​
func test(a string,b int){
    fmt.Println(a,b)
}

默认值

Go语言的函数没有默认值,函数调用是参数必须要和定义的参数一致

package main
​
import (
    "fmt"
)
​
func main()  {
     //参数给少了
     test("嘿嘿嘿")  //not enough arguments in call to test
     //have (string)
     //want (string, int)
     //参数给多了
     test("嘿嘿嘿",3,5477)// too many arguments in call to test
     //have (string, number, number)
     //want (string, int)
     //参数顺序不一致
     test(3,"嘿嘿嘿") //cannot use 3 (type int) as type string in argument to test
     // cannot use "嘿嘿嘿" (type string) as type int in argument to test

}
​
func test(a string,b int){
    fmt.Println(a,b)
}

返回值

函数返回值是在参数括号后面,Go语言中函数返回值可以是多个

返回值可以定义类型,不定义具体名称

func test() string{
    return "返回值"
}

返回值也可以可以具体名称,超过2个字符必须加上括号()包起来

func test() (name string){
     name = "返回值"
     return name
}

多个返回值

func test1() (name string,age int){
     name = "返回值"
     age = 23
     return name,age
}

如果有多个返回值时,定义有具体名称的必须全部都要名称:

func test1() (name string, int){
     name = "返回值"
     return name,23
}
//syntax error: mixed named and unnamed function parameters

作用域

对于整个程序来说,函数参数、返回值和函数主体里定义的参数是局部变量,函数结束后,内存会被GC回收。但GC回收是看变量是否有被引用,如果引用就不回收。如下,常常用来生成结构体变量:

package main
​
import (
     "fmt"
)
​
type User struct {
     Name string
}
​
func NewUser() *User {
     return &User{}
}
​
func main()  {
     user := NewUser() //此处返回局部变量,但变量不会被回收,因为还没还在引用
     user.Name="嘿嘿"
     fmt.Println(user)
}

终止

有时候我们需要运行到某个地方必须终止本函数,不需要等函数全部运行完毕,使用关键字return来终止。

特殊函数

Go语言有一些内置函数,能直接调用的函数

函数名说明
append、copy切片的添加元素和复制切片
close关闭管道通信
complex、real 、imag创建和操作复数
len、caplen 返回长度或数量(字符串、数组、切片、map 和管道);cap 返回容量(只能用于切片和 map)
new、make用于分配内存
panic、recover错误处理机制
print、println底层打印函数

main()函数

main函数是程序的入口,一个程序只能有一个main函数

init()函数

init函数是在编译时自动调用的函数,无需(也不能)我们调用,一个包内可以有多个init函数,不冲突;有多个init时按导入包的顺序执行,如下图按箭头顺序执行。

image.png

defer

关键字defer用来延迟调用函数的执行,他是在函数结束前调用,如下:

func main()  {
     fmt.Println("函数开始。。。。")
     defer test()
     fmt.Println("函数结束。。。。")
}
​
func test() {
    fmt.Println("函数结束了,我执行。。。。")
}
//函数开始。。。。
//函数结束。。。。
//函数结束了,我执行。。。。

函数结束包括整个函数执行完毕和异常都触发

func main()  {
     fmt.Println("函数开始。。。。")
     defer test()
     panic("异常")
     fmt.Println("函数结束。。。。")
}
​
func test() {
    fmt.Println("函数结束了,我执行。。。。")
}
//函数开始。。。。
//函数结束了,我执行。。。。
//panic:异常

可以有多个defer,执行顺序是先进后出,即堆栈

package main
​
import (
    "fmt"
)
​
func main()  {
    fmt.Println("函数开始。。。。")
    defer test1()
    defer test2()
    fmt.Println("函数结束。。。。")
}
​
func test1() {
    fmt.Println("函数结束了,test1 执行。。。。")
}
​
func test2() {
    fmt.Println("函数结束了,test2 执行。。。。")
}
​
//函数开始。。。。
//函数结束。。。。
//函数结束了,test2 执行。。。。
//函数结束了,test1 执行。。。。

应用:defer 一般用于需要手动关闭的资源,如:文件的关闭、网络的关闭;还应用于处理互斥锁和错误处理。

package main
​
import (
     "net/http"
     "sync"
)
var mu sync.Mutex
func main()  {
     defer func() {
         if err := recover();err!=nil{
            //... 错误处理
         }
     }()
     resp, err := http.Get("http://www.example.com")
     if err != nil {
        panic(err)
     }
     defer resp.Body.Close()  //网络关闭
     //....
}
​
func Lock()  {
     mu.Lock()
     defer mu.Unlock()
     //.....  临界资源的操作
}

defer 只作用于语句的最后一个函数,如下:

package main
​
import (
    "fmt"
)
​
type User struct {
     Name string
}
​
func NewUser() *User {
    return &User{}
}
​
func (u *User) Show(name string) *User {
     fmt.Println(name)
     return u
}
​
func main()  {
     user := NewUser() 
     defer user.Show("1").Show("2").Show("3") //Show("1").Show("2") 和defer无关
     user.Show("结束了。。。")
}
//1
//2
//结束了。。。
//3

defer 参数是函数时

package main
​
import (
    "fmt"
)
​
func main()  {
     x,y:=1,2
     defer  call(x,call(x,y)) //作为参数的call(x,y)和defer 无关,先执行
     call(x,y)
}
​
func call(x ,y int) int {
     fmt.Println(x,y)
     return x+y
}
//1 2
//1 2
//1 3

匿名函数

匿名函数就是没有名字的函数,使用方式有两种,一种是直接调用,没有返回值;还有一种是赋值给一个变量,然后可以多次调用,可以有返回值。如下:

package main
​
import (
    "fmt"
)
​
func main()  {
     func() {
        fmt.Println("直接定义并且调用的匿名函数")  //直接定义并且调用的匿名函数
     }() // 加上()直接调用
    ​
     a:= func() {
         fmt.Println("定义匿名函数并负责给a变量")
     }
     a() // 调用匿名函数  定义匿名函数并负责给a变量
     a() // 调用匿名函数  定义匿名函数并负责给a变量
    ​
     b:= func() int {
     fmt.Println("定义匿名函数并负责给b变量,有返回值")
        return 1
     }
     i := b() //定义匿名函数并负责给b变量,有返回值
     fmt.Println(i) //1
}
​

匿名函数的使用非常方便,常常应用于 形式参数、启动新goroutine和defer 配合处理错误等,下面简单示例

package main
​
import (
     "fmt"
)
func show(y string)  {
     fmt.Println(y)
}
func test(x string, a func(y string))  {
     a(x)
}
func main()  {
     test("形式参数",show) //形式参数
    ​
     ch := make(chan struct{})
     go func() {
     fmt.Println("启动协程。。。")  //启动协程。。。
     ch <- struct{}{}
     }()
     <- ch
}

递归

递归函数简单来说就是自己调用自己,Go语言是支持递归函数的。下面递归求1~5数列的和

package main
​
import (
    "fmt"
)
var sum int
func main()  {
     test(5)
     fmt.Println(sum) //15
}
​
func test(x int)  {
     if x <=0 {
         return
     }
     sum += x
     x--
     test(x)  //自己调用自己
}

递归函数应用广泛,比如树的遍历、无限菜单等。虽然支持无效递归,但是递归比较消耗内存, 使用时不应该无限递归下去,应该有个打破条件跳出。

回调

回调函数参数是另一个函数。

package main
​
import (
    "fmt"
)
​
func main()  {
     cabblak(test) //参数为函数
}
​
func test()  {
     fmt.Println("参数为函数")
}
​
func cabblak(a func())  {
     a()
}

闭包

闭包函数是把函数作为返回值

package main
​
import (
     "fmt"
)
​
func main()  {
     i := test(1)
     i() //2
     i() //3
    ​
     j:=test(1)
     j() //2
     j() //3
}
​
func test(n int) func()  {
     return func() {
        n++
        fmt.Println(n)
     }
}
​

需要注意的是,函数本质上是引用类型变量,闭包函数返回函数即变量,这个变量还被引用状态,GC没有回收,故里面定义的各种变量内存地址没有发生变化,在后面不停重复调用i() ,地址没发生变化,所有n的值在不断累计。j是另一个变量了,和i没什么联系,就像var num1,num2 int的个num1num2关系。


一夕烟云
1 声望1 粉丝