摘要:本文由葡萄城技術團隊於部落格園原創並首發。轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。
為了更加深入地介紹Go語言以及與C#語言的比較,本文將會從多個維度出發進行詳細的闡述。首先,將從Go語言的關鍵字方面介紹Go與C#在語言特性上的異同,並且探討兩種語言在關鍵字方面的優化和不足之處。其次,本文將通過程式碼範例、效能測試等方式,展示Go語言在關鍵字方面的優勢,從而為讀者呈現出Go語言的強大之處。除此之外,為了更好地幫助讀者理解Go語言,本文還將介紹一些優秀的Go語言工具和社群資源,供讀者進一步學習和探索。相信通過這些內容的全面介紹,讀者們會對Go語言有更全面深入的認識和了解。
文章目錄:
1.2逐步成型
1.3正式釋出
2.3.2.Func
話說早在 2007 年 9 月的一天,Google 工程師 Rob Pike 和往常一樣啟動了一個 C++專案的構建,按照他之前的經驗,這個構建應該需要持續 1 個小時左右。這時他就和 Google公司的另外兩個同事 Ken Thompson 以及 Robert Griesemer 開始吐槽並且說出了自己想搞一個新語言的想法。當時 Google 內部主要使用 C++構建各種系統,但 C++複雜性巨大並且原生缺少對並行的支援,使得這三位大佬苦惱不已。
第一天的閒聊初有成效,他們迅速構想了一門新語言:能夠給程式設計師帶來快樂,能夠匹配未來的硬體發展趨勢以及滿足 Google 內部的大規模網路服務。並且在第二天,他們又碰頭開始認真構思這門新語言。第二天會後,Robert Griesemer 發出瞭如下的一封郵件:
可以從郵件中看到,他們對這個新語言的期望是:在 C 語言的基礎上,修改一些錯誤,刪除一些詬病的特性,增加一些缺失的功能。比如修復 Switch 語句,加入 import 語句,增加垃圾回收,支援介面等。而這封郵件,也成了 Go 的第一版設計初稿。
在這之後的幾天,Rob Pike 在一次開車回家的路上,為這門新語言想好了名字Go。在他心中,」Go」這個單詞短小,容易輸入並且可以很輕易地在其後組合其他字母,比如 Go 的工具鏈:goc 編譯器、goa 組合器、gol 聯結器等,並且這個單詞也正好符合他們對這門語言的設計初衷:簡單。
在統一了 Go 的設計思路之後,Go 語言就正式開啟了語言的設計迭代和實現。2008 年,C語言之父,大佬肯·湯普森實現了第一版的 Go 編譯器,這個版本的 Go 編譯器還是使用C語言開發的,其主要的工作原理是將Go編譯成C,之後再把C編譯成二進位制檔案。到2008年中,Go的第一版設計就基本結束了。這時,同樣在谷歌工作的伊恩·泰勒(Ian Lance Taylor)為Go語言實現了一個gcc的前端,這也是 Go 語言的第二個編譯器。伊恩·泰勒的這一成果不僅僅是一種鼓勵,也證明了 Go 這一新語言的可行性 。有了語言的第二個實現,對Go的語言規範和標準庫的建立也是很重要的。隨後,伊恩·泰勒以團隊的第四位成員的身份正式加入 Go 語言開發團隊,後面也成為了 Go 語言設計和實現的核心人物之一。羅斯·考克斯(Russ Cox)是Go核心開發團隊的第五位成員,也是在2008年加入的。進入團隊後,羅斯·考克斯利用函數型別是「一等公民」,而且它也可以擁有自己的方法這個特性巧妙設計出了 http 包的 HandlerFunc 型別。這樣,我們通過顯式轉型就可以讓一個普通函數成為滿足 http.Handler 介面的型別了。不僅如此,羅斯·考克斯還在當時設計的基礎上提出了一些更泛化的想法,比如 io.Reader 和 io.Writer 介面,這就奠定了 Go 語言的 I/O 結構模型。後來,羅斯·考克斯成為 Go 核心技術團隊的負責人,推動 Go 語言的持續演化。到這裡,Go 語言最初的核心團隊形成,Go 語言邁上了穩定演化的道路。
2009年10月30日,羅伯·派克在Google Techtalk上做了一次有關 Go語言的演講,這也是Go語言第一次公之於眾。十天後,也就是 2009 年 11 月 10 日,谷歌官方宣佈 Go 語言專案開源,之後這一天也被 Go 官方確定為 Go 語言的誕生日。
(Go語言吉祥物Gopher)
1.Go語言安裝包下載
Go 官網:https://golang.google.cn/
選擇對應的安裝版本即可(建議選擇.msi檔案)。
2.檢視是否安裝成功 + 環境是否設定成功
開啟命令列:win + R 開啟執行框,輸入 cmd 命令,開啟命令列視窗。
命令列輸入 go version 檢視安裝版本,顯示下方內容即為安裝成功。
Go有25個關鍵字,而C#則有119個關鍵字(其中包含77個基礎關鍵字和42個上下文關鍵字)。單從數量上來講,C#的數量是Go的5倍之多,這也是Go比C#更簡單的原因之一。
Go中的 25 個關鍵字:
break | default | func | interface | select |
---|---|---|---|---|
case | defer | go | map | struct |
chan | else | goto | package | switch |
const | fallthrough | if | range | type |
continue | for | import | return | var |
(Go關鍵字)
以上14個關鍵字是Go和C#共有的,它們之中大部分的用法都是完全相同的,這裡重點說一下在Go中有特殊語法的關鍵字。
Var在Go中用來表示定義變數,但語法和 C#不同。C#中只有一種定義變數的方法,而 Go中有兩種,它們分別是:
var i int = 1
這種方式是Go的原始變數定義方式,一般包級別的變數都是這樣定義的,並且如果定義那些編譯器可以自動推斷的型別,比如上述的例子,其後的型別可以省略。
i, j := 1, "hello"
上述程式碼可簡寫為語法糖形式。事實上,Go程式碼中,90%變數都以此方式定義,因為幾乎所有函數都有多個返回值,這種定義方式可省去許多麻煩。
Switch-case是一個連用的方法,但是case和default這兩個關鍵字在 Go中除了可以和 switch 連用,還可以和select 語句連用。
同時Go中預設把 switch 語句的一個弊端修復了,即 switch 子句中不用再寫 break 了。
switch n := "a"; n {
case n == "a":
fmt.Println("a")
case n == "b":
fmt.Println("b")
case n == "c":
fmt.Println("c")
}
上面這段程式碼的fmt是Go中的標準輸出包,其中的Println函數等同於C#中的Console.WriteLine方法。同時這段程式碼的最終結果只會輸出a,而 在C#中,同樣的程式碼會把abc全部輸出出來,這也是Go為何比C#簡單的原因之一。
除此之外,switch 語句後面出現了一種全新的寫法:n := "a"; n,這種寫法在Go中的控制語句(if, else if, switch-case, for)中都可以使用,分號前是變數的定義,分號後是定義的判斷條件。這種語法優點類似於 C#中的普通 for 迴圈的前兩個子句。
最後,可以發現switch之後沒有跟小括號,因為在Go中,控制塊的子句後面都是不需要寫小括號的,如果寫了同樣會被 gofmt 自動格式化掉。
Go中的if-else和C#幾乎也是相同的,它倆最大的區別是Go中特殊語法,可以在 if-else 控制塊中直接給變數賦值並且在控制塊中使用這些值。
func isEven(n int) bool {
return n % 2 == 0
}
func main() {
if n := rand.Intn(1000); isEven(n) {
fmt.Printf("%d是偶數\\n", n)
} else {
fmt.Printf("%d是奇數\\n", n)
}
}
Go中的迴圈控制語句有且只有一個 for 關鍵字。而 C#中的 while、foreach 等在Go中都是通過 for 的各種變形達成的。
for true {
// ...
}
for i := 0; i < n; i++ {
// ...
}
Go中普通的 for 迴圈和 C#中唯一的差別就是 i++從表示式變成了語句。也就是說,Go中沒有i = i++這樣的語法,也沒有++i這樣的語法,只有i++這種語法。
array := [5]int{1, 2, 3, 4, 5}
for index, value := range array {
// ...
}
foreach 語句的寫法和 C#中很不相同,上述的例子是 foreach 遍歷一個int型別的陣列,其中用到了一個range關鍵字,這個關鍵字會把陣列拆分成兩個迭代子物件index 和value,for range可以遍歷陣列、切片、字串、map 及通道(channel),這個語法同樣類似於 JavaScript 的迴圈語法。例如下面的程式碼就是遍歷陣列中的值並輸出:
for key, value := range []int{1, 2, 3, 4} {
fmt.Printf("key:%d value:%d\\n", key, value)
}
程式碼輸出如下:
key:0 value:1
key:1 value:2
key:2 value:3
key:3 value:4
Go中的struct關鍵字和C#中的作用是相同的,即定義一個結構體。因為Go中是沒有類這個概念的,所以struct就相當於是C#中class的定義。同樣的,struct在Go中是值型別結構,因此使用的時候一定需要注意案值傳遞導致的複製問題。(需要注意的是Go中的struct只能定義欄位,不能定義函數。)
// struct的定義是配合type關鍵字一起使用的
type People struct {
name string // 定義的欄位和Go語言其他的風格相同,名字在前,型別在後
age int
}
Go中的package和C#的namespace基本相同,就是定義組織的一個包,主要作用是對程式碼模組進行隔離。但Go和C#不同的是,C#十分靈活,即使不在一個資料夾下的程式碼都可以定義為相同的namespace。但是Go中package內的檔案都需要在相同的資料夾內才能被正確編譯,並且一個資料夾內只能出現最多一個包名。除此之外,類似於C#中的Main方法,Go中可執行程式的執行入口也是一個 main函數,但是main函數必須定義在package main下。
// Go中,同一個資料夾只能同時存在一個包名
// 包名可以和資料夾名不同,但是必須有且只有一個
package main
// main函數只能在main包下才能正確作為啟動函數執行
func main() {
//do something
}
// 同資料夾下的另一個檔案,比如hello.go
package hello //編譯器報錯
Import和using的作用都是用來匯入其他模組的程式碼。但是Go比C#多了一個強制要求:沒有在程式碼模組中使用import或者是定義了但是沒有使用的變數,在編譯時會直接報錯。這樣做的目的除了使程式碼看起來更簡潔以外,最主要的原因是import語句還有另一個重要功能就是呼叫包中的init()函數。例如如下程式碼:
// hello資料夾下的demo資料夾內的 demo.go
package demo
var me string
func init() {
me = "jeffery"
}
func SayHello() {
fmt.Printf("hello, %s", me)
}
// hello資料夾下的hello.go
package main
import "hello/demo"
func main() {
demo.SayHello() // 輸出:hello, jeffery
}
上述的程式定義了一個demo檔案,當demo檔案第一次被import關鍵字載入到其他包時,會自動呼叫其init()函數,這時就會把變數me賦值為jeffery,之後呼叫SayHello()函數時,返回的就都是」hello, jeffery」了。也正是因為init函數的存在,不使用的import需要被刪除,因為如果不刪除很有可能會自動呼叫到未被使用的包內的 init 函數。
把 type和class 對比其實是不太合理的。因為 C#中class關鍵字是定義一個型別和這個型別的具體實現,比如下述的程式碼在 C#中的意思是定義一個名為People的類,並且定義了這類中有一個屬性 Age。
interface IAnimal {
public void Move();
}
class People {
public int Age { get;set; }
}
然而Go中的type關鍵字僅僅是用來定義型別名稱的,如果想要定義其實現,必須後面再更上具體實現的關鍵字。比如上述的程式碼定義在Go中就變成了如下:
type IAnimal interface {
Move()
}
type People struct {
Age int
}
上述只是 type 的最常用用法,除此之外 type 還有兩個其他的用法:
type Human People
這樣的語句相當於用People型別定義了一個Human的新型別。注意,這裡是一個新型別,而不是 C#中的繼承。因此如果People內有一個Move函數,那Human物件是無法呼叫這個Move函數的,如果非要使用,則需要強制型別轉換。(Go中的強制型別轉換是型別+ (),比如上述的例子 Human(People)就可以把 People 型別強轉為 Human 型別)
type Human = People
如果使用了等號進行定義,那就相當於給型別 People 定義了一個別名 Human,這種情況下 People 中的程式碼 Human 也是可以正常使用的。上面兩種用法基本都不常用,這裡只做瞭解即可。
Go中的defer和C#的finally是一樣的,在一個方法執行結束退出之前只可以幹一件事。而和 C#不太一樣的是,Go中的 defer 語句不用必須寫在最後,比如我們會經常看到這樣風格的程式碼:
var mutex sync.Mutex // 一個全域性鎖,可以類似的等價於C#中的Monitor類
func do() {
mutex.Lock()
defer mutex.Unlock()
// ...
}
上面這個例子的意思是定義一個全域性鎖,在do函數進入時,加鎖,在退出時解鎖。之後再去寫自己的業務邏輯。除此之外,defer也可以寫多個,但最終的執行順序是從下向上執行,也就是最後定義的defer先執行。
這個關鍵字是為了相容C語言中的 fallthrough,其目的是是在 switch-case 語句中再向下跳一個case,比如下面這個例子:
switch n := "a"; n {
case n == "a":
fmt.Println("a")
fallthrough
case n == "b":
fmt.Println("b")
case n == "c":
fmt.Println("c")
}
這個例子的最終輸出結果就是:
a
b
和其他函數(比如 JavaScript 的 function,Python 中的 def)一樣,Go中的 func就是用來定義函數的。
// 定義了一個函數名稱為getName的函數
// 其中包含一個int型別的引數id
// 以及兩個返回值,string和bool型別
func getName(id int) (string, bool) {
return "jeffery", true
}
Go中的函數以及其他一系列需要定義型別的語法中,永遠都遵循名稱在前,型別在後。此外,Go中的func同樣也可以配合type使用定義C#中的委託,比如我們可以在 C#中定義一個.Net Core 的中介軟體:
public delegate void handleFunc(HttpContext httpContext);
public delegate handleFunc middleware(handleFunc next);
這樣的程式碼可以在 Go中這樣實現:
type handleFunc func(httpContext HttpContext)
type middleware func(next handleFunc) handleFunc
Go語言相較於C#在關鍵字上的優點有以下幾個:
1.更簡潔的語法:Go語言致力於簡化程式碼的編寫和理解,使得語言關鍵字的數量更少,更加簡潔明瞭。相比之下,C#擁有更多的關鍵字,從而使程式碼的可讀性稍微降低。
2.更好的並行性支援:Go語言天然支援並行程式設計,通過goroutine和channel管道,可以輕鬆實現高並行的程式。而C#對於並行程式設計需要手動處理鎖,號誌等機制來控制執行緒的並行,程式碼比較繁瑣。
3.更好的記憶體管理:Go語言使用垃圾回收機制,不需要開發者手動管理記憶體,避免了許多記憶體漏失等問題。相比之下,C#需要開發者手動進行記憶體管理,需要使用using關鍵字或者手動釋放記憶體等機制來控制記憶體,這增加了程式碼的複雜性。
4.更好的效能:由於採用了更簡潔的語法和更好的記憶體管理,Go語言編寫的程式具有更好的效能表現。與C#相比,Go語言的程式不僅執行速度更快,而且資源消耗更少,效能更出色。