乐趣区

关于golang:Go基础编程函数

函数是能够让咱们将语句打包成一个单元,而后能够屡次调用,其能够进步利用的模块性和代码的反复利用率。

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、cap len 返回长度或数量(字符串、数组、切片、map 和管道);cap 返回容量(只能用于切片和 map)
new、make 用于分配内存
panic、recover 错误处理机制
print、println 底层打印函数

main()函数

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

init()函数

init函数是在编译时主动调用的函数,无需(也不能)咱们调用,一个包内能够有 多个 init函数,不抵触;有多个 init 时按导入包的程序执行,如下图按箭头程序执行。

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关系。

退出移动版