這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
函數function
聽聽聽聽- Go函數不支援 嵌套、重載和預設參數
聽聽聽聽- 但支援以下特性:
聽聽聽聽 聽 聽無需聲明原形、不定長變參、多傳回值、命令傳回值參數、匿名函數、閉包
聽聽聽聽- 定義函數使用關鍵字func,且大括弧不能另起一行(所有有大括弧的均遵循此原則)
聽聽聽聽- 函數也可以作為一種類型的使用,直接賦值給變數(匿名函數)
定義一個函數
聽聽聽聽格式:func name( 傳入的變數1 類型,變數2 類型 ) [ 返回變數 類型,變數 類型 ]{ }
聽聽聽聽- 傳入的變數可以沒有,也可以使多個
聽聽聽聽- 當傳入的變數類型相同時,可以全部省略只留最後一個
聽聽聽聽聽聽聽聽func a(a,b,c int) {}
聽聽聽聽- 傳回值可以有多個,傳回值類型相同,也可以只留最後一個,其中返回變數名稱可以省略,省略的話,就需要每返回一個寫一個變數的類型了,如果指定了返回某個局部變數,那麼這個變數就已經被定義,那麼在函數體內即可直接使用。
聽聽聽聽- 不指定返回變數名稱,那麼需要在函數尾部寫入 return 變數1,變數2, 如果指定了返回的變數名,那麼只需要寫上return即可。
聽聽聽聽- 傳入的參數個數,也可以不定(不定長變參),使用...來表示,在函數體記憶體儲這些資料的類型為slice
聽聽聽聽func A(a ...int) 聽-->...int必須放在最後
聽聽聽聽- 如果傳入的值有1個string,有n個int,那麼只能 fun A(b string, a ...int)這種形式接受
聽聽聽聽- 如果傳入的參數是一個常規的int、string等類型的話,屬於值傳遞(預設),即只是值得拷貝,而如果傳遞sllice,則是引用傳遞(其實slice也屬於值拷貝,只不過,slice拷貝的是記憶體位址。而直接修改記憶體位址會影響來源資料)
聽聽聽聽- 如果需要把int、string類型的值傳入並修改,那麼就需要把這些類型的變數的記憶體位址傳入
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽a聽:=聽2聽聽聽聽A(a)聽聽聽聽fmt.Println(a)}func聽A(a聽int)聽{聽聽聽聽i聽:=聽3聽聽聽聽fmt.Println(i)}結果:32
把變數a的地址傳入到函數中
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽a聽:=聽2聽聽聽聽A(&a)聽聽聽聽//&a表示取a的記憶體位址聽聽聽聽fmt.Println(a)}func聽A(a聽*int)聽{聽聽聽聽//定義指標類型,指向a的記憶體位址聽聽聽聽*a聽=聽3聽聽聽聽//直接對記憶體位址進行賦值聽聽聽聽fmt.Println(*a)}結果:33
參數傳遞(傳值與傳指標)
聽聽聽聽函數的參數傳遞分為兩種,值傳遞,和引用傳遞,值傳遞指在調用函數時將實際參數複製一份傳遞到函數中,這樣在函數中如果對參數進行修改,將不會影響到實際參數。預設情況下,GO是值傳遞,在調用過程中不會影響到實際參數。
聽聽聽聽變數在記憶體中是存放於一定的地址上的,修改變數實際是修改變數地址處的記憶體。只有讓局部函數知道參數的記憶體位址,才能修改變數的值。所以引用傳遞的時候需要把變數的記憶體位址傳入到局部函數內(&a,&表示傳遞變數的記憶體位址),並將函數的參數類型調整為*int,即改為指標類型,才能在函數中修改變數的值,此時參數仍然是copy傳遞的,只不過copy的是一個指標。
函數作為其他變數的值
聽聽聽聽在Go語言中,一切皆類型,函數也可以被命名為變數,然後對變數進行函數的調用
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽a聽:=聽A聽聽聽聽a()}func聽A()聽{聽聽聽聽fmt.Println("Func聽A")}結果:Func聽A
匿名函數
聽聽聽聽在定義函數的時候不指定函數的名稱,而是把函數直接賦值給某個變數的函數叫做匿名函數,調用這個函數的時候,直接使用變數的名稱即可。(因為golang中的func不支援函數嵌套,使用匿名函數可以達到嵌套的效果) 聽 匿名函數不能作為頂級函數(最外層)
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽a聽:=聽func()聽{聽聽聽聽fmt.Println("Func")}聽聽聽聽a()}
閉包函數
聽聽聽聽所謂閉包函數就是將整個函數的定義一氣呵成寫好並賦值給一個變數。然後用這個變數名作為函數名去調用函數體。閉包函數對它外層的函數中的變數具有訪問和修改的許可權
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽f聽:=聽closure(10)聽聽聽聽//調用閉包函數並傳遞10聽聽聽聽fmt.Println(f(1))聽聽聽聽//傳遞1給返回的函數,10+1=11聽聽聽聽fmt.Println(f(2))聽聽聽聽//傳遞2給返回的函數,10+2=12}func聽closure(x聽int)聽func(int)聽int聽{聽聽聽//定義一個函數接收一個參數x,傳回值也是一個函數接收一個變數y聽聽聽聽return聽func(y聽int)聽int聽{聽聽聽聽//返回一個int,函數接收一個參數,返回x+y的值聽聽聽聽聽聽聽聽return聽x聽+聽y聽聽聽聽}}結果:1112
defer
聽聽聽聽- 執行方式類似其他語言中的解構函式,在函數體執行結束後按照調用順序的相反順序逐個執行 (類似於棧的方式,先進後出,後進先出)
聽聽聽聽- 即使函數發生了嚴重錯誤也會執行
聽聽聽聽- 支援匿名函數的調用
聽聽聽聽- 常用語資源清理、檔案關閉、解鎖以及記錄時間等操作
聽聽聽聽- 通過與匿名函數配合可在return之後修改Function Compute結果
聽聽聽聽- 如果函數體內某個變數作為defer時匿名函數的參數,則在定義defer時已經獲得了拷貝,否則則是引用某個變數的地址
聽聽聽聽- Go沒有異常機制,但有panic/recover模式來處理錯誤
聽聽聽聽- Panic可以再任何地方引發,但recover只有在defer調用的函數中有效
例子
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽fmt.Println("a")聽聽聽聽defer聽fmt.Println("1")聽聽聽聽defer聽fmt.Println("2")聽聽聽聽defer聽fmt.Println("3")}結果:a321可以看到,在程式執行完畢後,defer是從最後一條語句開始執行的,證明了defer類似棧的運行方式
defer搭配迴圈的結果
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽for聽i聽:=聽0;聽i聽<聽3;聽i++聽{聽聽聽聽defer聽fmt.Println(i)聽聽聽聽}}結果:210
panic/recover執行個體
主要用來對程式的控制,並且僅針對函數層級的錯誤進行收集與回調。使程式能繼續運行下去
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽A()聽聽聽聽B()聽聽聽聽C()}func聽A()聽{聽聽聽聽fmt.Println("A")}func聽B()聽{聽聽聽聽defer聽func()聽{聽聽聽聽//這裡定義defer執行一個匿名函數,用於捕捉panic,這裡如果把defer放在panic之後那麼程式執行到panic後就會崩潰,那麼defer就不會生效聽聽聽聽聽聽聽聽if聽err聽:=聽recover();聽err聽!=聽nil聽{聽聽聽聽//對引發的panic進行判斷,由於手動觸發了panic並發送了資訊,那麼用recover接收的異常傳回值就要不為空白,如果為nil表示沒有異常,不為nil就表示異常了,這裡對recover的傳回值進行判斷聽聽聽聽}}()聽聽聽聽panic("this聽is聽painc")//發送異常,異常資訊為”this聽is聽panic“}func聽C()聽{聽聽聽聽fmt.Println("C")}結果:AC由於在函數B中定義了異常的recover機制,所以不會迫使程式退出,會繼續執行
panic/recover 執行個體2
package聽mainimport聽"fmt"func聽main()聽{聽聽聽聽fmt.Println("1")聽聽聽聽fmt.Println("2")聽聽聽聽f聽:=聽func()聽{聽聽聽聽聽聽聽聽defer聽func()聽{聽聽聽聽聽聽聽聽聽聽聽聽if聽err聽:=聽recover();聽err聽!=聽nil聽{聽聽聽聽聽聽聽聽聽聽聽聽fmt.Println("panic")聽聽聽聽聽聽聽聽聽聽聽聽}聽聽聽聽聽聽聽聽}()聽聽聽聽panic("hello聽world")聽聽聽聽fmt.Println("7")聽聽聽聽}聽聽聽聽f()聽聽聽聽fmt.Println("8")聽聽聽聽}結果:12panic聽聽聽聽聽聽//列印panic說明程式已經成功的捕捉到了異常8
聽聽聽聽定義了匿名函數,並賦值給了變數f,匿名函數中的"7"不會列印,因為執行到panic已經崩潰了,而我們在匿名函數內定義了recover捕捉,所以匿名函數會被退出,然後繼續執行其他程式
擴充:
聽聽聽聽在go語言中是沒有異常捕獲機制的,通過panic/recover來實現錯誤的捕獲以及處理,利用go函數多傳回值的概念,來進行check,如果err等於nil表示沒有發生錯誤,當程式發生比較嚴重的錯誤,嚴重到無法彌補,比如索引越界,由於我們不能準確的判斷元素的個數,所以recover也沒有意義,所以說這個時候就是一個panic。如果知道可能會索引越界,並且希望程式能從錯誤中回複回來,那麼這時候就需要用到recover,一旦調用recover,系統就會認為你需要從panic狀態恢複過來,當程式進入panic狀態,那麼正常的程式將不會被執行,那麼需要定義defer來執行recover(),defer不管在任何狀態下,都會執行,只要把recover放在defer中,那麼不管程式發生了怎樣的錯誤,程式都會回複過來,需要注意的是defer類似棧的模式,後進先出。在可能發生panic的程式之前,預先定義defer,否則程式運行到painc後直接崩潰了,這個時候他只會去檢查預先定義好的defer,而你放在panic之後,將會失效
例子1:
判斷奇偶數
package聽mainimport聽"fmt"func聽main()聽{a聽:=聽[]int{1,聽2,聽3,聽4,聽5,聽6,聽7,聽8,聽9,聽10,聽11,聽12,聽13,聽14}聽聽聽聽fmt.Println("the聽slice聽is聽",聽a)聽聽聽聽fmt.Println("the聽odd聽is聽",聽odd(a))聽聽聽聽fmt.Println("the聽even聽is聽",聽even(a))}func聽odd(num聽[]int)聽[]int聽{聽聽聽聽var聽result聽[]int聽聽聽聽聽聽聽聽for聽_,聽value聽:=聽range聽num聽{聽聽聽聽聽聽聽聽聽聽聽聽if聽value%2聽==聽0聽{聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽result聽=聽append(result,聽value)聽聽聽聽聽聽聽聽聽聽聽聽}聽聽聽聽聽聽聽聽}聽聽聽聽return聽result}func聽even(num聽[]int)聽[]int聽{聽聽聽聽var聽result聽[]int聽聽聽聽聽聽聽聽for聽_,聽value聽:=聽range聽num聽{聽聽聽聽聽聽聽聽聽聽聽聽if聽value%2聽==聽0聽{聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽continue聽聽聽聽聽聽聽聽}聽聽聽聽result聽=聽append(result,聽value)聽聽聽聽}return聽result}
思路:分別對切片進行過濾,偶數功能模組過濾一遍,挑出偶數,奇數功能模組過濾一遍,挑出奇數。缺點,模組複用 性差。
判斷奇偶數:
package聽mainimport聽(聽聽聽聽"fmt")type聽funcation聽func(int)聽boolfunc聽odd(num聽int)聽bool聽{聽聽聽聽if聽num%2聽==聽0聽{聽聽聽聽聽聽聽聽return聽false聽聽聽聽}聽聽聽聽return聽true}func聽even(num聽int)聽bool聽{聽聽聽聽if聽num%2聽==聽0聽{聽聽聽聽聽聽聽聽return聽true聽聽聽聽聽聽聽聽}聽聽聽聽return聽false}func聽filter(slice聽[]int,聽f聽funcation)聽[]int聽{聽聽聽聽var聽result聽[]int聽聽聽聽聽聽聽聽for聽_,聽value聽:=聽range聽slice聽{聽聽聽聽聽聽聽聽聽聽聽聽if聽f(value)聽{聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽聽result聽=聽append(result,聽value)聽聽聽聽聽聽聽聽聽聽聽聽}聽聽聽聽聽聽聽聽}聽聽聽聽return聽result}func聽main()聽{聽聽聽聽a聽:=聽[]int{1,聽2,聽3,聽4,聽5,聽6,聽7,聽8,聽9}聽聽聽聽fmt.Println("the聽slice聽is聽",聽a)聽聽聽聽fmt.Println("the聽odd聽is聽",聽filter(a,聽odd))聽聽聽聽fmt.Println("the聽even聽is聽",聽filter(a,聽even))}
思路:把判斷奇偶的功能模組化,然後再通過一個模組調奇偶判斷模組,然後再用main函數組織,(使用func類型,進行功能模組的傳遞),有點,結構性強,邏輯強。
本文出自 “你的黑夜” 部落格,請務必保留此出處http://lixin15.blog.51cto.com/3845983/1851888