原文鏈接:https://chai2010.cn/advanced-go-programming-book/ch1-basic/ch1-01-genesis.html
Go 語(yǔ)言最初由 Google 公司的 Robert Griesemer、Ken Thompson 和 Rob Pike 三個(gè)大牛于 2007 年開始設(shè)計(jì)發(fā)明,設(shè)計(jì)新語(yǔ)言的最初的洪荒之力來自于對(duì)超級(jí)復(fù)雜的 C++11 特性的吹捧報(bào)告的鄙視,最終的目標(biāo)是設(shè)計(jì)網(wǎng)絡(luò)和多核時(shí)代的 C 語(yǔ)言。到 2008 年中期,語(yǔ)言的大部分特性設(shè)計(jì)已經(jīng)完成,并開始著手實(shí)現(xiàn)編譯器和運(yùn)行時(shí),大約在這一年 Russ Cox 作為主力開發(fā)者加入。到了 2009 年,Go 語(yǔ)言已經(jīng)逐步趨于穩(wěn)定。同年 9 月,Go 語(yǔ)言正式發(fā)布并開源了代碼。
Go 語(yǔ)言很多時(shí)候被描述為“類 C 語(yǔ)言”,或者是“21 世紀(jì)的 C 語(yǔ)言”。從各種角度看,Go 語(yǔ)言確實(shí)是從 C 語(yǔ)言繼承了相似的表達(dá)式語(yǔ)法、控制流結(jié)構(gòu)、基礎(chǔ)數(shù)據(jù)類型、調(diào)用參數(shù)傳值、指針等諸多編程思想,還有徹底繼承和發(fā)揚(yáng)了 C 語(yǔ)言簡(jiǎn)單直接的暴力編程哲學(xué)等。圖1-1是《Go語(yǔ)言圣經(jīng)》中給出的 Go 語(yǔ)言的基因圖譜,我們可以從中看到有哪些編程語(yǔ)言對(duì) Go 語(yǔ)言產(chǎn)生了影響。
圖 1-1 Go 語(yǔ)言基因族譜
首先看基因圖譜的左邊一支??梢悦鞔_看出 Go 語(yǔ)言的并發(fā)特性是由貝爾實(shí)驗(yàn)室的 Hoare 于 1978 年發(fā)布的 CSP 理論演化而來。其后,CSP 并發(fā)模型在 Squeak/NewSqueak 和 Alef 等編程語(yǔ)言中逐步完善并走向?qū)嶋H應(yīng)用,最終這些設(shè)計(jì)經(jīng)驗(yàn)被消化并吸收到了 Go 語(yǔ)言中。業(yè)界比較熟悉的 Erlang 編程語(yǔ)言的并發(fā)編程模型也是 CSP 理論的另一種實(shí)現(xiàn)。
再看基因圖譜的中間一支。中間一支主要包含了 Go 語(yǔ)言中面向?qū)ο蠛桶匦缘难莼瘹v程。Go 語(yǔ)言中包和接口以及面向?qū)ο蟮忍匦詣t繼承自 Niklaus Wirth 所設(shè)計(jì)的 Pascal 語(yǔ)言以及其后所衍生的相關(guān)編程語(yǔ)言。其中包的概念、包的導(dǎo)入和聲明等語(yǔ)法主要來自于 Modula-2 編程語(yǔ)言,面向?qū)ο筇匦运峁┑姆椒ǖ穆暶髡Z(yǔ)法等則來自于 Oberon 編程語(yǔ)言。最終 Go 語(yǔ)言演化出了自己特有的支持鴨子面向?qū)ο竽P偷碾[式接口等諸多特性。
最后是基因圖譜的右邊一支,這是對(duì) C 語(yǔ)言的致敬。Go 語(yǔ)言是對(duì) C 語(yǔ)言最徹底的一次揚(yáng)棄,不僅僅是語(yǔ)法和 C 語(yǔ)言有著很多差異,最重要的是舍棄了 C 語(yǔ)言中靈活但是危險(xiǎn)的指針運(yùn)算。而且,Go 語(yǔ)言還重新設(shè)計(jì)了 C 語(yǔ)言中部分不太合理運(yùn)算符的優(yōu)先級(jí),并在很多細(xì)微的地方都做了必要的打磨和改變。當(dāng)然,C 語(yǔ)言中少即是多、簡(jiǎn)單直接的暴力編程哲學(xué)則被 Go 語(yǔ)言更徹底地發(fā)揚(yáng)光大了(Go 語(yǔ)言居然只有 25 個(gè)關(guān)鍵字,sepc 語(yǔ)言規(guī)范還不到 50 頁(yè))。
Go 語(yǔ)言其它的一些特性零散地來自于其他一些編程語(yǔ)言;比如 iota 語(yǔ)法是從 APL 語(yǔ)言借鑒,詞法作用域與嵌套函數(shù)等特性來自于 Scheme 語(yǔ)言(和其他很多編程語(yǔ)言)。Go 語(yǔ)言中也有很多自己發(fā)明創(chuàng)新的設(shè)計(jì)。比如 Go 語(yǔ)言的切片為輕量級(jí)動(dòng)態(tài)數(shù)組提供了有效的隨機(jī)存取的性能,這可能會(huì)讓人聯(lián)想到鏈表的底層的共享機(jī)制。還有 Go 語(yǔ)言新發(fā)明的 defer 語(yǔ)句(Ken 發(fā)明)也是神來之筆。
作為 Go 語(yǔ)言標(biāo)志性的并發(fā)編程特性則來自于貝爾實(shí)驗(yàn)室的 Tony Hoare 于 1978 年發(fā)表的鮮為外界所知的關(guān)于并發(fā)研究的基礎(chǔ)文獻(xiàn):順序通信進(jìn)程(communicating sequential processes ,縮寫為 CSP)。在最初的 CSP 論文中,程序只是一組沒有中間共享狀態(tài)的平行運(yùn)行的處理過程,它們之間使用管道進(jìn)行通信和控制同步。Tony Hoare 的 CSP 并發(fā)模型只是一個(gè)用于描述并發(fā)性基本概念的描述語(yǔ)言,它并不是一個(gè)可以編寫可執(zhí)行程序的通用編程語(yǔ)言。
CSP 并發(fā)模型最經(jīng)典的實(shí)際應(yīng)用是來自愛立信發(fā)明的 Erlang 編程語(yǔ)言。不過在 Erlang 將 CSP 理論作為并發(fā)編程模型的同時(shí),同樣來自貝爾實(shí)驗(yàn)室的 Rob Pike 以及其同事也在不斷嘗試將 CSP 并發(fā)模型引入當(dāng)時(shí)的新發(fā)明的編程語(yǔ)言中。他們第一次嘗試引入 CSP 并發(fā)特性的編程語(yǔ)言叫 Squeak(老鼠的叫聲),是一個(gè)用于提供鼠標(biāo)和鍵盤事件處理的編程語(yǔ)言,在這個(gè)語(yǔ)言中管道是靜態(tài)創(chuàng)建的。然后是改進(jìn)版的 Newsqueak 語(yǔ)言(新版老鼠的叫聲),新提供了類似 C 語(yǔ)言語(yǔ)句和表達(dá)式的語(yǔ)法,還有類似 Pascal 語(yǔ)言的推導(dǎo)語(yǔ)法。Newsqueak 是一個(gè)帶垃圾回收的純函數(shù)式語(yǔ)言,它再次針對(duì)鍵盤、鼠標(biāo)和窗口事件管理。但是在 Newsqueak 語(yǔ)言中管道已經(jīng)是動(dòng)態(tài)創(chuàng)建的,管道屬于第一類值、可以保存到變量中。然后是 Alef 編程語(yǔ)言(Alef 也是 C 語(yǔ)言之父 Ritchie 比較喜愛的編程語(yǔ)言),Alef 語(yǔ)言試圖將 Newsqueak 語(yǔ)言改造為系統(tǒng)編程語(yǔ)言,但是因?yàn)槿鄙倮厥諜C(jī)制而導(dǎo)致并發(fā)編程很痛苦(這也是繼承 C 語(yǔ)言手工管理內(nèi)存的代價(jià))。在 Aelf 語(yǔ)言之后還有一個(gè)叫 Limbo 的編程語(yǔ)言(地獄的意思),這是一個(gè)運(yùn)行在虛擬機(jī)中的腳本語(yǔ)言。Limbo 語(yǔ)言是 Go 語(yǔ)言最接近的祖先,它和 Go 語(yǔ)言有著最接近的語(yǔ)法。到設(shè)計(jì) Go 語(yǔ)言時(shí),Rob Pike 在 CSP 并發(fā)編程模型的實(shí)踐道路上已經(jīng)積累了幾十年的經(jīng)驗(yàn),關(guān)于 Go 語(yǔ)言并發(fā)編程的特性完全是信手拈來,新編程語(yǔ)言的到來也是水到渠成了。
圖1-2展示了 Go 語(yǔ)言庫(kù)早期代碼庫(kù)日志可以看出最直接的演化歷程(Git 用 git log --before={2008-03-03} --reverse
命令查看)。
圖 1-2 Go 語(yǔ)言開發(fā)日志
從早期提交日志中也可以看出,Go 語(yǔ)言是從 Ken Thompson 發(fā)明的 B 語(yǔ)言、Dennis M. Ritchie 發(fā)明的 C 語(yǔ)言逐步演化過來的,它首先是 C 語(yǔ)言家族的成員,因此很多人將 Go 語(yǔ)言稱為 21 世紀(jì)的 C 語(yǔ)言。
圖1-3是 Go 語(yǔ)言中來自貝爾實(shí)驗(yàn)室特有并發(fā)編程基因的演化過程:
圖 1-3 Go 語(yǔ)言并發(fā)演化歷史
縱觀整個(gè)貝爾實(shí)驗(yàn)室的編程語(yǔ)言的發(fā)展進(jìn)程,從 B 語(yǔ)言、C 語(yǔ)言、Newsqueak、Alef、Limbo 語(yǔ)言一路走來,Go 語(yǔ)言繼承了來著貝爾實(shí)驗(yàn)室的半個(gè)世紀(jì)的軟件設(shè)計(jì)基因,終于完成了 C 語(yǔ)言革新的使命。縱觀這幾年來的發(fā)展趨勢(shì),Go 語(yǔ)言已經(jīng)成為云計(jì)算、云存儲(chǔ)時(shí)代最重要的基礎(chǔ)編程語(yǔ)言。
按照慣例,介紹所有編程語(yǔ)言的第一個(gè)程序都是“Hello, World!”。雖然本教假設(shè)讀者已經(jīng)了解了 Go 語(yǔ)言,但是我們還是不想打破這個(gè)慣例(因?yàn)檫@個(gè)傳統(tǒng)正是從 Go 語(yǔ)言的前輩 C 語(yǔ)言傳承而來的)。下面的代碼展示的 Go 語(yǔ)言程序輸出的是中文“你好, 世界!”。
package main
import "fmt"
func main() {
fmt.Println("你好, 世界!")
}
將以上代碼保存到 hello.go
文件中。因?yàn)榇a中有非 ASCII 的中文字符,我們需要將文件的編碼顯式指定為無 BOM 的 UTF8 編碼格式(源文件采用 UTF8 編碼是 Go 語(yǔ)言規(guī)范所要求的)。然后進(jìn)入命令行并切換到 hello.go
文件所在的目錄。目前我們可以將 Go 語(yǔ)言當(dāng)作腳本語(yǔ)言,在命令行中直接輸入 go run hello.go
來運(yùn)行程序。如果一切正常的話。應(yīng)該可以在命令行看到輸出 你好, 世界!
的結(jié)果。
現(xiàn)在,讓我們簡(jiǎn)單介紹一下程序。所有的 Go 程序,都是由最基本的函數(shù)和變量構(gòu)成,函數(shù)和變量被組織到一個(gè)個(gè)單獨(dú)的 Go 源文件中,這些源文件再按照作者的意圖組織成合適的 package,最終這些 package 再有機(jī)地組成一個(gè)完整的 Go 語(yǔ)言程序。其中,函數(shù)用于包含一系列的語(yǔ)句(指明要執(zhí)行的操作序列),以及執(zhí)行操作時(shí)存放數(shù)據(jù)的變量。我們這個(gè)程序中函數(shù)的名字是 main。雖然Go語(yǔ)言中,函數(shù)的名字沒有太多的限制,但是 main 包中的 main 函數(shù)默認(rèn)是每一個(gè)可執(zhí)行程序的入口。而 package
則用于包裝和組織相關(guān)的函數(shù)、變量和常量。在使用一個(gè) package 之前,我們需要使用 import 語(yǔ)句導(dǎo)入包。例如,我們這個(gè)程序中導(dǎo)入了 fmt 包(fmt 是 format 單詞的縮寫,表示格式化相關(guān)的包),然后我們才可以使用 fmt 包中的 Println 函數(shù)。
而雙引號(hào)包含的“你好, 世界!”則是 Go 語(yǔ)言的字符串面值常量。和 C 語(yǔ)言中的字符串不同,Go 語(yǔ)言中的字符串內(nèi)容是不可變更的。在以字符串作為參數(shù)傳遞給 fmt.Println 函數(shù)時(shí),字符串的內(nèi)容并沒有被復(fù)制——傳遞的僅僅是字符串的地址和長(zhǎng)度(字符串的結(jié)構(gòu)在 reflect.StringHeader
中定義)。在 Go 語(yǔ)言中,函數(shù)參數(shù)都是以復(fù)制的方式(不支持以引用的方式)傳遞(比較特殊的是,Go 語(yǔ)言閉包函數(shù)對(duì)外部變量是以引用的方式使用)。
更多建議: