,,繼承的特性子類Go-?Go

2023-11-26    分類: 網站建設

面向對象的編程風格在開發(fā)者中非常流行,尤其以C++和Java為代表的編程語言風靡一時!

有趣的是,這兩種語言幾乎不出所料都是從C語言衍生而來,但是它們不同于C的面向過程編程。這種面向對象的編程風格給開發(fā)者帶來了極大的方便,解放了勞動,松耦合,高內聚也成為了設計標準,讓我們可以更愉快的復制粘貼,成為代碼的搬運工。很多第三方工具都是開箱即用的,語義清晰,職責明確,都是面向對象的。編程的好處!

Go 語言也是從 C 語言派生而來的。不知道大家是否也好奇Go語言是否支持面向對象的編程風格?

準確地說,Go 支持面向對象編程,而不是面向對象的語言!

不,它和薛定諤的貓一樣不確定嗎?

其實這個答案是官方的答案,不是我自己憑空捏造出來的。詳情請參考Is Go an-?

為什么這么說?

Go 支持封裝,但不支持繼承和多態(tài),所以嚴格按照面向對象規(guī)范,Go 語言不是面向對象的編程語言。

然而,Go 提供的接口是一種非常易于處理且更通用的方式。雖然它在表達上與其他主流編程語言略有不同,甚至無法實現多態(tài),但 Go 的接口不僅適用于結構。 body,它也可以應用于任何數據類型,這無疑是非常靈活的!

比較有爭議的是繼承。由于沒有關鍵字支持繼承特性,所以沒有繼承的痕跡。雖然有一些方法可以將類型嵌入到其他類型中來實現子類化,但這并不是真正的繼承。

因此,Go 既支持面向對象的編程風格,又不完全是面向對象的編程語言。

如果換個角度看問題,正是因為沒有繼承,Go比面向對象的編程語言更輕量級。您可能希望考慮繼承特性,子類和父類之間的關系,單繼承或多繼承。訪問控制權限等問題!

按照面向對象的編程規(guī)范,實現封裝特性的部分應該是類和對象,但是這個概念和實現語言的關鍵詞是分不開的,但是Go沒有關鍵詞而是C語言 關鍵字,所以調用類或者對象不是很合適,所以下面的解釋過程還是采用這種結構!

如何定義結構

關鍵字聲明結構,屬性之間的回車和換行。

例如下面例子中定義了動態(tài)數組結構,下面例子中將使用動態(tài)數組結構作為演示對象。

type MyDynamicArray struct {
	ptr *[]int
	len int
	cap int
}

在Go語言中定義對象的多個屬性時,用直接換行代替分號來分隔?為什么它與其他主流編程語言不同?

對于習慣了分號結尾的開發(fā)者來說,他們可能有一段時間不習慣 Go 的這種語法,所以他們決定探索 Go 編程規(guī)范!

如果手動添加分號,編輯器會提示分號重復,所以我猜可能是Go編譯器自動添加了分號,用分號作為語句語句的分隔符。手動添加分號后,Go忽略了或者添加了分號,所以報了上面的錯誤。

這樣做有什么好處?

不是自己加分號,編譯器無條件加分號的結果,更何況其他主流編程語言都是手動加分號的!

當有多個屬性時,可以直接換行,不用加分號作為分隔符。對于從來沒有接觸過編程語言的小白來說,可能會省事,但是對于有編程經驗的開發(fā)者來說,要記住不能加分號,真的很吵!

如果多個屬性寫在一行,則沒有換行符。我看你怎么區(qū)分它們。這個時候應該用逗號還是分號隔開?

首先,空格不能分隔多個屬性,所以試試分號或逗號。

根據提示提示需要分號或換行符,換行符是標準形式,試試分號能不能分開?

此時編輯器不會報錯或警告,所以一行上的多個屬性應該用分號隔開,這意味著Go編譯器識別多個屬性還是和其他主流編程語言一樣。用數字隔開,但開發(fā)者不能用!

和上面的規(guī)則類似,記憶很簡單,驗證也比較容易。難點在于理解為什么?

為什么 Go 是這樣設計的?或者如何理解這種設計思想所代表的語義?

Go 作為一種新的編程語言,不僅體現在具體的語法差異上,更重要的是編程思想的特殊性。

就像面向對象中的接口概念一樣,設計者只需要定義抽象的行為,并不關心行為的具體實現。

