是時候開始研究函式和方法了。。。
函式
通過函式,可以把開發任務分解成一個個小的單元,這些小單元可以被其他單元複用,進而提高開發效率、降低程式碼重合度。
1. 函式宣告
func funcName(params) result {
body
}
- 關鍵字 func
- 函式名字 funcName
- 函式的引數 params,用來定義形參的變數名和型別
- result 是返回的函式值,用於定義返回值的型別,如果沒有可以省略
- body 就是函式體,可以在這裡寫函式的程式碼邏輯
寫一個計算兩數相加的函式:
package main
import (
"fmt"
)
// 計算兩值之和
// 變數名稱在前,變數型別在後
// 變數名稱叫做引數名稱,也就是函式的形參
func addTwoNum(a int, b int) int{
return a + b
}
func main(){
fmt.Println(addTwoNum(12, 21))
}
執行結果為:
2. 多值返回
Go 語言的函式可以返回多個值,也就是多值返回
第一個值返回函式的結果,第二個值返回函式出錯的資訊
package main
import (
"errors"
"fmt"
)
// 計算兩值之和,如果為負數就返回錯誤
func addTwoNum(a int, b int) (int, error) {
if a < 0 || b < 0 {
return 0, errors.New("a或者b不能為負數")
}
return a + b, nil
}
func main() {
// 獲取結果和錯誤資訊
a, err := addTwoNum(-12, 21)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("計算結果為:", a)
}
}
執行結果為:
3.命名返回引數
函式的返回值也可以有變數名稱,這個並不常用,瞭解一下
改造函式為:
package main
import (
"errors"
"fmt"
)
// 計算兩值之和,如果為負數就返回錯誤
func addTwoNum(a int, b int) (sum int, err error) {
if a < 0 || b < 0 {
// 這裡按照正常進行返回
return 0, errors.New("a或者b不能為負數")
}
// 這裡按照返回值給相關引數賦值,return後面不需要任何引數
sum = a + b
err = nil
return
}
func main() {
// 獲取結果和錯誤資訊
a, err := addTwoNum(-12, 21)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("計算結果為:", a)
}
}
執行結果為:
4. 可變引數
可變引數,就是函式的引數數量是可變的
如果函式中既有普通引數又有可變引數,那麼可變引數一定要放到引數列表的末尾
主要就是在引數型別前面新增三個點
例如:
// 沒有引數
fmt.Println()
// 一個引數
fmt.Println("zhouzhaodong")
// 兩個引數
fmt.Println("zhouzhaodong","xiaoqiang")
我們寫一個計算所有數字之和的函式:
package main
import (
"fmt"
)
// 計算所有值之和
func addAllNum(params ...int) int {
sum := 0
for _, i := range params {
sum += i
}
return sum
}
func main() {
fmt.Println("計算結果為:", addAllNum(1, 2, 3, 3, 3, 4, 5, 9))
}
執行結果為:
5. 包級函式
不管是自定義的函式
還是我們使用到的函式,都會從屬一個包
也就是 package
不同包的函式要被呼叫,那麼函式的作用域必須是公有的,也就是函式名稱的首字母要大寫
- 函式名稱首字母小寫代表私有函式,只有在同一個包中才可以被呼叫
- 函式名稱首字母大寫代表公有函式,不同的包也可以呼叫
- 任何一個函式都會從屬於一個包
6. 匿名函式和閉包
匿名函式就是沒有名字的函式
package main
import (
"fmt"
)
func main() {
sum := func(a, b int) int {
return a + b
}
fmt.Println("計算結果為:", sum(1, 2))
}
執行結果為:
在函式中再定義函式(函式巢狀),定義的這個匿名函式,也可以稱為內部函式
更重要的是,在函式內定義的內部函式,可以使用外部函式的變數等,這種方式也稱為閉包
方法
方法必須要有一個接收者,這個接收者是一個型別
這樣方法就和這個型別繫結在一起,成為這個型別的方法
接收者的定義和普通變數、函式引數等一樣
前面是變數名,後面是接收者型別
package main
import (
"fmt"
)
// 定義一個新的型別,該型別等價於 uint
// 可以理解為 uint 的重新命名
type Age uint
// 定義一個方法,引數就是Age
func (age Age) String() {
fmt.Println("the age is", age)
}
func main() {
age := Age(25)
age.String()
}
執行結果為:
接收者就是函式和方法最大的不同
1. 值型別接收者和指標型別接收者
定義的方法的接收者型別是指標,所以我們對指標的修改是有效的,如果不是指標,修改就沒有效果,如下所示:
package main
import (
"fmt"
)
// 定義一個新的型別,該型別等價於 uint
// 可以理解為 uint 的重新命名
type Age uint
// 定義一個方法,引數就是Age
func (age Age) String() {
fmt.Println("the age is", age)
}
// 定義一個方法,引數就是Age指標
func (age *Age) Modify() {
*age = Age(30)
}
func main() {
age := Age(25)
age.String()
// 修改age的值
age.Modify()
age.String()
}
執行結果為:
提示:在呼叫方法的時候,傳遞的接收者本質上都是副本,只不過一個是這個值副本,一是指向這個值指標的副本。指標具有指向原有值的特性,所以修改了指標指向的值,也就修改了原有的值。我們可以簡單地理解為值接收者使用的是值的副本來呼叫方法,而指標接收者使用實際的值來呼叫方法。
這就是 Go 語言編譯器幫我們自動做的事情:
- 如果使用一個值型別變數呼叫指標型別接收者的方法,Go 語言編譯器會自動幫我們取指標呼叫,以滿足指標接收者的要求。
- 如果使用一個指標型別變數呼叫值型別接收者的方法,Go 語言編譯器會自動幫我們解引用呼叫,以滿足值型別接收者的要求。
總之,方法的呼叫者,既可以是值也可以是指標,不用太關注這些,Go 語言會幫我們自動轉義,大大提高開發效率,同時避免因不小心造成的 Bug。
不管是使用值型別接收者,還是指標型別接收者,要先確定你的需求:在對型別進行操作的時候是要改變當前接收者的值,還是要建立一個新值進行返回?這些就可以決定使用哪種接收者。