也談Go語言編程 – Hello,Go!

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go is expressive, concise, clean, and efficient. Its concurrency mechanisms make it easy to write programs that get the most out of multicore and networked machines, while its novel type system enables flexible and modular program construction. Go compiles quickly to machine code yet has the convenience of garbage collection and the power of run-time reflection. It's a fast, statically typed, compiled language that feels like a dynamically typed, interpreted language.
                                                                                          – 摘自Go語言官方網站

對於一門程式設計語言最深刻的喜歡莫過於對這門程式設計語言的設計理念的認同了,Go語言是繼C語言之後又一門讓我有如此感覺的程式設計語言。

初聽到這門語言的存在時,我皺了皺眉:怎麼起了這麼一個名字!絕大多數程式設計語言都以名詞或人名命名(如C、Java、Python、Ruby、 Haskell、Ada等),而這門語言卻用了一個日常生活中使用極為頻繁的動詞Go作為名字,這似乎有些太福士化了。不知為何,這個名字總是讓 我聯想到以前中國農村給小孩子常起的幾個名字:二狗、牛娃等^_^。況且之前已經有很多IT產品也以Go作為名字(例 如,Thoughtworks公司出品的敏捷管理工具也叫Go)。

不過隨著對這門語言的瞭解的深入,名字已不再是問題了。Go語言對我這個C程式員產生了強大的吸引力,原因如下:

* Go保持了與C語言一脈相承的理念:短小精悍、致力於成為系統程式設計語言、簡潔而熟悉的C語言家族文法、靜態編譯型語言、保留了指標、運行高效;
* Go填平了C語言與生俱來的為數不少的"坑";
* Go提升了編譯速度,統一了源碼組織、構建規範以及編碼規範,讓程式員更集中精力於問題域;
* Go改進了並行存取模型,在語言層級原生支援多核平台;
* Go語言起點高,以創新的設計以及甚小的代價相容了現有主流編程範型(例如OO等)。

因此有人稱Go為21世紀的C語言,我覺得不為過。從這篇文章開始,我將和大家一起走入Go語言的世界。

一、安裝Go

Go語言官方站(從國內訪問十分不穩定,時能時不能,原因你懂的)對Go安裝有著較為詳盡的說明。如果你使用的是Linux、Mac OS或Windows,那你應該可以很順利地完成Go的安裝。Go於今年上旬發布了第一個穩定版本Go 1,目前最新版本是1.0.2,可以從Google Code上的Go項目中下載。我的環境為Ubuntu 10.04 32-bit,下載go1.0.2.linux-386.tar.gz後,解壓到/usr/local/go下面:

$ ls /usr/local/go
api/     bin/           doc/        include/  LICENSE  PATENTS    README        src/   VERSION
AUTHORS  CONTRIBUTORS  favicon.ico  lib/      misc/    pkg/    robots.txt  test/

然後將/usr/local/go/bin添加到你的PATH環境變數中,你就可以在任意目錄下執行go程式了:

$ go version
go version go1.0.2

如果你得到上面的輸出結果,可以斷定你的Go安裝成功了!

二、第一個Go程式 – Hello, Go!

我們建立一個用於編寫Go程式的工作目錄go-examples,其絕對路徑為/home/tonybai/go-examples。好了,開始 編寫我們的第一個Go程式。

我們在go-examples下建立一個檔案hellogo.go,其內容如下:

package main

import (
    "fmt"
)

func main() {
    fmt.Printf("Hello, Go!\n")
}

下面我們來編譯該源檔案並執行產生的可執行檔:

$ go build hellogo.go
$ ls
hellogo*  hellogo.go
$ hellogo
Hello, Go!

通過go build加上要編譯的Go源檔案名稱,我們即可得到一個可執行檔,預設情況下這個檔案的名字為源檔案名稱字去掉.go尾碼。當然我們也 可以通過-o選項來指定其他名字:

$ go build -o myfirstgo hellogo.go
$ ls
myfirstgo*  hellogo.go

如果我們在go-examples目錄下直接執行go build命令,後面不帶檔案名稱,我們將得到一個與目錄名同名的可執行檔:

$ go build
$ ls
go-examples*  hellogo.go

三、程式進入點(entry point)和包(package)

Go保持了與C家族語言一致的風格:即目標為可執行程式的Go源碼中務必要有一個名為main的函數,該函數即為可執行程式的進入點。除此之外 Go還增加了一個約束:作為進入點的main函數必須在名為main的package中。正如上面hellogo.go源檔案中的那樣,在源碼第 一行就聲明了該檔案所歸屬的package為main。

Go去除了標頭檔的概念,而借鑒了很多主流語言都採用的package的源碼組織方式。package是個邏輯概念,與檔案沒有一一對應的關係。 如果多個源檔案都在開頭聲明自己屬於某個名為foo的包,那這些源檔案中的代碼在邏輯上都歸屬於包foo(這些檔案最好在同一個目錄下,至少目前 的Go版本還無法支援不同目錄下的源檔案歸屬於同一個包)。

