先介紹一下go語言的類型系統(tǒng)
Golang中的類型系統(tǒng)
類型系統(tǒng)是指一個語言的類型體系結(jié)構(gòu)。一個典型的類型系統(tǒng)通常包含如下基本內(nèi)容:
? 基礎(chǔ)類型,如byte、int、bool、float等;
? 復(fù)合類型,如數(shù)組、結(jié)構(gòu)體、指針等;
? 可以指向任意對象的類型(Any類型);
? 值語義和引用語義;
? 面向?qū)ο螅此芯邆涿嫦驅(qū)ο筇卣?比如成員方法)的類型;
? 接口。
Go語言中的大多數(shù)類型都是值語義,并且都可以包含對應(yīng)的操作方法。在需要的時候,你可以給任何類型(包括內(nèi)置類型)“增加”新方法。而在實現(xiàn)某個接口時,無需從 該接口繼承(事實上,Go語言根本就不支持面向?qū)ο笏枷胫械睦^承語法),只需要實現(xiàn)該接口 要求的所有方法即可。任何類型都可以被Any類型引用。Any類型就是空接口,即interface{}。
什么是結(jié)構(gòu)體
結(jié)構(gòu)體(struct)是用戶自定義的類型,它代表若干字段的集合,可以用于描述一個實體對象,類似java中的class,是golang面向?qū)ο缶幊痰幕A(chǔ)類型。
結(jié)構(gòu)體的概念在軟件工程上舊的術(shù)語叫 ADT(抽象數(shù)據(jù)類型:Abstract Data Type)。在 C++ 它也存在,并且名字也是 struct,在面向?qū)ο蟮木幊陶Z言中,跟一個無方法的輕量級類一樣。因為 Go 語言中沒有類的概念,所以在 Go 中結(jié)構(gòu)體有著更為重要的地位。
如何定義一個結(jié)構(gòu)體
type Coordinate struct {
X, Y float32
}
語法:type<Name>struct{}
上述代碼定義個一個名為Coordinate
的結(jié)構(gòu)體,里面包括了兩個float32的變量X
,Y
,該結(jié)構(gòu)體可用于表示一個平面坐標(biāo)。
添加對象方法
其他的經(jīng)典面向?qū)ο笳Z言,如java,C#,定義對象方法時,會包含在class的定義內(nèi),如
public class Coordinate{
public float X {get; set;}
public float Y {get; set;}
//打印坐標(biāo)
public void GetCoordinate(){
Console.WriteLine("("+this.X+","+this.Y+")");
}
}
在go語言中,對象方法在結(jié)構(gòu)體定義的外部添加
type Coordinate struct {
X, Y float32
}
//打印坐標(biāo)
func (coo *Coordinate) GetCoordinate() {
fmt.Printf("(%.2f,%.2f)\n", coo.X, coo.Y)
return
}
其中,func
關(guān)鍵字后面的(coo *Coordinate)
,表示該函數(shù)傳入一個指向Coordinate
的指針,可通過指針變量coo
來操作結(jié)構(gòu)體的值。
幾種結(jié)構(gòu)體初始化
一、按原始字段順序通過創(chuàng)建結(jié)構(gòu)體
package main
import (
"fmt"
)
func main(){
p0 := Coordinate{1, 2}
p0.GetCoordinate()
}
輸出:(1.00,2.00)
,其中X=1,Y=2
二、按照自定義字段順序進(jìn)行初始化
package main
import (
"fmt"
)
func main(){
p0 := Coordinate{Y:1, X:2}
p0.GetCoordinate()
}
輸出:(2.00,1.00)
,其中X=2,Y=1
三、通過new函數(shù)創(chuàng)建
package main
import (
"fmt"
)
func main(){
//給該結(jié)構(gòu)體p2變量分配內(nèi)存,它返回指向已分配內(nèi)存的指針
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}
比較三種創(chuàng)建方式
其中,第一種與第二種,p0
均為一個類型為Coordinate
的實例,而第三種p0
為一個指向Coordinate
的指針,相當(dāng)于var p0 *Coordinate = new(Coordinate)
一般在進(jìn)行例如
type T struct {a, b int}
的結(jié)構(gòu)體定義之后
習(xí)慣使用t := new(T)
給該結(jié)構(gòu)體變量分配內(nèi)存,它返回指向已分配內(nèi)存的指針。變量t
是一個指向T
的指針,此時結(jié)構(gòu)體字段的值是它們所屬類型的零值。
聲明var t T
也會給t
分配內(nèi)存,并零值化內(nèi)存,但是這個時候 t是類型T。在這兩種方式中,t 通常被稱做類型T的一個實例(instance)
或?qū)ο?code>(Object)。var t *T = new(T)
等價于t := new(T)
。
通過代碼分析以上結(jié)論
package main
import (
"fmt"
)
func main(){
p0 := Coordinate{1, 2}
//給該結(jié)構(gòu)體p2變量分配內(nèi)存,它返回指向已分配內(nèi)存的指針
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()
來創(chuàng)建,其中t
為一個指針變量。我們也可以通過值拷貝的方式,添加一個對象方法,語法為func(t T) functionname()
package main
import (
"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("SetPosition01調(diào)用前:")
p0.GetCoordinate()
p0.SetPosition01(0, 0)
fmt.Print("SetPosition01調(diào)用后:")
p0.GetCoordinate()
fmt.Print("SetPosition02調(diào)用前:")
p0.GetCoordinate()
p0.SetPosition02(0, 0)
fmt.Print("SetPosition02調(diào)用后:")
p0.GetCoordinate()
}
輸出:
SetPosition01調(diào)用前:(1.00,2.00)
SetPosition01調(diào)用后:(1.00,2.00)
SetPosition02調(diào)用前:(1.00,2.00)
SetPosition02調(diào)用后:(0.00,0.00)
從程序輸出中可以看出,調(diào)用SetPosition01
方法,發(fā)生了值拷貝,即使在方法內(nèi)改變了coo
的值,外部的p0
的值沒有被改變。而SetPosition02
方法中,coo
為指向p0
地址的指針,由于是通過指針變量修改了X,Y
的值,所以調(diào)用完畢后,外部p0
的值會被修改為(0,0)
匿名結(jié)構(gòu)體
package main
import (
"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
三個變量的匿名結(jié)構(gòu)體
golang構(gòu)造函數(shù)?
在Go語言中沒有構(gòu)造函數(shù)的概念,對象的創(chuàng)建通常交由一個全局的創(chuàng)建函數(shù)來完成,以
NewXXX
來命名,表示“構(gòu)造函數(shù)”:
這一切非常自然,開發(fā)者也不需要分析在使用了new之后到底背后發(fā)生了多少事情。在Go語言中,一切要發(fā)生的事情都直接可以看到。
—— 《Go語言編程》
func NewRect(x, y, width, height float64) *Rect {
return &Rect{x, y, width, height}
}
變量、方法可見性
Go語言對關(guān)鍵字的增加非常吝嗇,其中沒有private
、protected
、public
這樣的關(guān)鍵 字。要使某個符號對其他包(package
)可見(即可以訪問),需要將該符號定義為以大寫字母開頭,如:
type Rect struct {
X, Y float64
Width, Height float64
}
這樣,Rect類型的成員變量就全部被導(dǎo)出了,可以被所有其他引用了Rect所在包的代碼訪問到。 成員方法的可訪問性遵循同樣的規(guī)則,例如:
func (r *Rect) area() float64 {
return r.Width * r.Height
}
這樣,Rect
的area()
方法只能在該類型所在的包內(nèi)使用。
需要注意的一點是,Go語言中符號的可訪問性是包一級的而不是類型一級的。在上面的例 子中,盡管area()
是Rect
的內(nèi)部方法,但同一個包中的其他類型也都可以訪問到它。這樣的可訪問性控制很粗曠,很特別,但是非常實用。如果Go語言符號的可訪問性是類型一級的,少不 了還要加上friend這樣的關(guān)鍵字,以表示兩個類是朋友關(guān)系,可以訪問彼此的私有成員。