跟著老貓來搞GO,"物件導向"

程式設計師老貓發表於2021-11-30

前言

之前和大家分享了容器以及相關的基礎語法,以及函式,相信如果大家有接觸過C++或者java的朋友都曉得物件導向,其實在GO語言中也存在物件導向,但是還是比較簡單的,下面我們來看一下GO語言的“物件導向”。

物件導向

結構體的定義

其實在GO語言中並不能準確得說是物件導向,go語言其實是面向介面函式程式設計的語言。為什麼要說成GO語言的物件導向,其實也是部分特性類似於物件導向。GO語言中的物件導向還是比較簡單的,GO語言僅支援封裝,不支援多型和繼承。語言沒有class,只有struct。

結構體本質上也是一種資料型別,它是將0個或者多個任意型別的命名變數組合在一起的聚合資料型別,其中每個變數都叫做結構體的成員。看下結構體的定義,具體如下

type Employee struct {
	ID int //id編號
	Name string //名稱
	Address string //地址
	Position string //職位
}

func main() {
	var jack Employee
	jack.Name = "jack"
	jack.Address = "shanghai"
	fmt.Println(jack.Name)
}

以上我們就定義一個員工的結構體,這個結構體包含了名稱、地址、職位等一些屬性。在main函式中,我們建立了一個Employee型別的jack,並且給予其初始化了值。上述比較簡單地,我們們可以直接用“.”的方式進行對結構體變數進行賦值以及取值,當然我們們也可以獲取成員變數的地址,然後通過指標來訪問它。如下:

func main() {
	var jack Employee
	jack.Name = "jack"
	jack.Address = "shanghai"
	jack.Position = "Engineer"
	//fmt.Println(jack.Name)

	position := &jack.Position
	*position = "Senior" + *position
	fmt.Println(*position)
}

顯然這裡的jack被升值了,變成了高階工程師。

我們們的點號其實也可以直接用在結構體的指標上,如下

func main() {
	var jack Employee
	jack.Name = "jack"
	jack.Address = "shanghai"
	jack.Position = "Engineer"
	//fmt.Println(jack.Name)

	position := &jack.Position
	*position = "Senior" + *position
	//fmt.Println(*position)

	var employeeA *Employee = &jack
	employeeA.Position = "Super" + employeeA.Position
	fmt.Println(employeeA.Position)
}

還沒理解透徹指標的小夥伴可以會有點懵,後面老貓還是專門把指標說一下。上面的那個步驟,我們只是獲取了jack的職位並通過指標將其重新賦值升級,那麼下面,其實我們們就定義了一個Employee的指標,並且這個指標指向的是jack這個結構體,那麼針對我們的employeeA這個員工指標就能獲取其結構體中所有的屬性,並且將其重新賦值。

當然我們甚至可以定義指標型別的結構體函式,當然,其返回值必須是某個結構體的地址,具體定義如下:

func EmployeeByID(id int) *Employee {
	var json Employee
	json.ID = id
	json.Name = "json"
	json.Address = "beijing"
	json.Position = "Engineer"
	return &json
}

上述,我們們主要介紹了結構體以及指標的相關的用法,那麼關於結構體的話還有哪些注意點呢?

  • 成員變數的順序對於結構體同一性很重要,如果我們將上面的Employee結構體的屬性進行順序顛倒調換,那麼我們就說定義了另外一個不同型別的結構體。
  • 關於GO結構中定義變數的大小寫,大家可以看到,老貓上述定義的都是以大寫字母開頭的,因為只有以大寫字母開頭定義的屬性,才能夠被外圍訪問。大家可以手動敲一下程式碼體驗一下。這個也是GO最主要的訪問控制機制。
  • 結構體型別不可以定義一個擁有相同結構體型別s的成員變數,也就是一個聚合型別不可以包含它自己。

這個是什麼意思呢?我們們來看一下例子!
編譯出錯

上圖我們可以看到,如果結構體中套有自身是會報編譯錯誤的。但是Employee中可以定義個S的指標型別。例如下面則是OK的

type Employee struct {
	ID int
	Name string
	Address string
	Position *Employee
}

所以我們們就可以利用這種形式來做遞迴結構的定義,例如連結串列或者樹的定義我們們就可以這麼來定義

type tree struct {
	value int
	left,right *tree
}

結構體的字面量

這裡說的字面量就是結構體中的值,我們結構體型別中的值可以通過結構體字面量來進行設定。如下,有兩種結構體字面量。

第一種

type Point struct {X,Y int}
p:=Point{1,2}

這種方式要求按照正常順序為每個成員變數進行賦值,很顯然,如果結構體比較簡單的時候無所謂,但是一旦結構體之後隨著業務的演化變得相當複雜的時候,程式碼的可維護性就變得相當差了。

第二種

type Point struct {X,Y int}
p:=Point{X:1,Y:2}

這種方式顯然會比較清晰一些,但是需要注意的是如果其中某個成員變數沒有指定的值的話,那麼其值預設就為零值。由於指定了成員變數的名字,在這種方式中相當於第一種而言,這裡的順序就無所謂了。

結構體的比較

如果結構體的所有成員變數都可以比較,那麼這個結構體就是可以比較的,兩個結構體的比較直接使用==或者!=即可。

p:=Point{1,2}
q:=Point{2,1}
e:=Point{1,2}
fmt.Println(q.x == p.x) //成員函式比較 false
fmt.Println(p == q) //整體比較 false
fmt.Println(p == e) // true

在面嚮物件語言中,例如java,在我們比較兩個物件值的時候需要去比較兩個物件的hash值,甚至需要重寫equals方法,我們在這裡看到的go語言的結構體物件的比較就很簡單明瞭了。這裡不多贅述,還是希望大家能夠多寫寫。

結構體的巢狀機制

結構體的巢狀機制可以讓我們將一個命名結構體當做另一個結構體型別的匿名成員來使用。這句話可能有點不好理解,我們還是來直接看一下例子。

首先我們們來定義一個圓,圓的話包含了圓心的座標以及相關的半徑,由此,我們們可以抽象出如下程式碼

type Circle struct {
	X,Y,Radius int
}

在我們的日常生活中,輪子也是圓形的,輪子可能多一些條幅數,由此,我們們輪子也可以抽象一下

type Wheel struct {
    X,Y,Radius,Spokes int
}

看到上述兩個,我們們其實可以發現這兩個結構體中有挺多相同的成員變數,那我們們是不是可以再度抽象一下,於是我們們就抽象成了如下:

type Circle struct {
	Center Point
	Radius int
}

type Wheel struct {
	Circle Circle
	Spokes int
} 

這樣,我們們就會發現整個程式看起來變得更加清晰。其實這也是更好地說明了結構體說白了也是一種特殊的資料型別而已。

寫在最後

本篇中其實和大家粗略分享了結構體的相關知識,有java相關面嚮物件語言經驗的小夥伴會發現,結構體和麵向物件語言中的類比較相像,但是GO語言中的結構體的用法相比之下會簡單得多。

我是老貓,更多內容,歡迎大家搜尋關注老貓的公眾號“程式設計師老貓”。

相關文章