Go語言進階之路(三):函數和接口

上一期我們說到了《 》,這一期來聊一下Go語言的函數和接口,看看它和Java、Python有什麼異同點。

一 函數

Go語言的函數用關鍵字func來定義,函數可以有返回值也可以沒有返回值,有返回值的話,返回值寫在函數參數的後面:

<code>// 沒有返回值的函數,函數參數是int類型的a和b
func myFunc(a, b int){ // a和b參數同類型
xxx
}/<code>

帶返回值的函數,還可以給返回值命名:

<code>func change(a, b int) (x, y int) {
x = a + 100
y = b + 100
return //101, 102
//return x, y //同上
//return y, x //102, 101
}

func main(){
a := 1
b := 2
c, d := change(a, b)
println(c, d)
}/<code>

可變參數函數

Go語言支持可變參數函數,和Java中一樣,都是用三個點...來表示參數個數是可變的,可以看下面的例子。另外,可變參數的類型其實是切片,跟Java一樣,可變參數其實就是個語法糖。

<code>func sum(nums ...int) {
fmt.Println(reflect.TypeOf(nums)) // 輸出:[]int

fmt.Println(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
sum(1, 2) // [1 2] 3
sum(1, 2, 3) // [1 2 3] 6
nums := []int{1, 2, 3, 4}
sum(nums...) // [1 2 3 4] 10
}/<code>

方法

一般的,我們把普通定義的函數叫做函數,定義給結構體的函數叫做方法。比如:

<code>func func1(a, b int) int {  // 函數
xxx
}


type Animal struct {
name string
age int
}

func (animai Animal) func2(a string, b int) (string, int) { // 方法
xxx
}/<code>

這裡我們就把func1稱作函數,func2稱作方法。

匿名函數

Go語言支持匿名函數,和JavaScript中一樣,我們可以把函數賦值給一個變量,也可以直接創建一個匿名函數立即執行。

我們把上面的可變參數函數例子改一下,把函數賦值給變量func1,然後調用func1函數:

<code>var func1 = func(nums ...int) {
fmt.Println(reflect.TypeOf(nums)) // 輸出:[]int
fmt.Println(nums, " ")
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total)
}
func main() {
func1(1, 2) // [1 2] 3
func1(1, 2, 3) // [1 2 3] 6
nums := []int{1, 2, 3, 4}
func1(nums...) // [1 2 3 4] 10
}/<code>

我們創建一個匿名函數立即執行:

<code>func(nums ...int) {
fmt.Println(reflect.TypeOf(nums)) // 輸出:[]int
fmt.Println(nums, " ") // 輸出:[1 2 3]
total := 0
for _, num := range nums {
total += num
}
fmt.Println(total) // 輸出:6
}(1, 2, 3)/<code>

創建個匿名函數用於goroutine:

<code>var jobs = make(chan int, 5)
var done = make(chan bool)

var func1 = func() {
for {
j, more := if more {
fmt.Println("received job", j)
} else {
fmt.Println("received all jobs")
done return
}
}
}
func main() {
go func1() // 創建goroutine立即執行

}/<code>

後面會有專門的文章詳解goroutine和通道。

二 接口

Go語言不是專門為面向對象設計的語言,但是Go語言也能實現面向對象的功能。在Go語言中,接口類型用interface關鍵字。比較特殊的是,Go語言中任何數據、任何對象都是interface類型的。Go語言中的interface類型就像Java中的Object類一樣。

定義接口

<code>type live interface{  // 定義一個接口
eat()
sleep()
}/<code>

定義一個表示“生活”的接口,有“吃”函數和“睡”函數。

實現接口

我們定義一個“人”類型,來實現“生活”接口:

<code>type person struct {
name string
age int
}

func (p person) eat() {
fmt.Println("eat something")
}

func (p *person) sleep() {
fmt.Println("sleep sometimes")
}/<code>

可以看到,我們的person類型實現了eat函數和sleep函數。實現方法eat時候,我們把p person寫在func關鍵字後面、eat方法前面,這樣,我們就把p person作為了方法eat的接收者,換句話說,person類型實現了接口的eat函數。