如果我們也用這種思維去理解不同的編程語言,那么就可以通過現象看本質,否則真的很容易陷入語法細節(jié),進而可能會忽略背后的核心思想。

其實對于結構的多屬性分隔符,其實不管用什么作為分隔符,逗號或者句號都可以,只要編譯器能識別出這是一個不同的屬性。

因為大多數主流編程語言一般都是用分號作為分隔符,開發(fā)者需要手動寫分隔符讓編譯器識別,但是Go語言不這么認為,算了,直接換行,我也能識別出來out(雖然底層的 Go 編譯器在編譯時仍然使用分號來表示換行)!

加不加分號,對于開發(fā)者來說,只是一個分隔多個屬性的標志。如果不加就可以實現,為什么還要加?

這三個基本問題是什么、為什么和如何。如果簡單易學、易懂,學什么、怎么學就夠了,但這樣學、學就難免會出現自治的局面。也就是說,各種編程語言之間沒有關系,每種語言都是獨立的?!

世界上有千萬種語言,編程語言也有很多。學一門新語言不使用舊語言,學一門新語言和春小白有什么區(qū)別?

學習就是學習,可惜對舊語言沒有幫助,也沒有加深對舊語言的理解。這只是對一種全新語言的純粹學習。

語言是由進化創(chuàng)造的。它不是空中樓閣。它建立在現有系統(tǒng)的基礎上,逐步發(fā)展和演進。任何新語言或多或少都會找到舊語言的影子。

那何不嘗試一下,弄清楚新語言設計的初衷和設計過程中面臨的問題,然后再看語言是如何解決問題的。求解的過程稱為實現細節(jié)。我覺得這種方式應該是更好的學習方式!

雖然你不能在語言設計環(huán)境中,也不一定了解語言設計面臨的挑戰(zhàn),但先問并試著問為什么,你能不能不這樣設計等等,應該是一個好的開始。

所以接下來的文章將采用語義分析的角度,嘗試理解Go語言背后的原始設計,并通過大量的輔助測試來驗證猜想。不再是簡單的知識羅列過程,當然必要的知識歸納還是很重要的,這個自然不會放棄。

既然已經定義了動態(tài)數組,也就是設計者的工作暫時告一段落了。作為用戶,我們如何使用我們的動態(tài)數組?

根據面向對象的術語,從類創(chuàng)建對象的過程稱為實例化。但是,我們已經知道 Go 并不是一個完整的面向對象語言,所以為了避免使用面向對象的技術術語盡可能多地引用 Go 的實現細節(jié),我們可以暫時將其理解為結構類型和結構變量。隨著以后學習的深入,我們可能會對這部分有更深入的了解。

func TestMyDynamicArray(t *testing.T){
	var arr MyDynamicArray
	// { 0 0}
	t.Log(arr)
}

以上寫法沒有特別強調。它完全是使用之前文章中介紹過的語法規(guī)則來實現的。 var arr 表示聲明類型的變量 arr。此時直接打印變量的值,結果為{0 0}。

最后兩個值

都是0,自然容易理解,因為我們在Go語言中解釋變量的時候已經介紹過了。 Go的變量類型默認初始化有一個對應的0值,而int類型的len cap屬性自然是0,而ptr *[]int是數組的指針,所以是nil。

等等,有些不對勁。這里有一個設計錯誤。明明叫做動態(tài)數組,里面的結果是切片的。怎么回事?

先修復這個錯誤??梢钥闯?,粗心大意的效果太糟糕了,語義發(fā)生了變化。我先糾正一下!

我們知道要使用數組,必須指定數組的初始化長度。第一感覺是用cap所代表的容量來初始化*[cap]int數組,但是不行。編輯器提示必須使用整數。

雖然 cap 是一個 int 類型的變量,但內部數組 [cap]int 不識別這個方法??赡苁且驗檫@兩個變量是一起聲明的。 cap 和 (cap)int 都是變量,不能賦值。

那么如果指定了初始化長度,應該指定多少,如果是0,語義上是正確但與實際使用不符,因為這樣的話,內部數組就不能按照方法插入了!

所以數組的初始化長度不能為零,解決了無法操作數組的問題,但是語義不正確。因此,在這種情況下,需要維護len和cap這兩個變量的值,以確保語義和邏輯正確。 ,其中l(wèi)en代表數組的實際數量,cap代表內部數組的實際分配長度。由于這兩個變量非常重要,不應被調用者隨意修改,最多只能查看變量的值,所以必須提供一種機制來保護變量的值。

接下來我們嘗試用函數封裝的思路來完成這個需求,代碼實現如下:

type MyDynamicArray struct {
	ptr *[10]int
	len int
	cap int
}
func TestMyDynamicArray(t *testing.T){
	var myDynamicArray MyDynamicArray
	t.Log(myDynamicArray)
	myDynamicArray.len = 0
	myDynamicArray.cap = 10
	var arr [10]int
	myDynamicArray.ptr = &arr
	t.Log(myDynamicArray)
	t.Log(*myDynamicArray.ptr)
}

var聲明結構體變量并設置結構體的基本屬性,然后操作內部數組實現對數組的訪問修改。

然而,我們犯了一個典型的錯誤。調用者不應該關注實現細節(jié)。這不是打包要做的!

具體的實現細節(jié)由設計者完成面向對象編程語言,并將相關數據封裝成一個整體,對外提供相應的接口,讓調用者可以安全方便地調用。

第一步是封裝內部數組相關的兩個變量,只對外提供訪問接口,不提供設置接口,防止調用者隨意修改。

顯然這部分應該由函數來實現,所以有如下轉換過程。

可惜編輯器直接報錯:它必須是類型名或指向類型名的指針。

函數不能放置在結構中。這與 C 系列非常相似,但是像 Java 這樣的衍生系列會覺得不可思議。無論如何,這意味著結構只能定義結構而不能定義行為!

那我們把函數移到結構外,但是我們定義的函數名字叫l(wèi)en,而且系統(tǒng)也有l(wèi)en函數,這時候能正常運行嗎?讓我們拭目以待,眼見為實。

除了函數本身報錯,函數內部的len也報錯,因為此時函數和結構體還沒有建立任何連接。如何訪問 len 屬性?不報錯才怪!

解決這個問題很簡單。直接將結構體的指針傳遞給len函數是不夠的,這樣在函數內部可以訪問結構體的屬性。

從設計的角度來看,它確實解決了函數定義的問題,但是用戶調用函數的方式看起來與面向對象的編寫方式有些不同。

func TestMyDynamicArray(t *testing.T) {
	var myDynamicArray MyDynamicArray
	t.Log(myDynamicArray)
	myDynamicArray.len = 0
	myDynamicArray.cap = 10
	var arr [10]int
	myDynamicArray.ptr = &arr
	t.Log(myDynamicArray)
	t.Log(*myDynamicArray.ptr)
	(*myDynamicArray.ptr)[0] = 1
	t.Log(*myDynamicArray.ptr)
	t.Log(len(&myDynamicArray))
}

面向對象的方法一般都是通過點操作符來實現的。訪問屬性或方法,以及我們實現的屬性訪問。但是方法是函數調用的典型形式嗎?這看起來不像是一種方法!

為了讓普通函數看起來像面向對象的方法,Go做了如下改動,將當前結構體的變量聲明移到函數名的前面,從而實現類似于this或self in 面向語言的效果。

func len(myArr *MyDynamicArray) int {
	return myArr.len
}

這時候方法名和參數返回值又報錯了。根據提示,函數名和字段名不能相同?

這真的是一件很神奇的事情。有沒有可能 Go 無法區(qū)分函數和字段?這是未知的。

然后我們要修改函數名,改成面向對象中流行的方法命名規(guī)則,如下:

func (myArr *MyDynamicArray) GetLen() int {
	return myArr.len
}

讓我們簡單地談談 Go 的可訪問性規(guī)則。大寫字母開頭表示公共權限,小寫字母開頭表示私有權限。 Go 只有這兩種類型的權限,這兩種權限都是特定于包的。先這樣理解就好了。

根據實驗中得到的方法規(guī)則,繼續(xù)改進其他方法,補充其他方法。

現在我們已經解決了私有變量的可訪問性問題。初始化邏輯沒有處理。一般來說,初始化邏輯可以在構造函數中執(zhí)行。 Go 是否支持構造函數以及如何觸發(fā)構造函數?功能?

嘗試按照其他主流編程語言中構造函數的編寫方式來編寫Go的構造函數。沒想到Go編譯器直接報錯,提示重新定義了類型,影響了其余部分!

如果修改方法名,理論上可以解決報錯問題,但這不是構造函數的樣子。 Go 可能不支持構造函數嗎?

此時構造函數的面向對象形式轉化為自定義函數實現的構造函數。更準確的說,這是一個類似于工廠模式實現的構造函數方法。

