函數:是組織好的、可重複使用的、用於執行指定任務的代碼塊
Go語言中支持函數、匿名函數以及閉包函數
Go語言中定義函數使用func關鍵字
func 函數名(參數)(返回值){ 函數體 }
函數名:由字母、數字、下劃線組成。但是函數名的第一個字母不能是數字。在同一個包內,函數名也是不能重名的
參數:參數由參數變量和參數變量的類型組成,多個參數之間使用逗號進行分隔
返回值:返回值由返回值變量和其變量類型組成,也可以只寫返回值的類型,要是有多個返回值的話就必須用括號進行包裹,裏面用逗號進行分隔
函數體:實現指定功能的代碼塊
實現兩數之和 的函數:
func intSum(x int, y int) int { return x + y }
函數的參數和返回值都是可選的,我們也可以實現一個既不需要參數也沒有返回值的函數
func sayHello() { fmt.Println("Hello 沙河") }
函數的調用:
定義了函數之後,我們可以通過函數名加括號的方式調用函數,這裏要是在調用有返回值的函數的時候,可以不接受其的返回值
參數:
類型簡寫:
函數的參數中如果相鄰的變量的類型相同,是可以省略的
func intSum(x, y int) int { return x + y }
上面的代碼中,intSum函數有兩個參數,這兩個參數的類型都是int類型,所以可以省略x的類型,因爲y後面有類型說明,x參數的類型也是該類型
可變參數:
可變參數是指函數的參數數量不固定。Go語言中的可變參數通過在參數後面加上...來比標識
ps:可變參數通常要作爲函數的最後一個參數
func intSum2(x ...int) int { fmt.Println(x) //x是一個切片 sum := 0 for _, v := range x { sum = sum + v } return sum }
當有固定參數搭配可變參數一起使用的時候,要把可變參數放在固定參數的後面
func intSum3(x int, y ...int) int { fmt.Println(x, y) sum := x for _, v := range y { sum = sum + v } return sum }
本質上函數的可變參數是通過切片來實現的
返回值:
Go語言中的return關鍵字向外輸出返回值
多個返回值:
Go語言中函數支持多個返回值,函數如果有多個返回值的時候就必須使用括號將所有的返回值包裹起來
func calc(x, y int) (int, int) { sum := x + y sub := x - y return sum, sub }
返回值命名:
函數定義的時候可以給返回值命名,並在函數體中直接使用這些變量,最後通過return關鍵字返回
func calc(x, y int) (sum, sub int) { sum = x + y sub = x - y return }
defer語句:
Go語言中的defer語句會將其後面跟隨的語句進行延遲處理。在defer歸屬的函數即將返回的時候,將延遲處理的語句按defer定義的逆序進行執行,也就是說,先被defer的語句最後被執行,最後被defer的語句最先被執行
func main() { fmt.Println("start") defer fmt.Println(1) defer fmt.Println(2) defer fmt.Println(3) fmt.Println("end") } start end 3 2 1
由於defer語句延遲調用的特性,所以defer語句能非常方便的處理資源釋放問題。比如:資源清理、文件關閉、解鎖以及記錄時間等等。
defer執行時機:
在Go語言的函數中return語句在底層並不是原子操作,它分爲給返回值賦值和RET指令兩步。而defer語句執行的時機就是在返回值賦值操作之後,RET指令執行之前
變量作用域:
全局變量:
全局變量是定義在函數外部的變量,它在程序整個運行週期內都是生效的。在函數中是可以訪問到全局變量的
局部變量:
局部變量分爲兩種:
在函數內定義的變量無法在函數外部進行調用
如果局部變量和全部變量重名了,優先訪問局部變量
在if、for中定義的變量也只會在當前語句塊中生效
函數類型與變量:
定義函數的類型:
可以使用type關鍵字來定義一個函數的類型
type calculation func(int, int) int
上面的語句定義了一個calculation類型,它是一種函數類型,這種函數接受兩個int類型的參數並且返回一個int類型的參數
簡單來說只要滿足這個條件的函數都是calculation類型的函數
函數類型變量:
可以聲明函數類型的變量,並且爲該變量賦值
func main() { var c calculation // 聲明一個calculation類型的變量c c = add // 把add賦值給c fmt.Printf("type of c:%T\n", c) // type of c:main.calculation fmt.Println(c(1, 2)) // 像調用add一樣調用c f := add // 將函數add賦值給變量f1 fmt.Printf("type of f:%T\n", f) // type of f:func(int, int) int fmt.Println(f(10, 20)) // 像調用add一樣調用f }
高階函數:
分爲函數作爲參數和函數作爲返回值兩部分
函數作爲參數:
func add(x, y int) int { return x + y } func calc(x, y int, op func(int, int) int) int { return op(x, y) } func main() { ret2 := calc(10, 20, add) fmt.Println(ret2) //30 }
函數作爲返回值:
func do(s string) (func(int, int) int, error) { switch s { case "+": return add, nil case "-": return sub, nil default: err := errors.New("無法識別的操作符") return nil, err } }
package main import "fmt" type testInt func(int) bool // 聲明瞭一個函數類型 func isOdd(integer int) bool { if integer%2 == 0 { return false } return true } func isEven(integer int) bool { if integer%2 == 0 { return true } return false } // 聲明的函數類型在這個地方當做了一個參數 func filter(slice []int, f testInt) []int { var result []int for _, value := range slice { if f(value) { result = append(result, value) } } return result } func main(){ slice := []int {1, 2, 3, 4, 5, 7} fmt.Println("slice = ", slice) odd := filter(slice, isOdd) // 函數當做值來傳遞了 fmt.Println("Odd elements of slice are: ", odd) even := filter(slice, isEven) // 函數當做值來傳遞了 fmt.Println("Even elements of slice are: ", even) }
函數當作值和類型在我們寫一些通用接口的時候非常有用,上面的testInt是一個函數類型,然後兩個filter函數的參數和返回值與testInt這個類型是一個函數類型,然後兩個filter函數的參數和返回值與testInt類型是一樣的,但是我們可以實現很多種的邏輯,這樣會使我們的程序變得非常靈活
Panic和Recover
Go沒有像Java那樣的異常處理機制,它不能拋出異常,而是使用了panic和recover機制。記着這個應該當成最後的手段來使用,也就是說你的代碼中應該沒有甚至很少有panic的東西。這是個很強大的東西。
Panic:
是一個內建函數,可以中斷原有的控制流程,進入一個panic狀態中。當函數F調用panic,函數F的執行被中斷,但是F中的延遲函數會正常執行,然後F返回到調用它的地方,在調用的地方,F的行爲就像調用了panic。這一過程繼續向上,直到發生panic的goroutine中所有調用的函數返回,此時程序退出。panic可以直接調用panic產生。也可以由運行時錯誤產生,例如訪問越界的數組。
var user = os.Getenv("USER") func init() { if user == "" { panic("no value for $USER") } }
Recover:
是一個內建函數,可以讓進入panic的goroutine恢復過來。recover僅僅在延遲函數中有效。在正常的執行過程中,調用recover會返回nil,並且沒有其它任何的效果。如果當前的goroutine陷入了panic狀態,調用recover可以捕獲到panic的輸入值,並且恢復正常的執行。
下面這個函數檢查作爲其參數的函數在執行的時候是否會產生panic
func throwsPanic(f func()) (b bool) { defer func() { if x := recover(); x != nil { b = true } }() f() //執行函數f,如果f中出現了panic,那麼就可以恢復回來 return }
main函數和init函數:
Go裏面有兩個保留函數:init函數(能夠應用與所有的package)和main函數(只能應用於package main)。這兩個函數的定義是不能有任何的參數和返回值的。雖然一個package裏面可以寫任意多個init函數,但是這不論是對於可讀性還是對於以後的可維護性來說,都還是推薦在一個package中只寫一個init函數
Go程序會自動調用init()和main(),所以你不需要在任何地方調用這兩個函數。每個package中的init函數都是可選的,但是package main就必須包含一個main函數
程序的初始化和執行都起始於main包。如果main包還倒入了其它的包,那麼就會在編譯的時候將它們一次進行導入。有時有一個包會被多個包同時的導入,那麼它就會被導入一次(例如很多包都會用到fmt包,那麼它只會被導入一次,因爲沒有必要導入很多次)。當一個包被導入時,還倒入了其它的包,那麼會先將其它包導入進來,然後再對這些包中的變量和常量進行初始化,接着開始執行init函數(如果有的話就會執行),以此類推。等所有被導入的包加載完畢了,就會開始對main包中的包級常量和變量進行初始化,然後執行main包中的init函數(如果存在的話),最後執行main函數
main函數引入包初始化的整個流程
import
在寫Go代碼的時候會經常用到import命令來導入包文件,我們經常可以看到
import( "fmt" )
然後我們可以
fmt.Println("hello world")
上面這個fmt是Go的標準庫,其實就是去GOROOT環境變量執行目錄下加載該模塊,當然Go的import還支持相對路徑和絕對路徑來加載自己寫的模塊:
相對路徑:
import “./model” //當前文件同一目錄的model目錄,但是不建議這種方式來import
絕對路徑:
import “shorturl/model” //加載gopath/src/shorturl/model模塊
還有一些常用的導入的方式
點操作
import( . "fmt" )
點操作就是這個包導入之後在你調用這個包的函數的時候,你可以省略包的前綴名
別名操作
import( f "fmt" )
調用包函數的時候前綴編程我們自己定義的前綴
_操作
_操作其實是引入該包,而不直接使用包裏面的函數,而是調用了該包裏面的init函數
匿名函數、閉包
匿名函數:
函數當然還可以作爲返回值,但是在Go語言中函數內部不能在像之前那樣定義函數了,只能定義匿名函數。匿名函數顧名思義就是沒有函數名的函數
func(參數)(返回值){ 函數體 }
匿名函數因爲沒有函數名,所以沒有辦法像普通函數那樣進行調用,所以匿名函數需要保存到某個變量或者作爲立即執行的函數:
func main() { // 將匿名函數保存到變量 add := func(x, y int) { fmt.Println(x + y) } add(10, 20) // 通過變量調用匿名函數 //自執行函數:匿名函數定義完加()直接執行 func(x, y int) { fmt.Println(x + y) }(10, 20) }