當一個類型實現了一個接口的所有函數,那我們就叫做這個類型實現了這個接口。這樣,我們創建一個person類型的變量,然後調用它的接口方法就可以了。

<code>var p = person{}
p.eat() // 輸出:eat something
p.sleep() // 輸出:sleep sometimes/<code>

指針方法和值方法

等等,上面這個例子我們好像看到了點奇怪的東西,eat函數接收者是person類型,sleep函數接收者是指針類型。有什麼區別?

從函數調用方式上看,這兩者並沒有什麼區別。我們可以使用person類型和person的指針類型調用任意一個方法:

<code>type person struct {
name string
age int
}
func (p person) eat() {
fmt.Println("eat something")
}

func (p *person) sleep() {
fmt.Println("sleep sometimes")
}
func main() {

var p1 = person{}
p1.eat() // 輸出:eat something
p1.sleep() // 輸出:sleep sometimes
var p2 = &person{}
p2.eat() // 輸出:eat something
p2.sleep() // 輸出:sleep sometimes
}/<code>

其實,區別在於,用指針類型作為接收者,可以在函數/方法內部修改這個接收者的數據,而用值類型作為接收者,在函數/方法內部不能修改原接收者的數據。看下面的例子就明白了:

<code>type person struct {
name string
age int
}
func (p person) eat() {
p.name = "eat"
fmt.Println("eat something")
}

func (p *person) sleep() {
p.age = 28
fmt.Println("sleep sometimes")
}
func main() {
var p1 = person{}
fmt.Println(p1) // 輸出:{ 0}
p1.eat() // 輸出:eat something
p1.sleep() // 輸出:sleep sometimes
fmt.Println(p1) // 輸出:{ 28}
var p2 = &person{}
fmt.Println(p2) // 輸出:&{ 0}
p2.eat() // 輸出:eat something
p2.sleep() // 輸出:sleep sometimes
fmt.Println(p2) // 輸出:&{ 28}
}/<code>

可以看到,p2是指針類型,在調用值方法eat時,eat方法內部修改的name屬性也沒有反映到p2上面。其實,這和我們調用函數時,把切片作為函數參數一樣。在調用函數/方法時,接收者對象也會被複制一份,然後再調用函數/方法。對於值類型,直接就複製了一份數據,所以修改不到原來的值類型接收者;對於指針類型,複製的是指針,因此我們可以在函數/方法內部修改到原接收者的數據。

多態

提到接口,當然要提到Java中非常流行的多態。Go語言也可以實現多態,看下面的例子:

<code>type animal interface {
eat()
sleep()
}

type person struct {
name string
age int
}

func (p person) eat() {
fmt.Println("person eat something")
}

func (p person) sleep() {
fmt.Println("person sleep sometimes")
}

type cat struct {
name string
age int
}

func (p cat) eat() {
fmt.Println("cat eat something")
}

func (p cat) sleep() {
fmt.Println("cat sleep sometimes")
}
func main() {
var a animal = person{"person", 28}
a.eat() // 輸出:person eat something
a.sleep() // 輸出:person sleep sometimes
a = cat{"cat", 5}
a.eat() // 輸出:cat eat something
a.sleep() // 輸出:cat sleep sometimes
}/<code>

person類型和cat類型都實現了animal接口,創建animal類型變量a,把person類型變量和cat類型變量賦值給a,調用方法時,會多態執行到具體實現上。

空接口/任意類型接口

我們知道,interface{}類型就像Java裡面的Object類一樣,同時Go語言又具有多態的特性,那麼我們可以使用空接口變量來存放任意類型數據。比如:

<code> var a interface{} = 2
fmt.Println(a) // 輸出:2/<code>

我們來創建一個可以存放任意類型的map:

<code>func main() {
var m = make(map[string]interface{})
m["a"] = 1
m["b"] = "b"
m["c"] = []int{1, 2}
fmt.Println(m) // 輸出:map[a:1 b:b c:[1 2]]
}
/<code>

下一期我們聊一下Go語言中的Go語言中的標準錯誤和異常。

喜歡的可以關注我的訂閱號【程序猿架構】,更快的獲取高質量文章


分享到:


相關文章: