C+程序員是如何評價GO語言的
作者丨Murray
翻譯丨Peter
這是關於評論GO語言的第二部分,第一部分:C++程序員是如何評價GO的,第三部分會在不日後在CSDN公眾號(ID:csdnnews)發布。
在第一部分裡面就GO語言的簡單功能(特徵)做了論述,如常用語法,基本類型等。本文將主要提及GO所支持的package(包)和面向對象。在這之前呢,還是建議讀者閱讀一下此書,照舊,歡迎各方高人點評和糾錯。
總的來說,我發現GO語言面向對象的語法有點亂,一致性差、不明顯,所以對於大多數使用場合,個人更傾向於C++明顯的繼承層次結構。
在這個部分的文章裡面故意不提及系統構建,分發或者配置等內容。
Packages(包)
Go代碼是以軟體包的形式組織的,Java也有包的概念,二者很像,跟C++命名空間也有點類似。 在源文件的開頭聲明包的名稱:
package foo
當需要用到某個包時,用import方式導入:
package bar //定義了包名
import ( //告訴Go編譯器這個程序需要使用 foo、moo 包(的函數,或其他元素)
"foo"
"moo"
)
func somefunc() {
foo.Yadda()
var a moo.Thing
...
}
包名稱應與文件的目錄名稱匹配。 這是import語句找到對應包的關鍵。一個目錄可允許有多個文件,這些文件都是同一個包的一部分。
package main不受以上規則約束。由於其唯一性,所以對應的目錄不需要命名為main。
結構體
在Go語言中可以像C一樣聲明一個結構體:
type Thing struct {
// Member fields.
// Notice the lack of the var keyword.
a int
B int // See below about symbol visibility
}
var foo Thing
foo.B = 3
var bar Thing = Thing
var goo *Thing = new(Thing)
goo.B = 5
我習慣使用var關鍵字演示變數的實際類型,也可能會選擇較短的表達式 := 。
請注意,我們可以將其創建為一個值或一個指針(使用內置的new()函數),與C或C ++不同,Go中的結構體所佔的實際內存並不能確定是在堆還是棧上。 具體由編譯器決定,一般是根據內存是否需要延續功能調用來分配。
以前,我們已經看到內置的make()函數用於實例化slices(切片)和maps(集合)。 make()僅適用於那些內置類型。 對於自定義類型,可以使用new()函數。 我發現這個區別有點混亂,但是我一般不喜歡使用語言本身就可以實現的類型區別。 我喜歡C++標準庫如何在C++中實現方式,當往庫裡面添加內容時,語言本身幾乎沒有什麼特別的支持。
Go類型通常具有「構造函數」(而不是方法),應該調用該函數來正確實例化該類型,但是我認為沒有辦法強制執行正確的初始化,就像C++或Java中的默認構造函數。例如:
type Thing struct {
a int
name string
...
}
func NewThing() *Thing {
// 100 is a suitable default value for a in this type:
f := Thing
return &f
}
// Notice that different "constructors" must have different names,
// because go doesn t have function or method overloading.
func NewThingWithName(name string) *Thing {
f := Thing
return &f
}
Embedding Structs(嵌套結構體)
可以匿名地將一個結構體「嵌入」到其他結構體中,如下所示:
type Person struct {
Name string
}
type Employee struct {
Person
Position string
}
var a Employee
a.Name = "bob"
a.Position = "builder"
這感覺有點像C ++和Java中的繼承,例如,可以這樣:
var e = new(Employee)
// Compilation error.
var p *Person = e
// This works instead.
// So if we thought of this as a cast (we probably shouldn t),
// this would mean that we have to explicitly cast to the base class.
var p *Person = e.Person
// This works.
e.methodOnPerson()
// And this works.
// Name is a field in the contained Person struct.
e.Name = 2
// These work too, but the extra qualification is unnecessary.
e.Person.methodOnPerson()
Methods(方法)
Go語言中的結構體可以有Methods(與結構相關聯的函數),這點和C/Java語言的classes(類)很像 , 但在語法方面略有不同。 Method在結構體外被聲明,並且通過在函數名之前指定「receiver」來進行調用。 例如,它聲明(並實現)Thing結構體的DoSomething方法:
func (t Thing) DoSomething() {
...
}
還有需要注意的一點,由於GO沒有內置如「self」或「this」的實體名,故必須為receiver指定一個名稱。這感覺有點相互矛盾。
可以使用指針替代,而且如果要更改關於struct實例的任何內容,指針是不二選擇:
func (t *Thing) ChangeSomething() {
t.a = 4
}
如果需要保持代碼的一致性,最好給method receivers指定為指針類型。
跟C++/Java不同,這允許檢查實例是否為nil(Go為null或nullptr),使其可以在null實例上調用方法。 這讓我想起Objective-C如何在nil實例上調用方法而沒有崩潰,甚至返回一個nil/zero值。 我發現Objective-C沒有這一機制,更讓我沮喪的是,Go允許這樣做,但沒有一定的一致性。
與C++或Java不同,GO甚至可以將methods與非struct(非類)類型相關聯。 例如:
type Meters int
type Feet int
func (Meters) convertToFeet() (Feet) {
...
}
Meters m = 10
f := p.convertToFeet()
沒有賦值或比較運算符重載
在C++裡面,可將=、 !=、 、等運算符重載,所以可以使用這些常規的運算符,使代碼看起來更整潔:
MyType a = getSomething();
MyType b = getSomethingElse();
if (a == b) {
...
}
在Go語言中不能這麼使用, 只有部分內建類型是可比較的,如數字類型,字元串,指針或通道,或由這些類型組成的結構體或數組。當處理介面時這會是一個麻煩,我們稍後會看到。
符號可見性: 大寫或小寫字母
以大寫字母開頭的符號 (類型、函數、變數) 可從包外部獲得。結構方法和以大寫字母開頭的成員變數可從結構外部獲得。否則,它們就是私有的包或結構。例如:
type Thing int // This type will be available outside of the package.
var Thingleton Thing// This variable will be available outside of the package.
type thing int // Not available outside of the package.
var thing1 thing // Not available outside of the package.
var thing2 Thing // Not available outside of the package.
// Available outside of the package.
func DoThing() {
...
}
// Not available outside of the package.
func doThing() {
...
}
type Stuff struct {
Thing1 Thing // Available outside of the package.
thing2 Thing // "private" to the struct.
}
// Available outside of the struct.
func (s Stuff) Foo() {
...
}
// Not available outside of the struct.
func (s Stuff) bar() {
...
}
// Not available outside of the package.
type localstuff struct {
...
}
感覺這有點奇怪。相對而言, c++和Java中明確用public和private關鍵字聲明顯得更加友善。
Interfaces(介面)
有方法的介面
如果兩個Go類型滿足一個介面,那它們都具有該介面的方法, 這與Java介面類似。 Go介面也有點像C ++中的一個完全抽象類(只有純虛方法),跟C ++ Concept(概念)也很像(自C ++ 17)。
例如:
type Shape interface {
// The interface s methods.
// Note the lack of the func keyword.
SetPosition(x int, y int)
GetPosition() (x int, y int)
DrawOnSurface(s Surface)
}
type Rectangle struct {
...
}
// Methods to satisfy the Shape interface.
func (r *Rectangle) SetPosition(x int, y int) {
...
}
func (r *Rectangle) GetPosition() (x int, y int) {
...
}
func (r *Rectangle) DrawOnSurface(s Surface) {
...
}
// Other methods:
func (r *Rectangle) setCornerType(c CornerType) {
...
}
func (r *Rectangle) cornerType() (CornerType) {
...
}
type Circle struct {
...
}
// Methods to satisfy the Shape interface.
func (c *Circle) SetPosition(x int, y int) {
...
}
func (c *Circle) GetPosition() (x int, y int) {
...
}
func (c *Circle) DrawOnSurface(s Surface) {
...
}
// Other methods:
...
然後,就可以使用介面類型而不是特定的 「實際」 類型:
var someCircle *Circle = new(Circle)
var s Shape = someCircle
s.DrawOnSurface(someSurface)
請注意,這裡使用的是Shape, 而不是使用Shape(指向Shape的指針), 即使是從 Circle(指向circle)轉換。 「介面值」似乎是隱式指針,這似乎是不必要的混淆。 如果指向介面的指針只是具有與這些「介面值」相同的行為,即使語言禁止使用指針的介面類型, 它也會更加一致。
隱式滿足介面類型
但是,沒有明確聲明類型應實現介面。
通過這種方式, 介面就像C++的概念, 雖然C++概念是純編譯時功能, 用於通用 (模板) 代碼。您的類可以符合 c++ 概念,而無需具體聲明。因此, 與 go 介面一樣, 如果必須, 您可以使用現有類型而不更改它。
編譯器仍需檢查類型是否兼容, 但可能是檢查類型的方法鏈表, 而不是檢查類層次結構或已實現介面的鏈表。例如:
var a *Circle = new(Circle)
var b Shape = a // OK. The compiler can check that Circle has Shape s methods.
像 c++ 的dynamic_cast一樣,GO 也可以在運行時檢查。例如,可以檢查一個介面值是否引用一個同時滿足另一個介面的實例:
// Sometimes the Shape (our interface type) is also a Drawable
// (another interface type), sometimes not.
var a Shape = Something.GetShape()
// Notice that we want to cast to a Drawable, not a *Drawable,
// because Drawable is an interface.
var b = a.(Drawable) // Panic (crash) if this fails.
var b, ok = a.(Drawable) // No panic.
if ok {
b.DrawOnSurface(someSurface)
}
或者,可以檢查介面值是否特指某種具體類型。例如:
// Get Shape() returns an interface value.
// Shape is our interface.
var a Shape = Something.GetShape()
// Notice that we want to cast to a *Thing, not a Thing,
// because Thing is a concrete type, not an interface.
var b = a.(*Thing) // Panic (crash) if this fails.
var b, ok = a.(*Thing) // No panic.
if ok {
b.DoSomething()
}
Runtime調用
介面方法也類似於 c++ 虛方法 (或java 方法), 介面變數也類似於多態基類的實例。為了通過介面變數實際調用介面的方法, 程序需要在運行時檢查其實際類型, 並調用該類型的特定方法。也許,與 c++ 一樣,編譯器有時可以優化掉這種間接定址。
這顯然不如直接調用 c++ 模板中的模板化類型在編譯時標識的方法那樣有效。但它顯然是簡單得多。
比較介面
介面值有時可以比較, 但這似乎是一個危險的業務。介面值為:
類型不同,則不相等。
類型相同,只有一個為nil,不相等。
類型相同,可比較,並且它們的值一樣,則相等。
但是,如果類型是相同的,但這些類型是不可比較的, 將導致Go在運行時拋出異常 「panic」。(譯者註:panic 是用來表示非常嚴重的不可恢復的錯誤的。在Go語言中這是一個內置函數,接收一個interface{}類型的值(也就是任何值了)作為參數。panic的作用就像我們平常接觸C++的異常)
希望實現關鍵字
在C ++中,如果你願意,可以顯式聲明一個類應符合該概念,或者你可以從一個基類中顯示的派生出來,而在Java中,必須使用「implements」關鍵字。由於GO語言沒有此機制,因此需要習慣。我想要這些聲明來記錄我的架構,根據他們的一般目的明確地顯示我的「具體」類的預期,而不是僅僅用一些其他代碼來表達它們。沒有這個感覺很脆弱。
該書建議將這個笨拙的代碼放在某處,以檢查一個類型是否真正實現了一個介面。注意_(下劃線)的使用意味著我們不需要為結果保留一個命名變數。
var _ MyInterface =(* MyType)(nil)
如果類型不滿足介面,轉換是不可能的,編譯器應該報錯。作為最初級測試,我認為這是明智之舉。特別是如果您的包提供的類型,不是真正使用的包本身。對於我來說, 這是一個很糟糕的替代品,它使用特定的語言構造對類型本身進行明顯的編譯時檢查。
介面嵌入
在介面中嵌入介面
GO不具有繼承層次結構的概念, 但您可以在一個介面中 「嵌入」另 一個介面, 以指示滿足一個介面的類也滿足另一個介面。例如:
type Positionable interface {
SetPosition(x int, y int)
GetPosition() (x int, y int)
}
type Drawable interface {
drawOnSurface(s Surface) }
}
type Shape interface {
Positionable
Drawable
}
為了滿足Shape介面,任何類型也必須滿足Drawable和Positionable介面。 因此,任何滿足Shape介面的類型都可以與Drawable或Positionable介面關聯的方法使用。 這有點像一個java介面擴展另一個介面。
在結構體中嵌入一個滿足介面的結構體
我們早些時候就看到了如何將一個結構嵌入另一個匿名結構體中。如果包含的struct實現了一個介面, 則包含的struct也可以實現該介面, 而不需要手動實現的轉發方法。例如:
type Drawable interface {
drawOnSurface(s Surface)
}
type Painter struct {
...
}
// Make Painter satisfy the Drawable interface.
func (p *Painter) drawOnSurface(s Surface) {
...
}
type Circle struct {
// Make Circle satisfy the Drawable interface via Painter.
Painter
...
}
func main() {
...
var c *Circle = new(Circle)
// This is OK.
// Circle satisfies Drawable, via Painter
c.drawOnSurface(someSurface)
// This is also OK.
// Circle can be used as an interface value of type Drawable, via Painter.
var d Drawable = c
d.drawOnSurface(someSurface)
}
再一次感覺有點像繼承
我實際上非常喜歡匿名地包含結構體的(介面)結構影響父結構的介面,即使是Go的異樣介面系統,儘管我希望語法對於發生的事情更加明顯。在C++中有類似的東西可能很好。封裝而不是繼承(和Decorator模式)是一種非常高效的技術,C ++通常會嘗試以多種方式進行操作,而不會對最好的方式有所了解,儘管這本身就會成為複雜性的來源。但是在C++(和Java)中,你現在必須手動編碼大量的轉發方法來實現此目的,您仍然需要繼承某種東西,以告知支持封裝介面的類型系統。
點擊展開全文
※七大有效的編程習慣助你成為更好的程序員
※Windows經典桌面背後的故事
※減少網頁載入時間的6個技巧
TAG:CSDN |
※如何評價賓士GLA?
※如何評價NBA球員胡德?
※如何評價博瑞GE?
※如何評價《XXXHolic》這部作品?
※NBA球員是如何評價CBA?德克垃圾話讓CBA躺槍,老馬稱是我的生命
※EDG橫掃OMG後,觀眾用四個字評價表現!網友:基本操作
※如何評價SM的solo藝人
※如何評價iPhone X?
※OPPO Find X消費者口碑如何?看看首批用戶是如何評價的
※如何評價C羅?
※神評論|如果評價韓國組合BIGBANG?網友:bigbang永遠的王者
※GAI首度評價吳亦凡DISS事件,我非常尊重吳亦凡老師
※蘋果 HomePod 的音質如何?外媒是這樣評價的
※網友評價GAI夫妻像鳳凰傳奇,GAI卻用粗話回敬,想重蹈PGONE覆轍?
※美國史汀生A-1客機是不是DC3,如何評價?航空界的商業典範
※如何評價NBA總決賽各個球員的表現?
※OPPO Find X好評如潮 網友評價稱全球最美的手機!
※如何評價SUV型車?
※佔領車聯網入口只是第一步,如何評價NXP與AliOS的合作?
※自然語言處理:語言模型與評價方法