這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
函式宣告
func (p myType ) funcName ( a, b int , c string ) ( r , s int ) { 函數體 return 語句}
- func 關鍵字
- (p myType) 表明 函數所屬於的類型對象!,即為特定類型定義方法,可以省去不寫,即為普通的函數 (這裡我們主要講解 普通的函數)
- 函數名
- 參數 (可以不聲明)
- 傳回值 (可以不聲明)
- 函數體
函數調用
我們知道C++的類中有 private,public,friend,可以控制類成員可見度。並且通過命名空間來減少命名衝突。Java也是如此。
在Go語言中 和python一樣,也是採用 package 來對 不同的模組 進行劃分的。
- 可以直接調用 13 個內建函數(函數全部為小寫名稱)
- 調用標準包中的函數 (直接匯入標準包
import fmt,通過包名 直接調用)
- 調用一個自訂函數,且和主調函數在同一個包中 (直接調用即可,不用包名首碼)
- 被調用的函數 由 使用者建立的包提供! (go install 產生 .a 檔案,import 包名,通過包名調用)
其中第2中和第4中都是來自外部包,並且函數名稱第一個字母都是大寫字母,所不同的是標準包有Go提供,而使用者自己建立的包由使用者自己建立。
import "pcakageName"packageName.FunctionName(參數)
第三種情況由於被掉函數 和 主調函數在同一個包中,所以直接調用即可,無需匯入包名,並且函數首字母可以是小寫字母 (這裡,哪怕兩個兩個函數不在同一個檔案中,只要在同一個包中即可)。
調用標準函數
Golang 提供了 大量的包和實用函數 供使用者使用,這些函數被稱為標準函數。常見的標準包有 fmt, math, os, time bytes 一般包名都是小寫。、
標準包的訊息可以在 Go安裝目錄的 pkg 下面查看,也可以使用godoc 查看。
調用自訂函數
通常,一個可執行檔Go程式一般都有一個 main 包,在 main包中必須聲明一個 main 函數。
調用 外部包的函數
如果需要調用外部包的函數,那麼需要匯入這個包,才能調用相關函數(首字母必須大寫)。
比如構建 mymath包:
1. 首先建立 mymath.go 源檔案:定義了四個函數 (這個源檔案,必須在目錄 $GOPAHT/src/mymath 下面)
package mymathfunc Add(a, b int) int { return (a + b)}func Sub(a, b int) int { return (a - b)}func Mult(a, b int) int { return (a * b)}func Div(a, b int) int { if b != 0 { return float32(a) / float32(b) } else { return 0 }}
調用 go install mypath ,將會在$GOPAHT/src/mymath 下面 編譯這些源檔案,並且安裝到 $GOPATH/src/pkg/(體繫結構名)/下面
匯入這個包之後就可以直接調用了這些函數了。 系統會在相應的下面找到這個 package,並且找到這些包中的函數。(go build 和 go install的區別是什嗎?)
調用內建函數
13個內建函數,這些內建函數,非常有用。
len():可以擷取數組,字串,切片的長度
panic() 可以直接作用於系統底層,用於錯誤處理。
參數傳遞
參數傳遞主要是為了在函數之間,傳遞資料。
Go 語言中,函數參數可以使實值型別,也可以是參考型別。實值型別作為函數參數進行傳遞的時候是一個參數的拷貝。 參考型別是一個地址的拷貝。 大概分為以下幾種類型:
常規傳遞
指標傳遞
數組元素 作為參數
數組名 作為參數 (將會進行數組的整體複製)
package main import ( "fmt")func main() { var b = [5]int {1, 2, 3, 4, 5} f4(b) fmt.Println(b[0])}func f4(a [5]int) { a[0] += 1 fmt.Println(a[0]) //不會影響原先的數組。因為傳遞數組名是指拷貝}
在使用 數組名 作為參數的時候,實參類型和形參類型必須一致。 比如實參 b的類型是 [5]int,那麼形參的類型也應該是 [5]int。 將形參定義為 []int, [10]int 等類型都是錯誤的,而C語言往往允許這麼做。
Go 語言是型別安全的,[5]int 和 []int、[10]int 用示範不同的類型。 如果你的函數的傳回值類型定義為 float32,那麼如果你在函數中返回一個 int 類型變數也將不會通過。(Go語言是型別安全的)
Slice 作為函數參數
- 這是一個地址拷貝,將底層數組的地址賦值給參數的Slice
- 對Slice元素的操作即使對底層數組的操作。
函數作為參數傳遞
函數也是一種資料類型,可以將一個函數賦值給一個變數。
func main(){ var a, b int = 3, 4 f := sum f1(a, b, f)}func f1(a, b int, sum func(int ,int) int) int { fmt.Println(sum(a, b))}func sum(a, b int) int { return (a+b)}
傳回值
傳回值: 允許多個傳回值,並且允許定義傳回值變數,這樣return 語句可以更加方便。
func f2(a, b int) (int ,float32){ //多個傳回值 需要一個括弧 return a *b, float32(a) / float32(b)}
同時在調用函數的時候,也可以忽略傳回值。
func main() { ret, _ := f2(3, 6) //可以忽略傳回值}func f2(a, b int) (int ,float32){ return a *b, float32(a) / float32(b)}
可以命名傳回值參數,這樣在return 的時候,就可以不用直接寫傳回值了。
func main() { sum, sub = f3(3, 6) fmt.Println(sum, sub)}func f3(a, b int) (sum, sub int) { //直接命名了傳回值參數,需要一個括弧 sum = a + b sub = a - b return}
變參函數
形式參數的 類型 和 個數 都是可以變化的。
典型的變參函數有: fmt.Printf(), fmt.Scanf() exec.Command() 等
變參函數的聲明
func functionName (variableArgumetName ... dataType) returnValue {...}
(1) 變參的類型是”…類型“,而且變參必須是函數的最後一個參數。如果函數還有其他的參數,比如放在 變參的前面。 func f1(a int ,s string, args ...int) {...}
(2) 不定長 的變參,實際上就是一個切片,可以使用 range 進行遍曆。
package mainimport "fmt"func main() { f1(1, 2, 3) f1(4, 5, 6, 7)}func f1(args ...int) { fmt.Println(args)}輸出為:[1 2 3][4 5 6 7]
變參函數的 傳遞
一個變參函數,如何將這些變參傳遞給另外一個 變參函數呢?
因為實際上變參就是一個切片,所以可以進行全部的傳遞,也可以進行部分的傳遞。
package mainimport "fmt"func main() { f2(1, 2, 3) f2(4, 5, 6, 7)}func f1(args ...int) { fmt.Println(args)}func f2(args ...int) { f1(args...) f1(args[2:]...)}
不定長的變參在進行參數傳遞的是偶雖然接受到的是一個 Slice,但是和直接傳遞一個Slice還是有區別的:不定長參數在傳遞一個 Slice的時候,它僅僅是擷取 Slice的一個副本,對這個副本進行操作,不會改變原先的Slice的值。
任意類型的 變參函數
當使用者希望傳遞不同類型的參數的時候,就像 fmt.Printf() 可以接受 int string 等各種類型。
此時,應該指定 變參 類型為為 空介面 interface{}
func f1(args... interface{}) //指定變參類型為 interface{}
在Go語言中,interface{} 可以指向任何資料類型,所以可以使用 interface{}定義任意類型的變參。 同時 interface{] 也是型別安全的。 (對所有資料類型的抽象。。。嗎?)
package mainimport ( "fmt")func main() { f1(2, "go", 8, "language", 'a', false, "A", 3.24)}// 採用 interface {} 作為類型func f1(args ...interface{}) { var num = make([]int, 0, 6) var str = make([]string, 0, 6) var ch = make([]int32, 0, 6) //字元類型,是int32的哦! var other = make([]interface{}, 0, 6) //採用 interface{}作為類型 for _, arg := range args { switch v := arg.(type) { //這個是什麼用法? case int: num = append(num, v) case string: str = append(str, v) case int32: //這裡 'a' 被統計到了 int32中。 ch = append(ch, v) default: other = append(other, v) } } fmt.Println(num) fmt.Println(str) fmt.Println(ch) fmt.Println(other)}輸出為:[2 8][go language A][97][false 3.24]
可以看到: Go 語言是型別安全的。 int 類型和 int32不是同一個類型,但是應該是相容的。 字元 字面值 被當做rune類型(也就是 int32 類型,但是不是 int類型 )
匿名函數
聲明:
func (參數列表)(傳回值){函數體} //注意沒有函數名,所以稱為匿名函數func (a, b int) int { return (a + b)}
- 可以隨時在代碼裡定義匿名函數,並且將這個匿名函數 賦值 給一個變數。
- 可以隨時定義匿名函數,並且 執行這個 匿名函數。(聲明函數的時候,直接執行!)
package mainimport ( "fmt")func main() { //聲明 並且直接將 匿名函數 賦值 給變數f f := func(a, b int) int { return a + b } // 對函數類型的變數盡心調用 sum := f(2, 3) fmt.Println(sum) // 聲明 並且 直接執行 匿名函數 sum = func(a, b int) int { return a + b }(2, 3) fmt.Println(sum)}
注意: 使用 匿名函數,不能將它作為頂級函數使用,也就是說一定要將它放在其他函數的函數體中。
2. 匿名函數中 可以 直接使用 上級函數中的變數(這也是其一個方便的用處)
函數閉包
closure:
defer 語句
- defer 語句 向函數進行註冊。
- 在函數退出的時候執行(無論函數是 panic()還是正常退出)
- defer 註冊語句遵循 ”先註冊,後執行“的順序
- 可以 用 defer 語句進行一些資源清理工作。
Golang 異常恢複機制
golang 的 異常恢複機制,是採用 panic() / recover() 的機制。
這些都是 內建函數。