Golang與其他語言不同的九個特性
隨著編程語言的發(fā)展,Go 還很年輕。它于 2009 年 11 月 10 日首次發(fā)布。其創(chuàng)建者Robert Griesemer
Rob Pike 和 Ken Thompson在 Google 工作,在那里大規(guī)模擴(kuò)展的挑戰(zhàn)激勵他們將 Go 設(shè)計為一種快速有效的編程解決方案,用于具有大型代碼庫、管理由多個開發(fā)人員,具有嚴(yán)格的性能要求,并跨越多個網(wǎng)絡(luò)和處理核心。
Go 的創(chuàng)始人在創(chuàng)建他們的新語言時也借此機(jī)會學(xué)習(xí)了其他編程語言的優(yōu)點、缺點和漏洞。結(jié)果是一種干凈、清晰和實用的語言,具有相對較少的命令和功能集。
在本文中,今天這篇文章將給大家介紹一下 Go 與其他語言不同的 9 個特性。
1. Go 總是在構(gòu)建中包含二進(jìn)制文件
Go 運(yùn)行時提供內(nèi)存分配、垃圾收集、并發(fā)支持和網(wǎng)絡(luò)等服務(wù)。它被編譯到每個 Go 二進(jìn)制文件中。這與許多其他語言不同,其中許多語言使用需要與程序一起安裝才能正常工作的虛擬機(jī)。
將運(yùn)行時直接包含在二進(jìn)制文件中使得分發(fā)和運(yùn)行 Go 程序變得非常容易,并避免了運(yùn)行時和程序之間的不兼容問題。Python、Ruby 和 JavaScript 等語言的虛擬機(jī)也沒有針對垃圾收集和內(nèi)存分配進(jìn)行優(yōu)化,這解釋了 Go 相對于其他類似語言的優(yōu)越速度。例如,Go 將盡可能多的存儲在堆棧中,其中數(shù)據(jù)按順序排列以便比堆更快地訪問。稍后會詳細(xì)介紹。
關(guān)于 Go 的靜態(tài)二進(jìn)制文件的最后一件事是,因為不需要運(yùn)行外部依賴項,所以它們啟動得非???。如果您使用Google App Engine 之類的服務(wù),這是一種在 Google Cloud 上運(yùn)行的平臺即服務(wù),它可以將您的應(yīng)用程序縮減到零實例以節(jié)省云成本,這將非常有用。當(dāng)收到新請求時,App Engine 可以在眨眼間啟動 Go 程序的一個實例。在 Python 或 Node 中的相同體驗通常會導(dǎo)致 3-5 秒(或更長時間)的等待,因為所需的虛擬環(huán)境也會與新實例一起啟動。
2. Go 沒有針對程序依賴的集中托管服務(wù)
為了訪問已發(fā)布的 Go 程序,開發(fā)人員不依賴集中托管的服務(wù),例如Java 的Maven Central或JavaScript的NPM注冊表。相反,項目通過其源代碼存儲庫(最常見的是 Github)共享。在go install命令行允許以這種方式下載庫。
為什么我喜歡這個功能?我一直認(rèn)為像 Maven Central、PIP 和 NPM 這樣的集中托管的依賴服務(wù)有點令人生畏的黑盒子,也許可以抽象出下載和安裝依賴項的麻煩,但不可避免地會在依賴項錯誤時引發(fā)可怕的心跳停止發(fā)生。
此外,將的的模塊提供給其他人就像將其放入版本控制系統(tǒng)一樣簡單,這是分發(fā)程序的一種非常簡單的方式。
3. Go 是按值調(diào)用的
在 Go 中,當(dāng)你提供一個原始值(數(shù)字、布爾值或字符串)或一個結(jié)構(gòu)體(類對象的粗略等價物)作為函數(shù)的參數(shù)時,Go 總是會復(fù)制變量的值。
在 Java、Python 和 JavaScript 等許多其他語言中,原語是按值傳遞的,但對象(類實例)是按引用傳遞的,這意味著接收函數(shù)實際上接收的是指向原始對象的指針,而不是其副本。在接收函數(shù)中對對象所做的任何更改都會反映在原始對象中。
在 Go 中,結(jié)構(gòu)體和原語默認(rèn)按值傳遞,可以選擇傳遞指針,通過使用星號運(yùn)算符:
// 按值傳遞 func MakeNewFoo(f Foo ) (Foo, error) { f.Field1 = "New val" f.Field2 = f.Field2 + 1 return f, nil }
上述函數(shù)接收 Foo 的副本并返回一個新的 Foo 對象。
// 通過引用傳遞 func MutateFoo(f *Foo ) error { f.Field1 = "New val" f.Field2 = 2 return nil }
上面的函數(shù)接收一個指向 Foo 的指針并改變原始對象。
按值調(diào)用與按引用調(diào)用的這種明顯區(qū)別使您的意圖顯而易見,并降低了調(diào)用函數(shù)無意中改變傳入對象的可能性(當(dāng)它不應(yīng)該發(fā)生時(許多初學(xué)者開發(fā)人員很難做到這一點)握緊)。
正如麻省理工總結(jié)的:“可變性使得理解你的程序在做什么變得更加困難,并且更難以執(zhí)行契約”
此外,按值調(diào)用顯著減少了垃圾收集器的工作,這意味著應(yīng)用程序更快、內(nèi)存效率更高。這篇文章得出的結(jié)論是,指針追蹤(從堆中檢索指針值)比從連續(xù)堆棧中檢索值慢 10 到 20 倍。要記住的一個很好的經(jīng)驗法則是:從內(nèi)存中讀取的最快方法是順序讀取,這意味著將隨機(jī)存儲在 RAM 中的指針數(shù)量減少到最少。
4. ‘defer' 關(guān)鍵字
在NodeJS 中,在我開始使用knex.js之前,我會通過創(chuàng)建一個數(shù)據(jù)庫池來手動管理我的代碼中的數(shù)據(jù)庫連接,然后在每個函數(shù)中從池中打開一個新連接,一旦所需的數(shù)據(jù)庫 CRUD 功能已完成。
這有點像維護(hù)噩夢,因為如果我沒有在每個函數(shù)結(jié)束時釋放連接,未釋放的數(shù)據(jù)庫連接的數(shù)量會慢慢增長,直到池中沒有更多可用連接,然后中斷應(yīng)用程序。
現(xiàn)實情況是,程序經(jīng)常需要釋放、清理和拆除資源、文件、連接等,因此 Go 引入了defer關(guān)鍵字作為管理這些的有效方式。
任何以defer開頭的語句都會延遲對它的調(diào)用,直到周圍的函數(shù)退出。這意味著您可以將清理/拆卸代碼放在函數(shù)的頂部(很明顯),知道一旦函數(shù)完成它就會如此。
func main() { if len(os.Args) < 2 { log.Fatal("no file specified") } f, err := os.Open(os.Args[1]) if err != nil { log.Fatal(err) } defer f.Close() data := make([]byte, 2048) for { count, err := f.Read(data) os.Stdout.Write(data[:count]) if err != nil { if err != io.EOF { log.Fatal(err) } break } } }
在上面的例子中,文件關(guān)閉方法被推遲了。我喜歡這種在函數(shù)頂部聲明你的內(nèi)務(wù)處理意圖的模式,然后忘記它,知道一旦函數(shù)退出它就會完成它的工作。
5. Go 采用了函數(shù)式編程的最佳特性
函數(shù)式編程是一種高效且富有創(chuàng)造性的范式,幸運(yùn)的是 Go 采用了函數(shù)式編程的最佳特性。在Go中:
- 函數(shù)是值,這意味著它們可以作為值添加到映射中,作為參數(shù)傳遞給其他函數(shù),設(shè)置為變量,并從函數(shù)返回(稱為“高階函數(shù)”,在 Go 中經(jīng)常使用裝飾器創(chuàng)建中間件圖案)。
- 可以創(chuàng)建和自動調(diào)用匿名函數(shù)。
- 在其他函數(shù)內(nèi)聲明的函數(shù)允許閉包(在函數(shù)內(nèi)聲明的函數(shù)能夠訪問和修改在外部函數(shù)中聲明的變量)。在慣用的 Go 中,閉包被廣泛使用來限制函數(shù)的范圍,并設(shè)置函數(shù)然后在其邏輯中使用的狀態(tài)。
func StartTimer (name string) func(){ t := time.Now() log.Println(name, "started") return func() { d := time.Now().Sub(t) log.Println(name, "took", d) } } func RunTimer() { stop := StartTimer("My timer") defer stop() time.Sleep(1 * time.Second) }
上面是一個閉包的例子?!甋tartTimer' 函數(shù)返回一個新函數(shù),它通過閉包可以訪問在其出生范圍內(nèi)設(shè)置的 ‘t' 值。然后,此函數(shù)可以將當(dāng)前時間與“t”的值進(jìn)行比較,從而創(chuàng)建一個有用的計時器。感謝Mat Ryer的這個例子。
6. Go 有隱式接口
任何閱讀過有關(guān)SOLID編碼和設(shè)計模式的文獻(xiàn)的人都可能聽說過“優(yōu)先組合勝過繼承”的口頭禪。簡而言之,這表明您應(yīng)該將業(yè)務(wù)邏輯分解為不同的接口,而不是依賴于來自父類的屬性和邏輯的分層繼承。
另一個流行的方法是“為接口編程,而不是實現(xiàn)”: API 應(yīng)該只發(fā)布其預(yù)期行為的契約(其方法簽名),而不是有關(guān)如何實現(xiàn)該行為的詳細(xì)信息。
這兩者都表明接口在現(xiàn)代編程中的重要性。
因此,Go 支持接口也就不足為奇了。事實上,接口是 Go 中唯一的抽象類型。
然而,與其他語言不同,Go 中的接口不是顯式實現(xiàn)的,而是隱式實現(xiàn)的。具體類型不聲明它實現(xiàn)了接口。相反,如果為該具體類型設(shè)置的方法集包含底層接口的所有方法集,則Go 認(rèn)為該對象實現(xiàn)了 interface。
這種隱式接口實現(xiàn)(正式稱為結(jié)構(gòu)類型)允許 Go 強(qiáng)制執(zhí)行類型安全和解耦,保持動態(tài)語言中表現(xiàn)出的大部分靈活性。
相比之下,顯式接口將客戶端和實現(xiàn)綁定在一起,例如,在 Java 中替換依賴項比在 Go 中困難得多。
// 這是一個接口聲明(稱為Logic) type Logic interface { Process (data string) string } type LogicProvider struct {} // 這是 LogicProvider 上名為“Process”的方法 struct func (lp LogicProvider) Process (data string) string { // 業(yè)務(wù)邏輯 } // 這是具有 Logic 接口作為屬性的客戶端結(jié)構(gòu) type Client struct { L Logic } func(c Client) Program() { // 從某處獲取數(shù)據(jù) cLProcess(data) } func main() { c := Client { L: LogicProvider{}, } c.Program() }
LogicProvider 中沒有任何聲明表示它符合Logic接口。這意味著客戶端將來可以輕松替換其邏輯提供程序,只要該邏輯提供程序包含底層接口 ( Logic ) 的所有方法集。
7.錯誤處理
Go 中的錯誤處理方式與其他語言大不相同。簡而言之,Go 通過返回一個 error 類型的值作為函數(shù)的最后一個返回值來處理錯誤。
當(dāng)函數(shù)按預(yù)期執(zhí)行時,錯誤參數(shù)返回nil,否則返回錯誤值。調(diào)用函數(shù)然后檢查錯誤返回值,并處理錯誤,或拋出自己的錯誤。
// 函數(shù)返回一個整數(shù)和一個錯誤 func calculateRemainder(numerator int, denominator int) ( int, error ) { // if denominator == 0 { return 9, errors.New("denominator is 0" } // 沒有錯誤返回 return numerator / denominator, nil }
Go 以這種方式運(yùn)行是有原因的:它迫使編碼人員考慮異常并正確處理它們。傳統(tǒng)的 try-catch 異常還會在代碼中添加至少一個新的代碼路徑,并以難以遵循的方式縮進(jìn)代碼。Go 更喜歡將“快樂路徑”視為非縮進(jìn)代碼,在“快樂路徑”完成之前識別并返回任何錯誤。
8.并發(fā)
可以說是 Go 最著名的特性,并發(fā)允許處理在機(jī)器或服務(wù)器上的可用內(nèi)核數(shù)量上并行運(yùn)行。當(dāng)單獨的進(jìn)程不相互依賴(不需要順序運(yùn)行)并且時間性能至關(guān)重要時,并發(fā)性最有意義。這通常是 I/O 要求的情況,其中讀取或?qū)懭氪疟P或網(wǎng)絡(luò)的速度比除最復(fù)雜的內(nèi)存中進(jìn)程之外的所有進(jìn)程慢幾個數(shù)量級。
函數(shù)調(diào)用前的“ go ”關(guān)鍵字將同時運(yùn)行該函數(shù)。
func process(val int) int { // 用 val 做一些事情 } // 對于 'in' 中的每個值,同時運(yùn)行 process 函數(shù), // 并將 process 的結(jié)果讀取到 'out' func runConcurrently(in <-chan int, out chan<- int){ go func() { for val := range in { result := process(val) out <- result } } }
Go 中的并發(fā)是一項深入且相當(dāng)高級的功能,但在有意義的地方,它提供了一種有效的方法來確保程序的最佳性能。
9. Go 標(biāo)準(zhǔn)庫
Go 有一個“包含電池”的理念,現(xiàn)代編程語言的許多要求都被納入標(biāo)準(zhǔn)庫,這使程序員的生活變得更加簡單。
如前所述,Go 是一種相對年輕的語言,這意味著標(biāo)準(zhǔn)庫中可以滿足現(xiàn)代應(yīng)用程序的許多問題/要求。
一方面,Go 為網(wǎng)絡(luò)(特別是 HTTP/2)和文件管理提供了世界一流的支持。它還提供原生 JSON 編碼和解碼。因此,設(shè)置服務(wù)器來處理 HTTP 請求并返回響應(yīng)(JSON 或其他)非常簡單,這解釋了 Go 在基于 REST 的 HTTP Web 服務(wù)開發(fā)中的流行。
正如Mat Ryer還指出的那樣,標(biāo)準(zhǔn)庫是開源的,是學(xué)習(xí) Go 最佳實踐的絕佳方式。
如果你真的從這篇文章中學(xué)到了一些新東西,喜歡它,收藏它并與你的小伙伴分享。🤗最后,不要忘了❤或📑支持一下哦
到此這篇關(guān)于Golang與其他語言不同的九個特性的文章就介紹到這了,更多相關(guān)Golang 特性內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
go語言實現(xiàn)一個簡單的http客戶端抓取遠(yuǎn)程url的方法
這篇文章主要介紹了go語言實現(xiàn)一個簡單的http客戶端抓取遠(yuǎn)程url的方法,實例分析了Go語言http操作技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-03-03Go語言開源庫實現(xiàn)Onvif協(xié)議客戶端設(shè)備搜索
這篇文章主要為大家介紹了Go語言O(shè)nvif協(xié)議客戶端設(shè)備搜索示例實現(xiàn),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-04-04使用IDEA配置GO語言的開發(fā)環(huán)境備忘錄
最近在配置idea開發(fā)go語言時碰到很多問題,想著很多人都可能會遇到,所以下面這篇文章主要給大家介紹了關(guān)于使用IDEA配置GO語言的開發(fā)環(huán)境,文中通過圖文介紹的非常詳細(xì),需要的朋友可以參考下2024-05-05