共计 5038 个字符,预计需要花费 13 分钟才能阅读完成。
函数是能够让咱们将语句打包成一个单元,而后能够屡次调用,其能够进步利用的模块性和代码的反复利用率。
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
的个 num1
和num2
关系。