[Golang] function & method 函式與方法
函式宣告
最常見的宣告方式
使用 func
來定義函式:
// 完整寫法:func add(x int, y iny) int {...}
func add(x, y int) int {
return x + y
}
使用 expression 方式定義
也可以使用 :=
來定義函式:
func main() {
add := func(x, y int) int {
return x + y
}
}
匿名函式(anonymous function)
常用在 goroutine 或該 function 只會使用一次的情況:
// 定義一個匿名函式並直接執行
func main() {
// 沒有參數
func() {
fmt.Println("Hello anonymous")
}()
// 有參數
func(i, j int) {
fmt.Println(i + j)
}(1, 2)
}
在函式中帶入參數
一般來說,對於基本資料型別來說,帶入的參數都會是複製一個 value 後傳進去:
type Person struct {
Name string
Age int
}
func changeName(person Person, newName string) {
fmt.Println("name before", person.Name) // name before Aaron
person.Name = newName
fmt.Println("name after", person.Name) // name after Chen
}
func main() {
myPerson := Person{
Name: "Aaron",
Age: 32,
}
changeName(myPerson, "Chen")
// 這裡的 person.Name 還是 "Aaron"
fmt.Printf("this is my person %+v", myPerson) // this is my person {Name:Aaron Age:32}
}
然而,如果傳入的資料型別是 "reference type" 的話(例如,slice、map、channel、function),它雖然一樣會複製,但複製到的只是指稱到底層的 pointer,因此,如果對這個資料進行修改,會直接改動到原本的資料:
func changeProductName(products []string, idx int, name string) {
products[idx] = name
fmt.Println(products[idx]) // phone
}
func main() {
products := []string{"toy", "book", "cookie"}
fmt.Println(products[0]) // toy
changeProductName(products, 0, "phone")
// 這裡拿到的 value 會是 mutate 後的值
fmt.Println(products[0]) // phone
}
如果對 slice 中的資料進行修改,它是會直接改動到原本 slice 中的資料。之所以會這樣,是因為 slice 其實只是一個 pointer,這個 pointer 可以連結到底層的 array,所以這裡如果我們對這個 slice 進行資料的操作,會改動到的其實是底層的 array。
函式本身可以是另一個函式參數,也可以是回傳值
在 golang 中,函式本身可以是另一個函式的參數:
// cb 這個參數是一個函式
func cycleNames(names []string, cb func(string)) {
for _, name := range names {
cb(name)
}
}
func sayBye(name string) {
fmt.Printf("Goodbye %v \n", name)
}
func main() {
names := []string{"Aaron", "John", "Kelly"}
// 在參數中帶入函式
cycleNames(names, sayBye)
}
範例二:
// compute 這個函式可接收其他函式作為參數
func compute(fn func(float64, float64) float64) float64 {
return fn(3, 4)
}
func main() {
// sqrt(a^2 + b^2)
hypot := func(x, y float64) float64 {
return math.Sqrt(x*x + y*y)
}
fmt.Println(compute(hypot)) // 5, hypot(3, 4)
fmt.Println(compute(math.Pow)) // 81,math.Pow(3, 4)
}
函式也可以在執行後回傳另一個函式(閉包):
func fibonacci() func() int {
// ...
}
範例程式碼:
// fibonacci is a function that returns
// a function that returns an int.
func fibonacci() func() int {
position := 0
cache := map[int]int{}
return func() int {
position++
if position == 1 {
cache[position] = 0
return 0
} else if position <= 3 {
cache[position] = 1
} else {
cache[position] = cache[position-2] + cache[position-1]
}
return cache[position]
}
}
func main() {
f := fibonacci()
for i := 0; i < 10; i++ {
fmt.Println(f())
}
}
適用閉包(closure)的概念
在 golang 的函式同樣適用閉包的概念,可以利用閉包把某個變數保留起來:
func adder() func(int) int {
sum := 0
return func(x int) int {
sum += x
return sum
}
}
func main() {
// pos 中的 sum 和 neg 中的 sum 是不同變數
pos, neg := adder(), adder()
for i := 0; i < 10; i++ {
fmt.Println(pos(i), neg(-2*i))
}
}
透過 structure 增加參數的可擴充性
如果原本的 function 只需要兩個參數,可以這樣寫:
func add(x, y int) int {
return x, y
}
func main() {
fmt.Println(add(1 ,2))
}
但若今天我們需要添加一個參數時,變成要去修改所有使用到這個 func 的地方,需要把新的參數給帶進去,例如:
package main
import "fmt"
func add(x, y, z int) int {
return x, y
}
func main() {
// 每一個使用到 add 這個函式的地方都要使用
fmt.Println(add(1 ,2, 3))
}
這樣的函式很難擴充參數的使用,因此比較好的作法是去定義 struct。如此,未來如果參數需要擴充,只需要改動 struct
和 func
內就好,不用去改動使用這個 function 地方的參數:
// STEP 1:定義參數的 structure
type addOpts struct {
x int
y int
z int // STEP 4:如果新增一個參數
}
// STEP 2:把參數的型別指定為 structure
func add(opts addOpts) int {
// STEP 5:接收新的參數 z
// return opts.x + opts.y
return opts.x + opts.y + opts.z
}
// STEP 3:使用 add,參數的地方使用 structure
func main() {
// STEP 6:不用改用舊有參數的寫法
result := add(addOpts{
x: 10,
y: 5
})
newResult := add(addOpts{
x: 10,
y: 5,
z: 7
})
}