我們看到hellogo.go中import一個名為fmt的包,並利用該包內的Printf函數輸出"Hello, Go!"。直覺告訴我們fmt包似乎是一個標準庫中的包。沒錯,fmt包提供了格式化文本輸出以及讀取格式化輸入的相關函數,與C中的printf或 scanf等類似。我們通過import語句將fmt包匯入我們的源檔案後就可以使用該fmt包匯出(export)的功能函數了(比如 Printf)。

在C中,我們通過static來標識局部函數還是全域函數。而在Go中,包中的函數是否可以被外部調用,要看該函數名的首母是否為大寫。這是一種 Go語言固化的約定:首母大寫的函數被認為是匯出的函數,可以被包之外的代碼調用;而小寫字母開頭的函數則僅能在包內使用。在例子中你也看到了 fmt包的Printf函數其首母就是大寫的。

四、GOPATH

我們把上面的hellogo.go稍作改造,拆分成兩個檔案:main.go和hello.go。

/* hello.go */
package hello

import "fmt"

func Hello(who string) {
    fmt.Printf("Hello, %s!\n", who)
}

/* main.go */
package main

import (
    "hello"
)

func main() {
    hello.Hello("Go!")
}

用go build編譯main.go,結果如下:

$ go build main.go
main.go:4:2: import "hello": cannot find package

編譯器居然提示無法找到hello這個package,而hello.go中明明定義了package hello了。這是怎麼回事呢?原來go compiler搜尋package的方式與我們常規理解的有不同,Go在這方面也有一套約定,這裡面涉及到一個重要的環境變數:GOPATH。我們可以使用go help gopath來查看一下有關gopath的manual。

Go compiler的package搜尋順序是這樣的,以搜尋hello這個package為例:

* 首先,Go compiler會在GO安裝目錄(GOROOT,這裡是/usr/local/go)下尋找是否有src/pkg/hello相關包源碼;如果沒有則繼續;
* 如果export GOPATH=PATH1:PAHT2,則Go compiler會依次尋找是否存在PATH1/src/hello、PATH2/src/hello;配置在GOPATH中的PATH1和PATH2被稱作workplace;
* 如果在上述幾個位置均無法找到hello這個package,則提示出錯。

在本例子中,我們尚未設定過GOPATH環境變數,也沒有建立類似PATH1/src/hello這樣的路徑,因此Go compiler顯然無法找到hello這個package了。我們來設定一下GOPATH變數並建立相關目錄:

$ export GOPATH=/home/tonybai/go-examples
$ mkdir src/hello
$ mv hello.go src/hello
$ go build main.go
$ ls
main*  main.go    src/
$ main
Hello, Go!

五、Go install

我們將main.go移到src/main中,這樣這個demo project顯得更加合理,所有源碼均在src下:

$cd src
$ ls
hello/    main/

Go提供了install命令,與build命令相比,install命令在編譯源碼後還會將可執行檔或庫檔案安裝到約定的目錄下。我們以main目錄為例:

$ cd main
$ go install

install命令執行後,我們發現main目錄下沒有任何變化,原先build時產生的main可執行檔也不見了蹤影。別急,前面說過Go install也有一套自己的約定:
* go install(在src/DIR下)編譯出的可執行檔以其所在目錄名(DIR)命名
* go install將可執行檔安裝到與src同層級的bin目錄下,bin目錄由go install自動建立
* go install將可執行檔依賴的各種package編譯後,放在與src同層級的pkg目錄下

現在我們來看看bin目錄:
$ ls /home/tonybai/go-examples
bin/  src/ pkg/
$ ls bin
main*

的確出現一個bin目錄,並且剛剛編譯的程式main在bin下面。

hello.go編譯後並非可執行程式,在編譯main的同時,由於main依賴hello package,因此hello也被關聯編譯了。這與單獨在hello目錄下執行install的結果是一樣的,我們試試:

$ cd hello
$ go install
$ ls /home/tonybai/go-examples
bin/  pkg/  src/

在我們的workspace(go-examples目錄)下出現了一個pkg目錄,pkg目錄下是一個名為linux_386的子目錄,其下面有一個文 件:hello.a。這就是我們install的結果。hello.go被編譯為hello.a並安裝到pkg/linux_386目錄下了。

.a這個尾碼名讓我們想起了靜態共用庫,但這裡的.a卻是Go專屬的檔案格式,與傳統的靜態共用庫並不相容。但Go語言的設計者使用這個尾碼名似乎是希望 這個.a檔案也承擔起Go語言中"靜態共用庫"的角色。我們不妨來試試,看看這個hello.a是否可以被Go compiler當作"靜態共用庫"來對待。我們移除src中的hello目錄,然後在main目錄下執行go build:

$ go build
main.go:4:2: import "hello": cannot find package

Go編譯器提示無法找到hello這個包,可見目前版本的Go編譯器似乎不理pkg下的.a檔案。http://code.google.com/p/go/issues/detail?id=2775 這個issue也印證了這一點,不過後續Go版本很可能會支援連結.a檔案。畢竟我們在使用第三方package的時候,很可能無法得到其源碼,並且在每個項目中都儲存一份第三方包的源碼也十分不利於項目源碼的後期維護。

