天天看點

深入分析Go語言與C#的異同

作者:葡萄城GrapeCity

為了更加深入地介紹Go語言以及與C#語言的比較,本文将會從多個次元出發進行詳細的闡述。首先,将從Go語言的關鍵字方面介紹Go與C#在語言特性上的異同,并且探讨兩種語言在關鍵字方面的優化和不足之處。

其次,本文将通過代碼示例、性能測試等方式,展示Go語言在關鍵字方面的優勢,進而為讀者呈現出Go語言的強大之處。除此之外,為了更好地幫助讀者了解Go語言,本文還将介紹一些優秀的Go語言工具和社群資源,供讀者進一步學習和探索。相信通過這些内容的全面介紹,讀者們會對Go語言有更全面深入的認識和了解。

1.Go的前世今生

1.1Go語言誕生的過程

話說早在 2007 年 9 月的一天,Google 工程師 Rob Pike 和往常一樣啟動了一個 C++項目的建構,按照他之前的經驗,這個建構應該需要持續 1 個小時左右。這時他就和 Google公司的另外兩個同僚 Ken Thompson 以及 Robert Griesemer 開始吐槽并且說出了自己想搞一個新語言的想法。當時 Google 内部主要使用 C++建構各種系統,但 C++複雜性巨大并且原生缺少對并發的支援,使得這三位大佬苦惱不已。

深入分析Go語言與C#的異同

第一天的閑聊初有成效,他們迅速構想了一門新語言:能夠給程式員帶來快樂,能夠比對未來的硬體發展趨勢以及滿足 Google 内部的大規模網絡服務。并且在第二天,他們又碰頭開始認真構思這門新語言。第二天會後,Robert Griesemer 發出了如下的一封郵件:

深入分析Go語言與C#的異同

可以從郵件中看到,他們對這個新語言的期望是:在 C 語言的基礎上,修改一些錯誤,删除一些诟病的特性,增加一些缺失的功能。比如修複 Switch 語句,加入 import 語句,增加垃圾回收,支援接口等。而這封郵件,也成了 Go 的第一版設計初稿。

在這之後的幾天,Rob Pike 在一次開車回家的路上,為這門新語言想好了名字Go。在他心中,”Go”這個單詞短小,容易輸入并且可以很輕易地在其後組合其他字母,比如 Go 的工具鍊:goc 編譯器、goa 彙編器、gol 連接配接器等,并且這個單詞也正好符合他們對這門語言的設計初衷:簡單。

1.2逐漸成型

在統一了 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 語言邁上了穩定演化的道路。

1.3正式釋出

2009年10月30日,羅伯·派克在Google Techtalk上做了一次有關 Go語言的演講,這也是Go語言第一次公之于衆。十天後,也就是 2009 年 11 月 10 日,谷歌官方宣布 Go 語言項目開源,之後這一天也被 Go 官方确定為 Go 語言的誕生日。

深入分析Go語言與C#的異同

(Go語言吉祥物Gopher)

1.4.Go安裝指導

Go語言安裝包下載下傳

前往Go 官網下載下傳。

深入分析Go語言與C#的異同

選擇對應的安裝版本即可(建議選擇.msi檔案)。

檢視是否安裝成功 + 環境是否配置成功

打開指令行:win + R 打開運作框,輸入 cmd 指令,打開指令行視窗。

深入分析Go語言與C#的異同

指令行輸入 go version 檢視安裝版本,顯示下方内容即為安裝成功。

深入分析Go語言與C#的異同

2.Go和C#的關鍵字比較

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關鍵字)

2.1Go與C#都有的關鍵字

  • break
  • case
  • const
  • continue
  • default
  • else
  • for
  • goto
  • if
  • interface
  • return
  • struct
  • switch
  • var

以上14個關鍵字是Go和C#共有的,它們之中大部分的用法都是完全相同的,這裡重點說一下在Go中有特殊文法的關鍵字。

2.1.1.Var

Var在Go中用來表示定義變量,但文法和 C#不同。C#中隻有一種定義變量的方法,而 Go中有兩種,它們分别是:

  • 普通方式
var i int = 1           

