5.1 定義格式
函數(shù)構(gòu)成代碼執(zhí)行的邏輯結(jié)構(gòu)。在Go語言中咙鞍,函數(shù)的基本組成為:關(guān)鍵字func织堂、函數(shù)名叠艳、參數(shù)列表、返回值易阳、函數(shù)體和返回語句附较。
Go 語言函數(shù)定義格式如下:
func FuncName(/*參數(shù)列表*/) (o1 type1, o2 type2/*返回類型*/) {
//函數(shù)體
return v1, v2 //返回多個(gè)值
}
函數(shù)定義說明:
l func:函數(shù)由關(guān)鍵字 func 開始聲明
l FuncName:函數(shù)名稱,根據(jù)約定潦俺,函數(shù)名首字母小寫即為private拒课,大寫即為public
l 參數(shù)列表:函數(shù)可以有0個(gè)或多個(gè)參數(shù),參數(shù)格式為:變量名 類型事示,如果有多個(gè)參數(shù)通過逗號(hào)分隔早像,不支持默認(rèn)參數(shù)
l 返回類型:
① 上面返回值聲明了兩個(gè)變量名o1和o2(命名返回參數(shù)),這個(gè)不是必須肖爵,可以只有類型沒有變量名
② 如果只有一個(gè)返回值且不聲明返回值變量卢鹦,那么你可以省略,包括返回值的括號(hào)
③ 如果沒有返回值劝堪,那么就直接省略最后的返回信息
④ 如果有返回值冀自, 那么必須在函數(shù)的內(nèi)部添加return語句
5.2 自定義函數(shù)
5.2.1 無參無返回值
func Test() { //無參無返回值函數(shù)定義
fmt.Println("this is a test func")
}
func main() {
Test() //無參無返回值函數(shù)調(diào)用
}
5.2.2 有參無返回值
5.2.2.1 普通參數(shù)列表
func Test01(v1 int, v2 int) { //方式1
fmt.Printf("v1 = %d, v2 = %d\n", v1, v2)
}
func Test02(v1, v2 int) { //方式2, v1, v2都是int類型
fmt.Printf("v1 = %d, v2 = %d\n", v1, v2)
}
func main() {
Test01(10, 20) //函數(shù)調(diào)用
Test02(11, 22) //函數(shù)調(diào)用
}
5.2.2.2 不定參數(shù)列表
- 不定參數(shù)類型
不定參數(shù)是指函數(shù)傳入的參數(shù)個(gè)數(shù)為不定數(shù)量。為了做到這點(diǎn)秒啦,首先需要將函數(shù)定義為接受不定參數(shù)類型:
//形如...type格式的類型只能作為函數(shù)的參數(shù)類型存在熬粗,并且必須是最后一個(gè)參數(shù)
func Test(args ...int) {
for _, n := range args { //遍歷參數(shù)列表
fmt.Println(n)
}
}
func main() {
//函數(shù)調(diào)用,可傳0到多個(gè)參數(shù)
Test()
Test(1)
Test(1, 2, 3, 4)
}
- 不定參數(shù)的傳遞
func MyFunc01(args ...int) {
fmt.Println("MyFunc01")
for _, n := range args { //遍歷參數(shù)列表
fmt.Println(n)
}
}
func MyFunc02(args ...int) {
fmt.Println("MyFunc02")
for _, n := range args { //遍歷參數(shù)列表
fmt.Println(n)
}
}
func Test(args ...int) {
MyFunc01(args...) //按原樣傳遞, Test()的參數(shù)原封不動(dòng)傳遞給MyFunc01
MyFunc02(args[1:]...) //Test()參數(shù)列表中余境,第1個(gè)參數(shù)及以后的參數(shù)傳遞給MyFunc02
}
func main() {
Test(1, 2, 3) //函數(shù)調(diào)用
}
5.2.3 無參有返回值
有返回值的函數(shù)驻呐,必須有明確的終止語句,否則會(huì)引發(fā)編譯錯(cuò)誤芳来。
5.2.3.1 一個(gè)返回值
func Test01() int { //方式1
return 250
}
//官方建議:最好命名返回值含末,因?yàn)椴幻祷刂担m然使得代碼更加簡(jiǎn)潔了即舌,但是會(huì)造成生成的文檔可讀性差
func Test02() (value int) { //方式2, 給返回值命名
value = 250
return value
}
func Test03() (value int) { //方式3, 給返回值命名
value = 250
return
}
func main() {
v1 := Test01() //函數(shù)調(diào)用
v2 := Test02() //函數(shù)調(diào)用
v3 := Test03() //函數(shù)調(diào)用
fmt.Printf("v1 = %d, v2 = %d, v3 = %d\n", v1, v2, v3)
}
5.2.3.2 多個(gè)返回值
func Test01() (int, string) { //方式1
return 250, "sb"
}
func Test02() (a int, str string) { //方式2, 給返回值命名
a = 250
str = "sb"
return
}
func main() {
v1, v2 := Test01() //函數(shù)調(diào)用
_, v3 := Test02() //函數(shù)調(diào)用答渔, 第一個(gè)返回值丟棄
v4, _ := Test02() //函數(shù)調(diào)用, 第二個(gè)返回值丟棄
fmt.Printf("v1 = %d, v2 = %s, v3 = %s, v4 = %d\n", v1, v2, v3, v4)
}
5.2.4 有參有返回值
//求2個(gè)數(shù)的最小值和最大值
func MinAndMax(num1 int, num2 int) (min int, max int) {
if num1 > num2 { //如果num1 大于 num2
min = num2
max = num1
} else {
max = num2
min = num1
}
return
}
func main() {
min, max := MinAndMax(33, 22)
fmt.Printf("min = %d, max = %d\n", min, max) //min = 22, max = 33
}
5.3 遞歸函數(shù)
遞歸指函數(shù)可以直接或間接的調(diào)用自身侥涵。
遞歸函數(shù)通常有相同的結(jié)構(gòu):一個(gè)跳出條件和一個(gè)遞歸體。所謂跳出條件就是根據(jù)傳入的參數(shù)判斷是否需要停止遞歸宋雏,而遞歸體則是函數(shù)自身所做的一些處理芜飘。
//通過循環(huán)實(shí)現(xiàn)1+2+3……+100
func Test01() int {
i := 1
sum := 0
for i = 1; i <= 100; i++ {
sum += i
}
return sum
}
//通過遞歸實(shí)現(xiàn)1+2+3……+100
func Test02(num int) int {
if num == 1 {
return 1
}
return num + Test02(num-1) //函數(shù)調(diào)用本身
}
//通過遞歸實(shí)現(xiàn)1+2+3……+100
func Test03(num int) int {
if num == 100 {
return 100
}
return num + Test03(num+1) //函數(shù)調(diào)用本身
}
func main() {
fmt.Println(Test01()) //5050
fmt.Println(Test02(100)) //5050
fmt.Println(Test03(1)) //5050
}
5.4 函數(shù)類型
type FuncType func(int, int) int //聲明一個(gè)函數(shù)類型, func后面沒有函數(shù)名
//函數(shù)中有一個(gè)參數(shù)類型為函數(shù)類型:f FuncType
func Calc(a, b int, f FuncType) (result int) {
result = f(a, b) //通過調(diào)用f()實(shí)現(xiàn)任務(wù)
return
}
func Add(a, b int) int {
return a + b
}
func Minus(a, b int) int {
return a - b
}
func main() {
//函數(shù)調(diào)用,第三個(gè)參數(shù)為函數(shù)名字磨总,此函數(shù)的參數(shù)嗦明,返回值必須和FuncType類型一致
result := Calc(1, 1, Add)
fmt.Println(result) //2
var f FuncType = Minus
fmt.Println("result = ", f(10, 2)) //result = 8
}
5.5 匿名函數(shù)與閉包
所謂閉包就是一個(gè)函數(shù)“捕獲”了和它在同一作用域的其它常量和變量。這就意味著當(dāng)閉包被調(diào)用的時(shí)候蚪燕,不管在程序什么地方調(diào)用娶牌,閉包能夠使用這些常量或者變量奔浅。它不關(guān)心這些捕獲了的變量和常量是否已經(jīng)超出了作用域,所以只有閉包還在使用它诗良,這些變量就還會(huì)存在汹桦。
在Go語言里,所有的匿名函數(shù)(Go語言規(guī)范中稱之為函數(shù)字面量)都是閉包鉴裹。匿名函數(shù)是指不需要定義函數(shù)名的一種函數(shù)實(shí)現(xiàn)方式舞骆,它并不是一個(gè)新概念,最早可以回溯到1958年的Lisp語言径荔。
func main() {
i := 0
str := "mike"
//方式1
f1 := func() { //匿名函數(shù)督禽,無參無返回值
//引用到函數(shù)外的變量
fmt.Printf("方式1:i = %d, str = %s\n", i, str)
}
f1() //函數(shù)調(diào)用
//方式1的另一種方式
type FuncType func() //聲明函數(shù)類型, 無參無返回值
var f2 FuncType = f1
f2() //函數(shù)調(diào)用
//方式2
var f3 FuncType = func() {
fmt.Printf("方式2:i = %d, str = %s\n", i, str)
}
f3() //函數(shù)調(diào)用
//方式3
func() { //匿名函數(shù),無參無返回值
fmt.Printf("方式3:i = %d, str = %s\n", i, str)
}() //別忘了后面的(), ()的作用是总处,此處直接調(diào)用此匿名函數(shù)
//方式4, 匿名函數(shù)狈惫,有參有返回值
v := func(a, b int) (result int) {
result = a + b
return
}(1, 1) //別忘了后面的(1, 1), (1, 1)的作用是,此處直接調(diào)用此匿名函數(shù)鹦马, 并傳參
fmt.Println("v = ", v)
}
閉包捕獲外部變量特點(diǎn):
func main() {
i := 10
str := "mike"
func() {
i = 100
str = "go"
//內(nèi)部:i = 100, str = go
fmt.Printf("內(nèi)部:i = %d, str = %s\n", i, str)
}() //別忘了后面的(), ()的作用是胧谈,此處直接調(diào)用此匿名函數(shù)
//外部:i = 100, str = go
fmt.Printf("外部:i = %d, str = %s\n", i, str)
}
函數(shù)返回值為匿名函數(shù):
// squares返回一個(gè)匿名函數(shù),func() int
// 該匿名函數(shù)每次被調(diào)用時(shí)都會(huì)返回下一個(gè)數(shù)的平方菠红。
func squares() func() int {
var x int
return func() int {//匿名函數(shù)
x++ //捕獲外部變量
return x * x
}
}
func main() {
f := squares()
fmt.Println(f()) // "1"
fmt.Println(f()) // "4"
fmt.Println(f()) // "9"
fmt.Println(f()) // "16"
}
函數(shù)squares返回另一個(gè)類型為 func() int 的函數(shù)第岖。對(duì)squares的一次調(diào)用會(huì)生成一個(gè)局部變量x并返回一個(gè)匿名函數(shù)。每次調(diào)用時(shí)匿名函數(shù)時(shí)试溯,該函數(shù)都會(huì)先使x的值加1蔑滓,再返回x的平方。第二次調(diào)用squares時(shí)遇绞,會(huì)生成第二個(gè)x變量键袱,并返回一個(gè)新的匿名函數(shù)。新匿名函數(shù)操作的是第二個(gè)x變量摹闽。
通過這個(gè)例子蹄咖,我們看到變量的生命周期不由它的作用域決定:squares返回后,變量x仍然隱式的存在于f中付鹿。
5.6 延遲調(diào)用defer
5.6.1 defer作用
func main() {
fmt.Println("this is a test")
defer fmt.Println("this is a defer") //main結(jié)束前調(diào)用
/*
運(yùn)行結(jié)果:
this is a test
this is a defer
*/
}
5.6.2 多個(gè)defer執(zhí)行順序
func test(x int) {
fmt.Println(100 / x)//x為0時(shí)澜汤,產(chǎn)生異常
}
func main() {
defer fmt.Println("aaaaaaaa")
defer fmt.Println("bbbbbbbb")
defer test(0)
defer fmt.Println("cccccccc")
/*
運(yùn)行結(jié)果:
cccccccc
bbbbbbbb
aaaaaaaa
panic: runtime error: integer divide by zero
*/
}
5.6.3 defer和匿名函數(shù)結(jié)合使用
func main() {
a, b := 10, 20
defer func(x int) { // a以值傳遞方式傳給x
fmt.Println("defer:", x, b) // b 閉包引用
}(a)
a += 10
b += 100
fmt.Printf("a = %d, b = %d\n", a, b)
/*
運(yùn)行結(jié)果:
a = 20, b = 120
defer: 10 120
*/
}
5.7 獲取命令行參數(shù)
package main
import (
"fmt"
"os" //os.Args所需的包
)
func main() {
args := os.Args //獲取用戶輸入的所有參數(shù)
//如果用戶沒有輸入,或參數(shù)個(gè)數(shù)不夠,則調(diào)用該函數(shù)提示用戶
if args == nil || len(args) < 2 {
fmt.Println("err: xxx ip port")
return
}
ip := args[1] //獲取輸入的第一個(gè)參數(shù)
port := args[2] //獲取輸入的第二個(gè)參數(shù)
fmt.Printf("ip = %s, port = %s\n", ip, port)
}
運(yùn)行結(jié)果如下:
5.8 作用域
作用域?yàn)橐崖暶鳂?biāo)識(shí)符所表示的常量、類型舵匾、變量俊抵、函數(shù)或包在源代碼中的作用范圍。
5.8.1 局部變量
在函數(shù)體內(nèi)聲明的變量坐梯、參數(shù)和返回值變量就是局部變量徽诲,它們的作用域只在函數(shù)體內(nèi):
func test(a, b int) {
var c int
a, b, c = 1, 2, 3
fmt.Printf("a = %d, b = %d, c = %d\n", a, b, c)
}
func main() {
//a, b, c = 1, 2, 3 //err, a, b, c不屬于此作用域
{
var i int
i = 10
fmt.Printf("i = %d\n", i)
}
//i = 20 //err, i不屬于此作用域
if a := 3; a == 3 {
fmt.Println("a = ", a)
}
//a = 4 //err,a只能if內(nèi)部使用
}
5.8.2 全局變量
在函數(shù)體外聲明的變量稱之為全局變量,全局變量可以在整個(gè)包甚至外部包(被導(dǎo)出后)使用谎替。
var a int //全局變量的聲明
func test() {
fmt.Printf("test a = %d\n", a)
}
func main() {
a = 10
fmt.Printf("main a = %d\n", a) //main a = 10
test() //test a = 10
}
5.8.3 不同作用域同名變量
在不同作用域可以聲明同名的變量偷溺,其訪問原則為:在同一個(gè)作用域內(nèi),就近原則訪問最近的變量钱贯,如果此作用域沒有此變量聲明挫掏,則訪問全局變量,如果全局變量也沒有喷舀,則報(bào)錯(cuò)砍濒。
var a int //全局變量的聲明
func test01(a float32) {
fmt.Printf("a type = %T\n", a) //a type = float32
}
func main() {
fmt.Printf("a type = %T\n", a) //a type = int, 說明使用全局變量的a
var a uint8 //局部變量聲明
{
var a float64 //局部變量聲明
fmt.Printf("a type = %T\n", a) //a type = float64
}
fmt.Printf("a type = %T\n", a) //a type = uint8
test01(3.14)
test02()
}
func test02() {
fmt.Printf("a type = %T\n", a) //a type = int
}