Google最近發(fā)布新型的編程語(yǔ)言,,Go,。它被設(shè)計(jì)為將現(xiàn)代編程語(yǔ)言的先進(jìn) 性帶入到目前仍由C語(yǔ)言占統(tǒng)治地位的系統(tǒng)層面,。然而,,這一語(yǔ)言仍在試驗(yàn)階段并在不斷演變。 Go語(yǔ)言的設(shè)計(jì)者計(jì)劃設(shè)計(jì)一門(mén)簡(jiǎn)單,、高效,、安全和 并發(fā)的語(yǔ)言。這門(mén)語(yǔ)言簡(jiǎn)單到甚至不需要有一個(gè)符號(hào)表來(lái)進(jìn)行詞法分析,。它可以快速地編譯,;整個(gè)工程的編譯時(shí)間在秒以下的情況是常事。它具備垃圾回收功能,,因 此從內(nèi)存的角度是安全的,。它進(jìn)行靜態(tài)類(lèi)型檢查,并且不允許強(qiáng)制類(lèi)型轉(zhuǎn)換,,因而對(duì)于類(lèi)型而言是安全的,。同時(shí)語(yǔ)言還內(nèi)建了強(qiáng)大的并發(fā)實(shí)現(xiàn)機(jī)制。 閱讀GoGo的語(yǔ)法傳承了與C一樣的風(fēng)格,。程序由函數(shù)組成,,而函數(shù)體是一系列的語(yǔ)句序列。一段代碼塊用花括號(hào)括起來(lái),。這門(mén)語(yǔ)言保留有限的關(guān)鍵字,。表達(dá)式使用 同樣的中綴運(yùn)算符。語(yǔ)法上并 無(wú) 太多出奇之處,。 Go語(yǔ)言的作者在設(shè)計(jì)這一語(yǔ)言時(shí)堅(jiān)持一個(gè)單一的指導(dǎo)原則:簡(jiǎn)單明了至上,。一些新的語(yǔ)法構(gòu)件提供了簡(jiǎn)明地表達(dá)一些約定俗成的概 念的方式,相較之下用C表達(dá)顯得冗長(zhǎng),。而其他方面則是針對(duì)幾十年的使用所呈現(xiàn)出來(lái)的一些不合理的語(yǔ)言選擇作出了改進(jìn),。 變量聲明變量是如下聲明的: var sum int // 簡(jiǎn)單聲明 最值得注意的是,,這些聲明里的類(lèi)型跟在變量名的后面。乍一看有點(diǎn)怪,,但這更清晰明了,。比如,以下面這個(gè)C片段來(lái)說(shuō): int* a, b; 它并明了,,但這里實(shí)際的意思是a是一個(gè)指針,,但b不是。如果要將兩者都聲明為指針,,必須要重復(fù)星號(hào),。然后在Go語(yǔ)言里,通過(guò)如下方式可以將兩者都 聲明為指針: var a, b *int 如果一個(gè)變量初始化了,,編譯器通常能推斷它的類(lèi)型,,所以程序員不必顯式的敲出來(lái): var label = "name" 然而,在這種情況下var幾乎顯得是多余了,。因此,,Go的作者引入了一個(gè)新的運(yùn) 算符來(lái) 聲明和初始化一個(gè)新的變量: name := "Samuel" 條件語(yǔ)句Go語(yǔ)言當(dāng)中的條件句與C當(dāng)中所熟知的if-else構(gòu)造一樣,但條件不需要被打包在括號(hào)內(nèi),。這樣可以減少閱讀代碼時(shí)的視覺(jué)上的混亂,。 括號(hào)并不是唯一被移去的視覺(jué)干擾。在條件之間可以包括一個(gè)簡(jiǎn)單的語(yǔ)句,,所以如下的代碼: result := someFunc(); 可以被精簡(jiǎn)成: if result := someFunc(); result > 0 { 然而,,在后面這個(gè)例子當(dāng)中,result只在條件塊內(nèi)部有效—— 而前者 中,,它在整個(gè)包含它的上下文中都是可存取的,。 分支語(yǔ)句分支語(yǔ)句同樣是似曾相識(shí),但也有增強(qiáng),。像條件語(yǔ)句一樣,,它允許一個(gè)簡(jiǎn)單的語(yǔ)句位于分支的表達(dá)式之前。然而,,他們相對(duì)于在C語(yǔ)言中的分支而言走得更 遠(yuǎn),。 首先,為了讓分支跳轉(zhuǎn)更簡(jiǎn)明,,作了兩個(gè)修改,。情況可以是逗號(hào)分隔的列表,而fall-throuth也不再是默認(rèn)的行為,。 因此,,如下的C代碼: int result; 在Go里就變成了這樣: var result int 第二點(diǎn),Go的分支跳轉(zhuǎn)可以匹配比整數(shù)和字符更多的內(nèi)容,,任何有效的表達(dá)式都可以作為跳轉(zhuǎn)語(yǔ)句值,。只要它與分支條件的類(lèi)型是一樣的,。 因此如下的C代碼: int result = calculate(); 在Go里可以這樣表達(dá): switch result := calculate(); true { 這些都是公共的約定俗成,比如如果分支值省略了,,就是默認(rèn)為真,,所以上面的代碼可以這樣寫(xiě): switch result := calculate(); { 循環(huán)Go只有一個(gè)關(guān)鍵字用于引入循環(huán)。但它提供了除do-while外C語(yǔ)言當(dāng)中所有可用的循環(huán)方式,。 條件for a > b { /* ... */ } 初始,,條件和步進(jìn)for i := 0; i < 10; i++ { /* ... */ } 范圍range語(yǔ)句右邊的表達(dá)式必須是array,slice,,string或者map,, 或是指向array的指針,也可以是channel,。 for i := range "hello" { /* ... */ } 無(wú)限循環(huán)for { /* ever */ } 函數(shù)聲明函數(shù)的語(yǔ)法與C不同,。就像變量聲明一樣,類(lèi)型是在它們所描述的術(shù)語(yǔ)之后聲明的,。在C語(yǔ)言中: int add(int a, b) { return a + b } 在Go里面是這樣描述的: func add(a, b int) int { return a + b } 多返回值在C語(yǔ)言當(dāng)中常見(jiàn)的做法是保留一個(gè)返回值來(lái)表示錯(cuò)誤(比如,,read()返回 0),或 者保留返回值來(lái)通知狀態(tài),,并將傳遞存儲(chǔ)結(jié)果的內(nèi)存地址的指針。這容易產(chǎn)生了不安全的編程實(shí)踐,,因此在像Go語(yǔ)言這樣有良好管理的語(yǔ)言中是不可行的,。 認(rèn)識(shí)到這一問(wèn)題的影響已超出了函數(shù)結(jié)果與錯(cuò)誤通訊的簡(jiǎn)單需求的范疇,Go的作者們?cè)谡Z(yǔ)言中內(nèi)建了函數(shù)返回多個(gè)值的能力,。 作為例子,,這個(gè)函數(shù)將返回整數(shù)除法的兩個(gè)部分: func divide(a, b int) (int, int) { 有了多個(gè)返回值,有良好的代碼文檔會(huì)更好——而Go允許你給返回值命名,,就像參數(shù)一樣,。你可以對(duì)這些返回的變量賦值,就像其它的變量一樣,。所以我們 可以重寫(xiě)divide: func divide(a, b int) (quotient, remainder int) { 多返回值的出現(xiàn)促進(jìn)了"comma-ok"的模式,。有可能失敗的函數(shù)可以返回第二個(gè)布爾結(jié)果來(lái)表示成功。作為替代,,也可以返回一個(gè)錯(cuò)誤對(duì)象,,因此像 下面這樣的代碼也就不見(jiàn)怪了: if result, ok := moreMagic(); ok { 匿名函數(shù)有了垃圾收集器意味著為許多不同的特性敞開(kāi)了大門(mén)——其中就包括匿名函數(shù)。Go為聲明匿名函數(shù)提供了簡(jiǎn)單的語(yǔ)法,。像許多動(dòng)態(tài)語(yǔ)言一樣,,這些函數(shù)在它 們被定義的范圍內(nèi)創(chuàng)建了詞法閉包。 考慮如下的程序: func makeAdder(x int) (func(int) int) { 基本類(lèi)型像C語(yǔ)言一樣,,Go提供了一系列的基本類(lèi)型,,常見(jiàn)的布爾,,整數(shù)和浮點(diǎn)數(shù)類(lèi)型都具備。它有一個(gè)Unicode的字符串類(lèi)型和數(shù)組類(lèi)型,。同時(shí)該語(yǔ)言還引 入了兩 種新的類(lèi)型:slice 和map,。 數(shù)組和切片Go語(yǔ)言當(dāng)中的數(shù)組不是像C語(yǔ)言那樣動(dòng)態(tài)的。它們的大小是類(lèi)型的一部分,,在編譯時(shí)就決定了,。數(shù)組的索引還是使用的熟悉的C語(yǔ)法(如 a[i]),并且與C一樣,,索引是由0開(kāi)始的,。編譯器提供了內(nèi)建的功能在編譯時(shí)求得一個(gè)數(shù)組的長(zhǎng)度 (如 len(a))。如果試圖超過(guò)數(shù)組界限寫(xiě)入,,會(huì)產(chǎn)生一個(gè)運(yùn)行時(shí)錯(cuò)誤,。 Go還提供了切片(slices),作為數(shù)組的變形,。一個(gè)切片(slice)表示一個(gè)數(shù)組內(nèi)的連續(xù)分段,,支持程序員指定底層存儲(chǔ)的明確部分。構(gòu)建一 個(gè)切片 的語(yǔ)法與訪(fǎng)問(wèn)一個(gè)數(shù)組元素類(lèi)似: /* Construct a slice on ary that starts at s and is len elements long */ 該切片所引用的數(shù)組分段可以通過(guò)將新的切片賦值給同一變量來(lái)更改: /* Move the start of the slice forward by one, but do not move the end */ 切片的長(zhǎng)度可以更改,,只要不超出切片的容量,。切片s的容量是數(shù)組 從s[0]到數(shù)組尾端的大小,并由內(nèi)建的cap()函數(shù)返回,。一個(gè)切片的長(zhǎng)度永遠(yuǎn)不能超出它的容量,。 這里有一個(gè)展示長(zhǎng)度和容量交互的例子: a := [...]int{1,2,3,4,5} // The ... means "whatever length the initializer has" 通常,一個(gè)切片就是一個(gè)程序所需要的全部了,,在這種情況下,,程序員根本用不著一個(gè)數(shù)組,Go有兩種方式直接創(chuàng)建切片而不用引用底層存儲(chǔ): /* literal */ Map類(lèi)型幾乎每個(gè)現(xiàn)在流行的動(dòng)態(tài)語(yǔ)言都有的數(shù)據(jù)類(lèi)型,,但在C中不具備的,,就是dictionary。Go提供了一個(gè)基本的dictionary類(lèi)型叫做 map,。下 面的例子展示了如何創(chuàng)建和使用Go map: m := make(map[string] int) // A mapping of strings to ints 面向?qū)ο?/h2>Go語(yǔ)言支持類(lèi)似于C語(yǔ)言中使用的面向?qū)ο箫L(fēng)格,。數(shù)據(jù)被組織成structs,然后定義操作這些structs的函數(shù),。類(lèi)似于Python,,Go語(yǔ) 言提供 了定義函數(shù)并調(diào)用它們的方式,因此語(yǔ)法并不會(huì)笨拙,。 Struct類(lèi)型定義一個(gè)新的struct類(lèi)型很簡(jiǎn)單: type Point struct { 現(xiàn)在這一類(lèi)型的值可以通過(guò)內(nèi)建的函數(shù)new來(lái)分配,,這將返回一個(gè)指針,指向一塊 內(nèi)存單元,其所占內(nèi)存槽初始化為零,。 var p *Point = new(Point) 這顯得很冗長(zhǎng),,而Go語(yǔ)言的一個(gè)目標(biāo)是盡可能的簡(jiǎn)明扼要。所以提供了一個(gè)同時(shí)分配和初始化struct的語(yǔ)法: var p1 Point = Point{3,4} // Value 方法一旦聲明了類(lèi)型,,就可以將該類(lèi)型顯式的作為第一個(gè)參數(shù)來(lái)聲明函數(shù): func (self Point) Length() float { 這些函數(shù)之后可作為struct的方法而被調(diào)用: p := Point{3,4} 方法實(shí)際上既可以聲明為值也可以聲明為指針類(lèi)型,。Go將會(huì)適當(dāng)?shù)奶幚硪没蚪庖脤?duì)象,所以既可以對(duì)類(lèi)型T,,也可以對(duì)類(lèi)型*T聲明方式,,并合理地使用它們。 讓我們?yōu)?span style="FONT-FAMILY: Courier New">Point擴(kuò)展一個(gè)變換器: /* Note the receiver is *Point */ 然后我們可以像這樣調(diào)用: p.Scale(2); 很重要的一點(diǎn)是理解傳遞給MoveToXY的self和其它的參數(shù)一樣,,并且是值傳遞,,而不是引用傳遞。如果它被聲明為Point,,那么在方法內(nèi)修改的struct就不再跟調(diào)用方的一樣——值在它們傳遞給方法的時(shí)候被 拷貝,,并在調(diào)用結(jié)束后被丟棄。 接口像Ruby這樣的動(dòng)態(tài)語(yǔ)言所強(qiáng)調(diào)面向?qū)ο缶幊痰娘L(fēng)格認(rèn)為對(duì)象的行為比哪種對(duì)象是動(dòng)態(tài)類(lèi)型(duck typing)更為重要,。Go所 帶來(lái)的一個(gè)最強(qiáng)大的特性之一就是提供了可以在編程時(shí)運(yùn)用動(dòng)態(tài)類(lèi)型的思想而把行為定義的合法性檢查的工作推到編譯時(shí),。這一行為的名字被稱(chēng)作接口。 定義一個(gè)接口很簡(jiǎn)單: type Writer interface { 這里定義了一個(gè)接口和一個(gè)寫(xiě)字節(jié)緩沖的方法,。任何實(shí)現(xiàn)了這一方法的對(duì)象也實(shí)現(xiàn)了這一接口,。不需要像Java一樣進(jìn)行聲明,編譯器能推斷出來(lái),。這既給 予了動(dòng)態(tài)類(lèi)型的表達(dá)能力又保留了靜態(tài)類(lèi)型檢查的安全,。 Go當(dāng)中接口的運(yùn)作方式支持開(kāi)發(fā)者在編寫(xiě)程序的時(shí)候發(fā)現(xiàn)程序的類(lèi)型。如果幾個(gè)對(duì)象間存在公共行為,,而開(kāi)發(fā)者想要抽象這種行為,那么它就可以創(chuàng)建一個(gè) 接口并使用它,。 考慮如下的代碼: // Somewhere in some code: 需要特別指出的很重要的一點(diǎn)就是所有的對(duì)象都實(shí)現(xiàn)了這個(gè)空接口: interface {} 繼承Go語(yǔ)言不支持繼承,,至少與大多數(shù)語(yǔ)言的繼承不一樣。并不存在類(lèi)型的層次結(jié)構(gòu),。相較于繼承,,Go鼓勵(lì)使用組合和委派,并為此提供了相應(yīng)的語(yǔ)法甜點(diǎn)使 其更容易接受,。 有了這樣的定義: type Engine interface { 于是我可以像下面這樣編寫(xiě): func GoToWorkIn(c Car) { 當(dāng)我聲明Car這個(gè)struct的時(shí)候,,我定義了一個(gè)匿名成員。 這是一 個(gè)只能被其類(lèi)型識(shí)別的成員,。匿名成員與其它的成員一樣,,并有著和類(lèi)型一樣的名字。因此我還可以寫(xiě)成c.Engine.Start(),。 如果Car并沒(méi)有其自 身方法可以滿(mǎn)足調(diào)用的話(huà),編譯器自動(dòng)的會(huì)將在Car上的調(diào)用委派給它的Engine上面的方法,。 由匿名成員提供的分離方法的規(guī)則是保守的,。如果為一個(gè)類(lèi)型定義了一個(gè)方法,就使用它,。如果不是,,就使用為匿名成員定義的方法。如果有兩個(gè)匿名成員都 提供一 個(gè)方法,,編譯器將會(huì)報(bào)錯(cuò),,但只在該方法被調(diào)用的情況下。 這種組合是通過(guò)委派來(lái)實(shí)現(xiàn)的,,而不是繼承,。一旦匿名成員的方法被調(diào)用,控制流整個(gè)都被委派給了該方法,。所以你無(wú)法做到和下面的例子一 樣來(lái)模擬類(lèi)型層次: type Base struct {} 當(dāng)你創(chuàng)建一個(gè)Foo對(duì)象時(shí),,它將會(huì)影響Base的兩個(gè)方法。然而,,當(dāng)你調(diào)用MoreMagic時(shí),, 你將得不到期望的結(jié)果: f := new(Foo) 并發(fā)Go的作者選擇了消息傳遞模型來(lái)作為推薦的并發(fā)編程方法。該語(yǔ)言同樣支持共享內(nèi)存,,然后作者自有道理: 不要通過(guò)共享內(nèi)存來(lái)通信,,相反,通過(guò)通信來(lái)共享內(nèi)存,。 該語(yǔ)言提供了兩個(gè)基本的構(gòu)件來(lái)支持這一范型:goroutines和channels,。 Go例程Goroutine是輕量級(jí)的并行程序執(zhí)行路徑,與線(xiàn)程,,coroutine或者進(jìn)程類(lèi)似,。然而,它們彼此相當(dāng)不同,,因此Go作者決定給它一個(gè)新的 名字并 放棄其它術(shù)語(yǔ)可能隱含的意義,。 創(chuàng)建一個(gè)goroutine來(lái)運(yùn)行名為DoThis的函數(shù)十分簡(jiǎn)單: go DoThis() // but do not wait for it to complete 匿名的函數(shù)可以這樣使用: go func() { 這些goroutine將會(huì)通過(guò)Go運(yùn)行時(shí)而映射到適當(dāng)?shù)牟僮飨到y(tǒng)原語(yǔ)(比如,POSIX線(xiàn)程),。 通道類(lèi)型有了goroutine,,代碼的并行執(zhí)行就容易了。然而,,它們之間仍然需要通訊機(jī)制,。Channel提供一個(gè)FIFO通信隊(duì)列剛好能達(dá)到這一目的。 以下是使用channel的語(yǔ)法: /* Creating a channel uses make(), not new - it was also used for map creation */ 舉例來(lái)說(shuō),,如果我們想要進(jìn)行長(zhǎng)時(shí)間運(yùn)行的數(shù)值計(jì)算,,我們可以這樣做: ch := make(chan int) channel的阻塞行為并非永遠(yuǎn)是最佳的。該語(yǔ)言提供了兩種對(duì)其進(jìn)行定制的方式:
/* Create a channel with buffer size 5 */ 包Go提供了一種簡(jiǎn)單的機(jī)制來(lái)組織代碼:包。每個(gè)文件開(kāi)頭都會(huì)聲明它屬于哪一個(gè)包,,每個(gè)文件也可以引入它所用到的包,。任何首字母大寫(xiě)的名字是由包導(dǎo)出 的,并可以被其它的包所使用,。 以下是一個(gè)完整的源文件: package geometry 缺失Go語(yǔ)言的作者試圖將代碼的清晰明確作為設(shè)計(jì)該語(yǔ)言作出所有決定的指導(dǎo)思想,。第二個(gè)目標(biāo)是生產(chǎn)一個(gè)編譯速度很快的語(yǔ)言。有了這兩個(gè)標(biāo)準(zhǔn)作為方向,,來(lái) 自其它語(yǔ)言的許多特性就不那么適合了,。許多程序員會(huì)發(fā)現(xiàn)他們最?lèi)?ài)的語(yǔ)言特性在Go當(dāng)中不存在,確實(shí),,有很多人也許會(huì)覺(jué)得Go語(yǔ)言由于缺乏其它語(yǔ)言所共有的 一些特性,,還不太可用。 這當(dāng)中兩個(gè)缺失的特性就是異常和泛型,,兩者在其它語(yǔ)言當(dāng)中都是非常有用的,。而它們目前都不是Go的一分子。但因?yàn)樵?語(yǔ)言仍處于試驗(yàn)階段,,它們有可能最終會(huì)加入到語(yǔ)言里,。然而,如果將Go與其它語(yǔ)言作比較的話(huà),,我們應(yīng)當(dāng)記住Go是打算在系統(tǒng)編程層面作為C語(yǔ)言的替代,。明 白這一點(diǎn)的話(huà),那么缺失的這許多特性倒也不是很大的問(wèn)題了,。 最后,,因?yàn)檫@一語(yǔ)言才剛剛發(fā)布,因此它沒(méi)有什么類(lèi)庫(kù)或工具可以用,,也沒(méi)有Go語(yǔ) 言的集成編程環(huán)境。Go語(yǔ)言標(biāo)準(zhǔn)庫(kù)有些有用的代碼,,但這與更為成熟的語(yǔ)言比 起來(lái)仍還是很少的,。 |
|
來(lái)自: openlog > 《技術(shù)探討》