六、像指令碼一樣運行Go源碼

Go具有很高的編譯效率,這得益於其設計者對該目標的重視以及設計過程中細節方面的把控,當然這不是本文要關注的話題。正是由於go具有極速的編譯,我們才可以像使用運行指令碼語言那樣使用它。

目前Go提供了run命令來直接運行源檔案。比如:

$ go run main.go
Hello, Go!

go run實際上是一個將編譯源碼和運行編譯後的二進位程式結合在一起的命令。但目前go源檔案尚不支援作成Shebang Script,因為Go compiler尚不識別#!符號,下面的源碼檔案運行起來會出錯:

#! /usr/local/go/bin/go run

package main

import (
    "hello"
)

func main() {
    hello.Hello("Go!")
}

$ go run main.go
package :
main.go:1:1: illegal character U+0023 '#'

不過我們可以可藉助一些第三方工具來運行Shebang Go scripts,比如gorun。

七、測試Go程式

前面說過Go起點較高,因此其自身就提供了一個輕量級單元測試架構套件以及運行測試集的命令。

我們用一個例子來說明如何編寫包的測試代碼以及如何運行這個測試。我們在go-examples/src下建立另外一個目錄mymath,mymath目錄下mymath包的代碼如下:

/* mymath.go */
package mymath

func MyAdd(i int, j int) int {
    return i + j
}

要對mymath包進行測試,我們需在同一目錄下建立mymath_test.go檔案,其中對MyAdd函數的測試代碼如下:

/* mymath_test.go */
package mymath

import "testing"

func TestMyAdd(t *testing.T) {
    a, b := 4, 2
    if x := MyAdd(a, b); x != 6 {
        t.Errorf("MyAdd(%d, %d) = %d, want %d", a, b, x, 6)
    }
}

在這個檔案中我們import了Go提供的標準單元測試包-testing,並且每個測試方法都已Test作為首碼開頭。現在我們來運行一下這個測試,在mymath目錄下運行go test命令:

$ go test
PASS
ok      mymath    0.007s

如果用例出錯,我們就可看到下面提示:

$go test
— FAIL: TestMyAdd (0.00 seconds)
    mymath_test.go:8: MyAdd(4, 2) = 6, want 6
FAIL
exit status 1
FAIL    mymath    0.007s

由上可以看出,Go test也有自己的一些約定:測試源檔案的名字必須以_test.go作為結尾;測試代碼與被測代碼在同一個包中;測試代碼要匯入testing包;測試 函數要以Test作為首碼,並且測試函數的函數簽名必須是這樣的:func TestXXX(t *testing.T)。

語言內建對測試的支援的好處是一致性,避免了大家使用不同的測試架構而給閱讀、交流和維護帶來的不便。

八、項目源碼組織

有了源碼、有了對編譯原理的理解、有了測試架構的支援,我們就可以策劃項目源碼組織形式了。不過Go的諸多約定基本上已經將我們限制在如下結構上:

proj1/
    bin/
        myapp1*
    pkg/
        linux_386/
            lib1.a
            lib2.a
    src/
        lib1/
            lib1.go     
            lib1_test.go
        lib2/
            lib2.go     
            lib2_test.go
        … …
        myapp1/
            main.go       # main package source
            main_test.go  # test source

proj2/
    bin/
        myapp2*
    pkg/
        linux_386/
            lib3.a
            lib4.a
    src/
        lib3/
            lib3.go     
            lib3_test.go
        lib4/
            lib4.go     
            lib4_test.go
        … …
        myapp2/
            main.go       # main package source
            main_test.go  # test source

基於上述結構,我們可將GOPATH設定為proj1_path:proj2_path。

九、代碼風格(coding style)

Go程式員可以不再糾結於到底使用哪種代碼風格,因為Go已經將代碼風格做了嚴格的約定,一旦違反,Compiler直接給出Error。go還提供了fmt命令來協助Go程式員按標準格式化源檔案。

從上面例子中我們可以看到Go的幾大風格特點是:

* 左大括弧'{'一定在函數名或if等語句在同一行
   func foo {

   }

* 無需顯式用分號;將語句分隔(除非是在一行寫上多條語句),因為compiler會替大家在適當位置加入分號的。
   i, j := 2, 3
   MyAdd(i, j)

   if x := MyAdd(a, b); x != 6 {
            … …
   }

* if、for等後面的運算式無需用小括弧括上
  
   if x != 5 {
            … …
   }

十、查看文檔

Go的全量文檔幾乎與Go安裝包一起發布。安裝Go後,執行godoc –http=:連接埠號碼即可啟動doc server。開啟瀏覽器,輸入http://localhost:連接埠號碼即可以看到幾乎與Go官方站完全相同的文檔頁面。

十一、參考書籍

Go畢竟是新生代語言,其自身尚不成熟和完善,資料也較少。這裡推薦兩本市面上比較好的且內容已更新到Go 1的書籍:

* Mark Summerfield的《Programming in Go: creating applications for the 21st century》
* Ivo Balbaert的《The Way to Go – A Thorough Introduction to the Go Programming Language》

2012, bigwhite. 著作權.

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.