當前位置:
首頁 > 科技 > C+程序員是如何評價GO語言的

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)中,你現在必須手動編碼大量的轉發方法來實現此目的,您仍然需要繼承某種東西,以告知支持封裝介面的類型系統。

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 CSDN 的精彩文章:

七大有效的編程習慣助你成為更好的程序員
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的合作?
自然語言處理:語言模型與評價方法