這種方式是Go的原始變量定義方式,一般包級别的變量都是這樣定義的,并且如果定義那些編譯器可以自動推斷的類型,比如上述的例子,其後的類型可以省略。

  • 文法糖(是的,Go中也有文法糖…)
i, j := 1, "hello"           

上述代碼可簡寫為文法糖形式。事實上,Go代碼中,90%變量都以此方式定義,因為幾乎所有函數都有多個傳回值,這種定義方式可省去許多麻煩。

2.1.2.Switch-case-default

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 自動格式化掉。

2.1.3. If-else

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)
    }
}           

2.1.4. For

Go中的循環控制語句有且隻有一個 for 關鍵字。而 C#中的 while、foreach 等在Go中都是通過 for 的各種變形達成的。

  • while 語句
for true {
    // ...
  }           
  • for 語句
for i := 0; i < n; i++ {
    // ...
  }           

Go中普通的 for 循環和 C#中唯一的差别就是 i++從表達式變成了語句。也就是說,Go中沒有i = i++這樣的文法,也沒有++i這樣的文法,隻有i++這種文法。

  • foreach 語句
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           

2.1.5 Struct

Go中的struct關鍵字和C#中的作用是相同的,即定義一個結構體。因為Go中是沒有類這個概念的,是以struct就相當于是C#中class的定義。同樣的,struct在Go中是值類型結構,是以使用的時候一定需要注意案值傳遞導緻的複制問題。(需要注意的是Go中的struct隻能定義字段,不能定義函數。)

// struct的定義是配合type關鍵字一起使用的
type People struct {
    name string   // 定義的字段和Go語言其他的風格相同,名字在前,類型在後
    age int
}           

2.2Go與C#不一樣但使用方法差不多的關鍵字

  • package
  • import
  • type
  • defer

2.2.1.Package與namespace

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   //編譯器報錯           

2.2.2.Import與using

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 函數。

2.2.3. Type與class

  • 正常用法

把 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 也是可以正常使用的。上面兩種用法基本都不常用,這裡隻做了解即可。

2.2.4.Defer與finally

Go中的defer和C#的finally是一樣的,在一個方法執行結束退出之前隻可以幹一件事。而和 C#不太一樣的是,Go中的 defer 語句不用必須寫在最後,比如我們會經常看到這樣風格的代碼:

var mutex sync.Mutex    // 一個全局鎖,可以類似的等價于C#中的Monitor類
func do() {
  mutex.Lock()
  defer mutex.Unlock()
  // ...
}           

上面這個例子的意思是定義一個全局鎖,在do函數進入時,加鎖,在退出時解鎖。之後再去寫自己的業務邏輯。除此之外,defer也可以寫多個,但最終的執行順序是從下向上執行,也就是最後定義的defer先執行。

2.3Go有而 C#沒有的關鍵字

  • fallthrough
  • func

2.3.1. Fallthrough

這個關鍵字是為了相容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

2.3.2.Func

和其他函數(比如 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           

3.文章小結

Go語言相較于C#在關鍵字上的優點有以下幾個:

1.更簡潔的文法:Go語言緻力于簡化代碼的編寫和了解,使得語言關鍵字的數量更少,更加簡潔明了。相比之下,C#擁有更多的關鍵字,進而使代碼的可讀性稍微降低。

2.更好的并發性支援:Go語言天然支援并發程式設計,通過goroutine和channel管道,可以輕松實作高并發的程式。而C#對于并發程式設計需要手動處理鎖,信号量等機制來控制線程的并發,代碼比較繁瑣。

3.更好的記憶體管理:Go語言使用垃圾回收機制,不需要開發者手動管理記憶體,避免了許多記憶體洩漏等問題。相比之下,C#需要開發者手動進行記憶體管理,需要使用using關鍵字或者手動釋放記憶體等機制來控制記憶體,這增加了代碼的複雜性。

4.更好的性能:由于采用了更簡潔的文法和更好的記憶體管理,Go語言編寫的程式具有更好的性能表現。與C#相比,Go語言的程式不僅運作速度更快,而且資源消耗更少,性能更出色。