func NewMyDynamicArray() *MyDynamicArray {
	var myDynamicArray MyDynamicArray
	return &myDynamicArray
}

Go 語言真的不支持構造函數嗎?

至于是否支持構造函數,或者應該如何支持,真相不明。隨著學習的深入,相信以后會有明確的答案。以下是我個人觀點的簡要表達。

首先我們知道Go的結構體只能定義數據,結構體的方法必須定義在結構體之外。為了符合面向對象的使用習慣,即通過實例對象的點操作符來訪問方法。 Go的方法只能是函數的變體,即普通函數指向結構體變量的聲明部分,移到函數名前面來實現方法。這種把函數變成方法的模式也符合Go一貫的命名規(guī)則:按照人的思維習慣命名,先有輸入再輸出等邏輯。

結構方法從語法和語義兩個維度支持面向對象規(guī)范,那么構造函數應該怎么做才能實現面向對象?

顧名思義,構造函數應該是一個函數,而不是一個方法。該方法由指向自身的參數組成。這一點不應包含在構造函數中。否則,應該有對象的實例,并且會構造紗線?

既然構造函數是普通函數,那么按照面向對象的命名約定,方法名應該是結構體名,但是如果你真的操作了,編輯器會直接報錯,所以這不符合到面向對象的命名約定!

這樣構造函數的名字可能不是結構類型的名字,而是其他特殊的名字。最好能通過名字知道名字,并且有在實例化對象時自動調用的能力。

當然,這個名字取決于 Go 的設計者如何命名。在這里靠猜測很難猜到,否則我就是設計師!

另外,還有一種可能,就是Go沒有構造函數。如果要實現構造函數的邏輯,只能另尋他路了。

有什么可靠的依據嗎?

我認為這是可能的。構造函數雖然提供了自動初始化的能力,但如果真的在構造函數中加入復雜的初始化邏輯,無疑會增加日后排查的難度,帶來一定的用戶。閱讀障礙,所以在某種程度上,構造函數很可能被濫用!

這是否意味著不需要構造函數?

不能說同樣的話。除了基本的變量初始化和簡單的邏輯之外,構造函數在實際編程中還有一定的用途。為了避免濫用,直接禁用。有點像喝毒解渴的感覺吧?

因此,我個人的觀點是,構造函數的初始化邏輯應該保留,或者可以用其他方式實現,或者干脆放棄構造函數,讓編譯器自動實現構造函數,就像編譯器可以自動添加一樣就像多個字段之間的分號。

如果開發(fā)者真的需要構造函數,結構體初始化的邏輯總是可以通過工廠模式或者單例模式自定義,所以放棄也可以!

最后,以上純屬個人猜想。不知道Go中有沒有構造函數。如果你知道,請清楚地告訴我答案。我個人傾向于沒有構造函數,最多只提供類似的構造函數初始化。邏輯!

現在,我們已經封裝了結構體的數據,定義了結構體的方法,實現了結構體的工廠函數。那么讓我們繼續(xù)完善動態(tài)數組,實現數組的基本操作。

func NewMyDynamicArray() *MyDynamicArray {
	var myDynamicArray MyDynamicArray
	myDynamicArray.len = 0
	myDynamicArray.cap = 10
	var arr [10]int
	myDynamicArray.ptr = &arr
	return &myDynamicArray
}
func TestMyDynamicArray(t *testing.T) {
	myDynamicArray := NewMyDynamicArray()
	t.Log(myDynamicArray)
}

首先將測試用例中的邏輯提取到工廠函數中。不帶參數的工廠函數初始化的默認內部數組長度為10,然后再考慮調用者的規(guī)范和動態(tài)數組函數的實現,暫時實現最基本的功能。 .

初始化的內部數組都是零值,所以需要先提供外界可以添加的接口,實現如下:


func (myArr *MyDynamicArray) Add(index, value int) {
	if myArr.len == myArr.cap {
		return
	}
	if index < 0 || index > myArr.len {
		return
	}
	for i := myArr.len - 1; i >= index; i-- {
		(*myArr.ptr)[i+1] = (*myArr.ptr)[i]
	}
	(*myArr.ptr)[index] = value
	myArr.len++
}

由于默認的初始化工廠函數暫時是一個定長數組,所以新元素實際上是一個定長數組,但這并不妨礙動態(tài)數組部分的后續(xù)實現。

