宣告函式
函數就像個果汁機,使用者丟什麼食材進去,果汁機可以根據不同的設定(運算邏輯),產出不同的結果。在Go裡面使用函數的方式其實和JavaScript、Python很像,不管是傳入的參數值或是返回值得類型都是可選擇的,最大的不同則是是如果有return的話一定要定義輸出的類型,其宣告格式如下
func functionName([parameter list])[return type]{
//運算邏輯
}
最簡單呼叫函式的方式就是沒有任何傳遞任何參數,與要求回傳的類型
func main() {
newCard()
}
func newCard() {
fmt.Println("Ace of Spades")
}
// Ace of Spades
如在main函式裡面,呼叫add(5,3),add函式無論如何都會回傳int類型的結果。 值得注意的是,因為Go是一個強型別(strongly typed)語言,在執行階段會檢查型別,因此如果我們函式定義返回int,在第三行如果是return字串就會出現錯誤。
func add(x int, y int) int {
return x + y
//return "8"
}
func main() {
fmt.Println(add(5, 3))
}
// 8
此外在函式裡面如果也可以返回多個值,如下面compute function 定義回傳兩個int類型的結果
func main() {
fmt.Println(compute(5, 3))
}
func compute(x int, y int) (int, int) {
return x * y, x + y
}
// 15, 8
參數傳遞
在Go語言裡面,參數的傳遞都是都透過Pass by value,實際傳遞時會將參數複製一份到函式中, 因此在函式中對參數進行修改並不會影響原本的值。 如下為官網原文
As in all languages in the C family, everything in Go is passed by value. That is, a function always gets a copy of the thing being passed, as if there were an assignment statement assigning the value to the parameter. For instance, passing an int value to a function makes a copy of the int, and passing a pointer value makes a copy of the pointer, but not the data it points to
以下面例子為例,雖然將num1和num2傳入compute函式中,但在函式中的修改不會影響原本的值。
func main() {
num1 := 6
num2 := 8
fmt.Printf("compute函式前的num1值: %d\n", num1)
fmt.Printf("compute函式前的num2值: %d\n", num2)
fmt.Println(compute(num1, num2))
fmt.Printf("compute函式後的num1值: %d\n", num1)
fmt.Printf("compute函式後的num2值: %d\n", num2)
}
func compute(x int, y int) int {
x = 10
y = 12
return x + y
}
// compute函式前的num1值: 6
// compute函式前的num2值: 8
// 22
// compute函式後的num1值: 6
// compute函式後的num2值: 8
那如果我們想透過函式修改影響原本的變數要怎麼辦呢,就要傳遞指針。
傳遞指針也是以值傳遞,但複製的是指針本身,而指針所指向的地址是一樣的,所以在函式內部的修改會影響到函式外原本變數的值。
如下面例子,如果將變數前面加上ampersand&,可以指向該變數的內存地址,並在傳入函式後,透過*將原本的變數進行反解構修改變數的值(L13、L14),就會到函式外原本變數的值
func main() {
num1 := 6
num2 := 8
fmt.Printf("compute函式前的num1值: %d\n", num1)
fmt.Printf("compute函式前的num2值: %d\n", num2)
fmt.Println(compute(&num1, &num2))
fmt.Printf("compute函式後的num1值: %d\n", num1)
fmt.Printf("compute函式後的num2值: %d\n", num2)
}
func compute(x *int, y *int) int {
*x = 10
*y = 12
return *x+*y
}
// compute函式前的num1值: 6
// compute函式前的num2值: 8
// 22
// compute函式後的num1值: 10
// compute函式後的num2值: 12
defer的使用
在函式的使用中,經常需要創建資源,例如使用資料庫連接取得資料,為了讓該函式執行完畢後可以及時釋放資源,Go提供defer這個方法來達成,這也蠻像JavaScript裡面的try..finally。
其有下列特性 :
- 當執行到一個defer的時候,不會立即執行defer所定義的函式,而是將其放入一個專門儲存defer的stack裡面,然後繼續執行函式
- 當函式執行完畢後,會從該stack最上面取出開始執行 (First In, Last Out),如下圖
接下舉例兩種最常使用的例子,第一個為當我建立資料庫連線的時候,我要更新參加者的資訊,當執行完畢後,需要釋放資源,避免佔用記憶體資源
func UpdateParticipantInfo(participantInfo []byte, roomId string) {
var p pcpInRoom
err := json.Unmarshal(participantInfo, &p)
if err != nil {
fmt.Println("Error during json to struct")
return
}
db, _ := ConnectToMYSQL()
_, err = db.Exec("INSERT INTO participant(room_id,member_id,pcp_stream_id) values(?,?,?);", roomId, p.ParticipantId, p.ParticipantStreamId)
if err != nil {
fmt.Println("Insert participant failed")
return
}
defer db.Close()
}
第二個則是和recover函式一起使用,在Go裡面當函式遇到panic錯誤時候,可以透過recover()恢復執行。我們可以在可能產生panic的函式裡面放入defer recover()的方法,讓內層函式即使遇到panic錯誤,外層函式還是可以繼續執行
func f() {
defer func() {
r := recover()
if r != nil {
fmt.Println("Recovered in f", r)
}
}()
fmt.Println("Calling g.")
g()
fmt.Println("Returned normally from g.")
}