先介紹一下go語言的類型系統
Golang中的類型系統
類型系統是指一個語言的類型體繫結構。一個典型的類型系統通常包含如下基本內容:
基礎類型,如byte、int、bool、float等;
複合類型,如數組、結構體、指標等;
可以指向任意對象的類型(Any類型);
值語義和引用語義;
物件導向,即所有具備物件導向特徵(比如成員方法)的類型;
介面。
Go語言中的大多數類型都是值語義,並且都可以包含對應的操作方法。在需要的時候,你可以給任何類型(包括內建類型)“增加”新方法。而在實現某個介面時,無需從 該介面繼承(事實上,Go語言根本就不支援物件導向思想中的繼承文法),只需要實現該介面 要求的所有方法即可。任何類型都可以被Any類型引用。Any類型就是空介面,即interface{}。
什麼是結構體
結構體(struct)是使用者自訂的類型,它代表若干欄位的集合,可以用於描述一個實體物件,類似java中的class,是golang物件導向編程的基礎類型。
結構體的概念在軟體工程上舊的術語叫 ADT(抽象資料類型:Abstract Data Type)。在 C++ 它也存在,並且名字也是 struct,在物件導向的程式設計語言中,跟一個無方法的輕量級類一樣。因為 Go 語言中沒有類的概念,所以在 Go 中結構體有著更為重要的地位。
如何定義一個結構體
type Coordinate struct { X, Y float32}
文法:type<Name>struct{}
上述代碼定義個一個名為Coordinate
的結構體,裡麵包括了兩個float32的變數X
,Y
,該結構體可用於表示一個平面座標。
添加對象方法
其他的經典物件導向語言,如java,C#,定義對象方法時,會包含在class的定義內,如
public class Coordinate{ public float X {get; set;} public float Y {get; set;} //列印座標 public void GetCoordinate(){ Console.WriteLine("("+this.X+","+this.Y+")"); }}
在go語言中,對象方法在結構體定義的外部添加
type Coordinate struct { X, Y float32}//列印座標func (coo *Coordinate) GetCoordinate() { fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y) return}
其中,func
關鍵字後面的(coo *Coordinate)
,表示該函數傳入一個指向Coordinate
的指標,可通過指標變數coo
來操作結構體的值。
幾種結構體初始化
一、按原始欄位順序通過建立結構體
package mainimport ( "fmt")func main(){ p0 := Coordinate{1, 2} p0.GetCoordinate()}
輸出:(1.00,2.00)
,其中X=1,Y=2
二、按照自訂欄位順序進行初始化
package mainimport ( "fmt")func main(){ p0 := Coordinate{Y:1, X:2} p0.GetCoordinate()}
輸出:(2.00,1.00)
,其中X=2,Y=1
三、通過new函數建立
package mainimport ( "fmt")func main(){ //給該結構體p2變數分配記憶體,它返回指向已指派記憶體的指標 p0 := new(Coordinate) p0.X=1 p0.Y=2 p0.GetCoordinate()}
輸出:(1.00,2.00)
,其中X=1,Y=2
其中p0 := new(Coordinate)
等價於以下寫法
p3 := &Coordinate{X: 1, Y: 2}
p3 := &Coordinate{1,2}
比較三種建立方式
其中,第一種與第二種,p0
均為一個類型為Coordinate
的執行個體,而第三種p0
為一個指向Coordinate
的指標,相當於var p0 *Coordinate = new(Coordinate)
一般在進行例如type T struct {a, b int}
的結構體定義之後
習慣使用t := new(T)
給該結構體變數分配記憶體,它返回指向已指派記憶體的指標。變數t
是一個指向T
的指標,此時結構體欄位的值是它們所屬類型的零值。
聲明 var t T
也會給 t
分配記憶體,並零值化記憶體,但是這個時候 t是類型T。在這兩種方式中,t 通常被稱做類型T的一個執行個體(instance)
或對象(Object)
。var t *T = new(T)
等價於t := new(T)
。
通過程式碼分析以上結論
package mainimport ( "fmt")func main(){ p0 := Coordinate{1, 2} //給該結構體p2變數分配記憶體,它返回指向已指派記憶體的指標 p2 := new(Coordinate) p2.X = 1 p2.Y = 2 p3 := &Coordinate{X: 1, Y: 2} p4 := &Coordinate{1, 2} fmt.Println("-------輸出p0-------") fmt.Printf("%v\n%T\n", p0, p0) fmt.Println("-------輸出p2-------") fmt.Printf("%v\n%T\n", p2, p2) fmt.Println("-------輸出p3-------") fmt.Printf("%v\n%T\n", p3, p3) fmt.Println("-------輸出p4-------") fmt.Printf("%v\n%T\n", p4, p4)}
輸出:
-------輸出p0-------{1 2}Coordinate-------輸出p2-------&{1 2}*Coordinate-------輸出p3-------&{1 2}*Coordinate-------輸出p4-------&{1 2}*Coordinate
可以看出來,p2,p3,p4均為一個指標變數
添加值拷貝的對象方法
剛才說到了,添加一個對象方法,可以通過func (t *T) functionname()
來建立,其中t
為一個指標變數。我們也可以通過值拷貝的方式,添加一個對象方法,文法為func(t T) functionname()
package mainimport ( "fmt")type Coordinate struct { X, Y float32}func (coo *Coordinate) GetCoordinate() { fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y) return}//值拷貝對象方法func (coo Coordinate) SetPosition01(a float32,b float32) { coo.X = a coo.Y = b}//指標變數對象方法func (coo *Coordinate) SetPosition02(a float32,b float32) { coo.X = a coo.Y = b}func main(){ p0 := Coordinate{1, 2} fmt.Print("SetPosition02調用前:") p0.GetCoordinate() p0.SetPosition02(0, 0) fmt.Print("SetPosition02調用後:") p0.GetCoordinate()}
輸出:
SetPosition01調用前:(1.00,2.00)SetPosition01調用後:(1.00,2.00)SetPosition02調用前:(1.00,2.00)SetPosition02調用後:(0.00,0.00)
從程式輸出中可以看出,調用SetPosition01
方法,發生了值拷貝,即使在方法內改變了coo
的值,外部的p0
的值沒有被改變。而SetPosition02
方法中,coo
為指向p0
地址的指標,由於是通過指標變數修改了X,Y
的值,所以調用完畢後,外部p0
的值會被修改為(0,0)
匿名結構體
package mainimport ( "fmt")func main(){ p_3d := struct { X, Y, Z float32 }{1, 2, 3} fmt.Println("-------輸出p_3d-------") fmt.Printf("%v\n%T\n", p_3d, p_3d)}
輸出:
-------輸出p_3d-------{1 2 3}struct { X float32; Y float32; Z float32 }
p_3d
為一個包含X,Y,Z
三個變數的匿名結構體
golang建構函式?
在Go語言中沒有建構函式的概念,對象的建立通常交由一個全域的建立函數來完成,以 NewXXX
來命名,表示“建構函式”:
這一切非常自然,開發人員也不需要分析在使用了new之後到底背後發生了多少事情。在Go語言中,一切要發生的事情都直接可以看到。
—— 《Go語言編程》
func NewRect(x, y, width, height float64) *Rect { return &Rect{x, y, width, height}}
變數、方法可見度
Go語言對關鍵字的增加非常吝嗇,其中沒有private
、protected
、public
這樣的關鍵 字。要使某個符號對其他包(package
)可見(即可以訪問),需要將該符號定義為以大寫字母開頭,如:
type Rect struct { X, Y float64 Width, Height float64 }
這樣,Rect類型的成員變數就全部被匯出了,可以被所有其他引用了Rect所在包的代碼訪問到。 成員方法的可訪問性遵循同樣的規則,例如:
func (r *Rect) area() float64 { return r.Width * r.Height}
這樣,Rect
的area()
方法只能在該類型所在的包內使用。
需要注意的一點是,Go語言中符號的可訪問性是包一級的而不是類型一級的。在上面的例 子中,儘管area()
是Rect
的內部方法,但同一個包中的其他類型也都可以訪問到它。這樣的可訪問性控制很粗曠,很特別,但是非常實用。如果Go語言符號的可訪問性是類型一級的,少不 了還要加上friend這樣的關鍵字,以表示兩個類是朋友關係,可以訪問彼此的私人成員。