Go | 進階函式參數與回傳值
💬 簡介
在 Go 語言中,函式是組織程式碼的重要單位,而參數與回傳值則是函式的核心組成部分。
本篇文章將深入探討 Go 語言中 進階函式參數與回傳值 的應用,包括:
- 同類型參數的定義與應用
- 可變參數(
...
)的使用 - 空白標識符(
_
)的應用 - 改變外部變數的方式(值傳遞與指標傳遞)
- 命名返回值與多重回傳值
這些概念對於提升 Go 語言的函式設計能力至關重要,讓我們一同來探索吧!
圖片來源:Gophers
🔎 進階函式參數
1️⃣ 同類型參數的簡化定義
當函式的多個參數為相同型別時,可以省略部分型別標註,僅在最後一個參數標註型別。
- 範例:簡化參數定義
1
2
3
4
5
6
7
8
9
10
11package main
import "fmt"
func add(a, b int) int {
return a + b
}
func main() {
fmt.Println(add(10, 20)) // 輸出 30
}📝
a
和b
具有相同的int
型別,因此僅需標註一次int
。
2️⃣ 可變參數(Variadic Parameter)
Go 語言允許函式接受 不定數量的參數,透過 … 語法來定義可變參數(Variadic Parameter)。
- 範例:可變參數函式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16package main
import "fmt"
// 定義可變參數函式
func sum(numbers ...int) int {
total := 0
for _, num := range numbers {
total += num
}
return total
}
func main() {
fmt.Println(sum(1, 2, 3, 4, 5)) // 輸出 15
}📝
numbers ...int
允許傳入任意數量的int
,並以 切片(Slice) 形式接收。 - 可變參數的特殊應用
如果已有 切片(Slice),可使用 展開運算符(...
) 傳遞 切片(Slice) 內容:1
2nums := []int{10, 20, 30}
fmt.Println(sum(nums...)) // 展開 slice 傳入
3️⃣ 空白標識符 _
的應用
在 Go 語言中,空白標識符 _
可以用來忽略不需要的參數或回傳值。
- 範例:忽略函式回傳值
1
2
3
4
5
6
7
8
9
10
11
12package main
import "fmt"
func getValues() (int, string, bool) {
return 42, "hello", true
}
func main() {
number, _, flag := getValues() // 忽略字串回傳值
fmt.Println(number, flag) // 輸出 42 true
}📝
_
代表忽略的值,避免變數未使用的警告。
🎯 進階回傳值處理
1️⃣ 改變外部變數的方式:值傳遞與指標傳遞
在 Go 中,函式的參數預設為「值傳遞」,但可以使用「指標傳遞」來改變外部變數的值。
範例:值傳遞
1
2
3
4
5
6
7
8
9
10
11
12
13package main
import "fmt"
func incrementByValue(num int) {
num++
}
func main() {
x := 10
incrementByValue(x)
fmt.Println(x) // 輸出 10,值未改變
}📝
num
為值傳遞,修改num
不影響x
。範例:指標傳遞
1
2
3
4
5
6
7
8
9
10
11
12
13package main
import "fmt"
func incrementByPointer(num *int) {
*num++
}
func main() {
x := 10
incrementByPointer(&x) // 傳遞變數 `x` 的位址
fmt.Println(x) // 輸出 11,值已改變
}📝 使用
*num
來修改記憶體位址上的值,使x
變更。
🚨 當函式傳遞較大記憶體佔用的變數時,使用指標能顯著提高效能。然而,指標傳遞可能導致不確定的行為,因此要小心那些會改變外部變數的函式,並在必要時加上註解,讓其他人明白函式內部的變化。
2️⃣ 命名返回值與多重回傳值
Go 支援多重回傳值,並可使用「命名返回值」來簡化回傳變數的定義。
- 範例:命名返回值
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21package main
import "fmt"
func divide(a, b float64) (result float64, err error) {
if b == 0 {
err = fmt.Errorf("除數不能為 0")
return
}
result = a / b
return
}
func main() {
res, err := divide(10, 2)
if err != nil {
fmt.Println("錯誤:", err)
} else {
fmt.Println("結果:", res) // 輸出 5
}
}📝
result
和err
在函式內部已經預先定義,回傳時可以直接return
。
⚠️ 注意事項
🔥 指標傳遞的注意事項
指標傳遞雖然可以改變外部變數的值,但也可能引發 意想不到的副作用,需要小心:
- 指向無效記憶體(nil pointer dereference):
若指標未初始化或指向nil
,則會導致程式崩潰(panic)。1
2var ptr *int
*ptr = 10 // 會觸發 panic,因為 ptr 為 nil - 修改共享變數時需考慮同步問題(尤其在多執行緒情境):
若多個goroutine
同時修改同一個指標變數,可能會導致 競爭條件(Race Condition)。1
2
3var counter int
go func() { counter++ }() // 競爭條件
go func() { counter++ }() // 競爭條件👉 建議: 若變數需要在多執行緒間共享,應使用
sync.Mutex
或sync/atomic
來確保同步。
- 指向無效記憶體(nil pointer dereference):
📌 可變參數的影響
- 可變參數會產生新的 slice,可能影響效能
1
2
3func example(args ...int) {
fmt.Println(len(args)) // args 其實是 `[]int`
}📝 每次傳遞可變參數時,Go 會建立一個 新的 slice,若頻繁使用,可能會有額外的 記憶體分配開銷。
- 當傳入
slice
時,需注意slice
的可變性1
2
3
4
5
6
7
8
9func modify(s ...int) {
s[0] = 100
}
func main() {
nums := []int{1, 2, 3}
modify(nums...)
fmt.Println(nums) // 可能輸出 [100 2 3]
}👉 注意:
s ...int
其實是slice
,因此函式內部修改s[0]
會影響原始nums
。
○ 空白標識符 _
的誤用
- 避免忽略錯誤(Error)
1
2value, _ := strconv.Atoi("abc") // 忽略錯誤
fmt.Println(value) // 預設為 0,但我們不知道是否發生錯誤👉 建議: 除非確定錯誤不影響邏輯,否則應該妥善處理錯誤回傳值。
- 可能導致變數未使用的錯誤
1
2
3func main() {
_, name := getValues() // 變數 `name` 未使用
}👉 解法: 若
name
沒有用到,可以直接忽略所有回傳值_ = getValues()
,或使用fmt.Println(name)
避免編譯錯誤。
🎯總結
- 參數 可以簡化定義,或使用 可變參數(…) 接收多個值。
- 空白標識符
_
可用於忽略回傳值,避免變數未使用的警告。 - 指標傳遞 可用於修改外部變數,而 值傳遞 則不會影響原變數。
- 多重回傳值 讓函式能夠返回多個結果,並可使用 命名返回值 簡化回傳邏輯。
這些技巧能幫助我們更靈活地運用 Go 的函式機制,讓程式更具可讀性與擴展性!
最後建議回顧一下 Go | 菜鳥教學 目錄,了解其章節內容。
註:以上參考了
Go