為了方便操作,提供了插入頭部和插入尾部兩個接口,可以實現更高級的基于動態(tài)數組的數據結構。

func (myArr *MyDynamicArray) AddLast(value int) {
	myArr.Add(myArr.len, value)
}
func (myArr *MyDynamicArray) AddFirst(value int) {
	myArr.Add(0, value)
}

為了測試動態(tài)數組的算法是否正確,提供了打印方法查看數組的結構。

可以看出打印方式顯示的數據結構和真實的結構數據是一樣的,接下來我們更有信心繼續(xù)封裝動態(tài)數組!

func (myArr *MyDynamicArray) Set(index, value int) {
	if index < 0 || index >= myArr.len {
		return
	}
	(*myArr.ptr)[index] = value
}
func (myArr *MyDynamicArray) Get(index int) int {
	if index < 0 || index >= myArr.len {
		return -1
	}
	return (*myArr.ptr)[index]
}

這兩個接口比較簡單,更新數組指定索引的元素,根據索引查詢數組的值。

接下來,我們開始測試動態(tài)數組的所有接口!

動態(tài)數組暫時告一段落。不知道大家有沒有好奇我們?yōu)槭裁从脛討B(tài)數組作為例子來解釋面向對象?

其實主要是驗證上一篇的猜想,即切片和數組是什么關系?

我認為切片的底層是一個數組,但是語法層面提供了支持,讓你看不到數組的陰影。既然仙女已經學會了面向對象,那么就用面向對象的方式來實現切片的功能,雖然不能模擬語法層面的實現,但是功能特性是可以模仿的!

以下是對本文知識點的總結,即封裝的實現。

如何封裝結構

之所以叫結構體,是因為Go的關鍵字不是,而且也是面向對象編程風格中唯一支持的特性。不支持繼承和多態(tài),我會開一篇文章詳細說明。

結構是封裝數據的一種手段。結構體只能定義數據,不能定義方法。這些數據有時稱為字段,有時稱為屬性或簡稱為變量。至于叫什么,也沒什么特別的。重要的是,如何命名與環(huán)境的語義有關。

type MyDynamicArray struct {
	ptr *[10]int
	len int
	cap int
}

這個結構中有三個變量。變量由換行符而不是分號和換行符分隔。一開始感覺有點奇怪,不過編輯器一般都很聰明。如果你習慣性地加分號,會提示你刪除,所以不用在意語法細節(jié)。

結構不支持寫函數,只支持數據結構,也就是說數據和行為是分離的,兩者的關系比較弱。

func (myArr *MyDynamicArray) IsEmpty() bool {
	return myArr.len == 0
}

這種方式的功能與普通功能略有不同。包含結構變量的參數被推進到函數名的前面。語義也很清楚。它是指結構的功能。為了區(qū)別于普通函數,這種函數被稱為方法。

其實就簡單的實現函數而言,方法和函數沒有區(qū)別,無非就是調用者的使用方式!

func IsEmpty(myArr *MyDynamicArray) bool {
	return myArr.len == 0
}

之所以采用這種設計方式,一方面是體現了函數的重要性,畢竟在Go語言中它們是一等公民!

另一方面是為了實現面向對象的語法習慣,不管是屬性還是方法面向對象編程語言,都用點號調用。操作員。

在官方文檔中,這個結構參數被稱為接收者,因為數據和行為是弱相關的。發(fā)送數據的人是誰?

不言而喻,發(fā)送方應該是調用方傳遞過來的結構體實例對象,結構體變量將數據結構體發(fā)送給接收方方法,從而將數據和行為聯系在一起。

func TestMyDynamicArray(t *testing.T) {
	myDynamicArray := NewMyDynamicArray()
	fmt.println(myDynamicArray.IsEmpty())
}

好的,以上就是第一次面向對象體驗的所有部分。這只是很小的一部分,我花了三天時間。我想說的是,轉變思維不容易,寫好文章也不容易。 !

在下一篇文章中,我會繼續(xù)介紹面向對象的包裝特性,講解更多干貨。如果您覺得本文對您有幫助,請轉發(fā)您的評論,感受您的閱讀!

文章標題:,,繼承的特性子類Go-?Go
網頁鏈接:http://www.bm7419.com/news48/296798.html

成都網站建設公司_創(chuàng)新互聯,為您提供標簽優(yōu)化、搜索引擎優(yōu)化企業(yè)建站、做網站、電子商務自適應網站

廣告

聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯

搜索引擎優(yōu)化