示例
package main
import "fmt"
func main() {
fmt.Println("hello, world")
}
包的概念、導入與可見性
包是結構化代碼的一種方式:每個程序都由包(通常簡稱為 pkg)的概念組成瞬内,可以使用自身的包或者從其它包中導入內容秽澳。
如同其它一些編程語言中的類庫或命名空間的概念诈乒,每個 Go 文件都屬于且僅屬于一個包围肥。一個包可以由許多以 .go 為擴展名的源文件組成剿干,因此文件名和包名一般來說都是不相同的。
你必須在源文件中非注釋的第一行指明這個文件屬于哪個包穆刻,如:package main置尔。package main表示一個可獨立執(zhí)行的程序,每個 Go 應用程序都包含一個名為 main 的包氢伟。
一個應用程序可以包含不同的包榜轿,而且即使你只使用 main 包也不必把所有的代碼都寫在一個巨大的文件里:你可以用一些較小的文件,并且在每個文件非注釋的第一行都使用 package main 來指明這些文件都屬于 main 包腐芍。如果你打算編譯包名不是為 main 的源文件差导,如 pack1,編譯后產生的對象文件將會是 pack1.a 而不是可執(zhí)行程序猪勇。另外要注意的是设褐,所有的包名都應該使用小寫字母。
標準庫
在 Go 的安裝文件里包含了一些可以直接使用的包泣刹,即標準庫助析。在 Windows 下,標準庫的位置在 Go 根目錄下的子目錄 pkg\windows_386 中椅您;在 Linux 下外冀,標準庫在 Go 根目錄下的子目錄 pkg\linux_amd64 中(如果是安裝的是 32 位宣赔,則在 linux_386 目錄中)细办。一般情況下,標準包會存放在 $GOROOT/pkg/$GOOS_$GOARCH/ 目錄下悬蔽。
Go 的標準庫包含了大量的包(如:fmt 和 os)员舵,但是你也可以創(chuàng)建自己的包脑沿。
如果想要構建一個程序,則包和包內的文件都必須以正確的順序進行編譯马僻。包的依賴關系決定了其構建順序庄拇。
屬于同一個包的源文件必須全部被一起編譯,一個包即是編譯時的一個單元韭邓,因此根據慣例措近,每個目錄都只包含一個包。
如果對一個包進行更改或重新編譯女淑,所有引用了這個包的客戶端程序都必須全部重新編譯瞭郑。
Go 中的包模型采用了顯式依賴關系的機制來達到快速編譯的目的,編譯器會從后綴名為 .o 的對象文件(需要且只需要這個文件)中提取傳遞依賴類型的信息鸭你。
如果 A.go 依賴 B.go凰浮,而 B.go 又依賴 C.go:
編譯 C.go, B.go, 然后是 A.go.
為了編譯 A.go, 編譯器讀取的是 B.o 而不是 C.o.
這種機制對于編譯大型的項目時可以顯著地提升編譯速度我抠。
每一段代碼只會被編譯一次
一個 Go 程序是通過 import 關鍵字將一組包鏈接在一起。
import "fmt" 告訴 Go 編譯器這個程序需要使用 fmt 包(的函數袜茧,或其他元素)菜拓,fmt 包實現了格式化 IO(輸入/輸出)的函數。包名被封閉在半角雙引號 "" 中笛厦。如果你打算從已編譯的包中導入并加載公開聲明的方法纳鼎,不需要插入已編譯包的源代碼。
如果需要多個包裳凸,它們可以被分別導入:
import "fmt"
import "os"
或:
import "fmt"; import "os"
但是還有更短且更優(yōu)雅的方法(被稱為因式分解關鍵字贱鄙,該方法同樣適用于 const、var 和 type 的聲明或定義):
import (
"fmt"
"os"
)
它甚至還可以更短的形式姨谷,但使用 gofmt 后將會被強制換行:
import ("fmt"; "os")
當你導入多個包時逗宁,最好按照字母順序排列包名,這樣做更加清晰易讀梦湘。
如果包名不是以 . 或 / 開頭瞎颗,如 "fmt" 或者 "container/list",則 Go 會在全局文件進行查找捌议;如果包名以 ./ 開頭哼拔,則 Go 會在相對目錄中查找;如果包名以 / 開頭(在 Windows 下也可以這樣使用)瓣颅,則會在系統(tǒng)的絕對路徑中查找倦逐。
導入包即等同于包含了這個包的所有的代碼對象。
除了符號 _宫补,包中所有代碼對象的標識符必須是唯一的檬姥,以避免名稱沖突。但是相同的標識符可以在不同的包中使用粉怕,因為可以使用包名來區(qū)分它們健民。
包通過下面這個被編譯器強制執(zhí)行的規(guī)則來決定是否將自身的代碼對象暴露給外部文件:
可見性規(guī)則
當標識符(包括常量、變量斋荞、類型荞雏、函數名虐秦、結構字段等等)以一個大寫字母開頭平酿,如:Group1,那么使用這種形式的標識符的對象就可以被外部包的代碼所使用(客戶端程序需要先導入這個包)悦陋,這被稱為導出(像面向對象語言中的 public)蜈彼;標識符如果以小寫字母開頭,則對包外是不可見的俺驶,但是他們在整個包的內部是可見并且可用的(像面向對象語言中的 private )幸逆。
(大寫字母可以使用任何 Unicode 編碼的字符棍辕,比如希臘文,不僅僅是 ASCII 碼中的大寫字母)还绘。
因此楚昭,在導入一個外部包后,能夠且只能夠訪問該包中導出的對象拍顷。
假設在包 pack1 中我們有一個變量或函數叫做 Thing(以 T 開頭抚太,所以它能夠被導出),那么在當前包中導入 pack1 包昔案,Thing 就可以像面向對象語言那樣使用點標記來調用:pack1.Thing(pack1 在這里是不可以省略的)尿贫。
因此包也可以作為命名空間使用,幫助避免命名沖突(名稱沖突):兩個包中的同名變量的區(qū)別在于他們的包名踏揣,例如 pack1.Thing 和 pack2.Thing庆亡。
你可以通過使用包的別名來解決包名之間的名稱沖突,或者說根據你的個人喜好對包名進行重新設置捞稿,如:import fm "fmt"又谋。下面的代碼展示了如何使用包的別名:
alias.go
package main
import fm "fmt" // alias3
func main() {
fm.Println("hello, world")
}
注意事項
如果你導入了一個包卻沒有使用它,則會在構建程序時引發(fā)錯誤括享,如 imported and not used: os搂根,這正是遵循了 Go 的格言:“沒有不必要的代碼!“铃辖。
包的分級聲明和初始化
你可以在使用 import 導入包之后定義或聲明 0 個或多個常量(const)剩愧、變量(var)和類型(type),這些對象的作用域都是全局的(在本包范圍內)娇斩,所以可以被本包中所有的函數調用(如 gotemplate.go 源文件中的 c 和 v)仁卷,然后聲明一個或多個函數(func)。
函數
這是定義一個函數最簡單的格式:
func functionName()
你可以在括號 () 中寫入 0 個或多個函數的參數(使用逗號 , 分隔)犬第,每個參數的名稱后面必須緊跟著該參數的類型锦积。
main 函數是每一個可執(zhí)行程序所必須包含的,一般來說都是在啟動后第一個執(zhí)行的函數(如果有 init() 函數則會先執(zhí)行該函數)歉嗓。如果你的 main 包的源代碼沒有包含 main 函數丰介,則會引發(fā)構建錯誤 undefined: main.main。main 函數既沒有參數鉴分,也沒有返回類型(與 C 家族中的其它語言恰好相反)哮幢。如果你不小心為 main 函數添加了參數或者返回類型,將會引發(fā)構建錯誤:
func main must have no arguments and no return values results.
在程序開始執(zhí)行并完成初始化后志珍,第一個調用(程序的入口點)的函數是 main.main()(如:C 語言)橙垢,該函數一旦返回就表示程序已成功執(zhí)行并立即退出。
函數里的代碼(函數體)使用大括號 {} 括起來伦糯。
左大括號 { 必須與方法的聲明放在同一行柜某,這是編譯器的強制規(guī)定嗽元,否則你在使用 gofmt 時就會出現錯誤提示:
build-error: syntax error: unexpected semicolon or newline before {
(這是因為編譯器會產生 func main() ; 這樣的結果,很明顯這錯誤的)
Go 語言雖然看起來不使用分號作為語句的結束喂击,但實際上這一過程是由編譯器自動完成剂癌,因此才會引發(fā)像上面這樣的錯誤
右大括號 } 需要被放在緊接著函數體的下一行。如果你的函數非常簡短翰绊,你也可以將它們放在同一行:
func Sum(a, b int) int { return a + b }
對于大括號 {} 的使用規(guī)則在任何時候都是相同的(如:if 語句等)珍手。
因此符合規(guī)范的函數一般寫成如下的形式:
func functionName(parameter_list) (return_value_list) {
…
}
其中:
parameter_list
的形式為 (param1 type1, param2 type2, …)
return_value_list 的形式為 (ret1 type1, ret2 type2, …)
只有當某個函數需要被外部包調用的時候才使用大寫字母開頭,并遵循 Pascal 命名法辞做;否則就遵循駱駝命名法琳要,即第一個單詞的首字母小寫,其余單詞的首字母大寫秤茅。
下面這一行調用了 fmt 包中的 Println 函數稚补,可以將字符串輸出到控制臺,并在最后自動增加換行字符 \n:
fmt.Println("hello, world")
使用 fmt.Print("hello, world\n")
可以得到相同的結果框喳。
Print 和 Println 這兩個函數也支持使用變量课幕,如:fmt.Println(arr)。如果沒有特別指定五垮,它們會以默認的打印格式將變量 arr 輸出到控制臺乍惊。
單純地打印一個字符串或變量甚至可以使用預定義的方法來實現,如:print放仗、println:print("ABC")润绎、println("ABC")、println(i)(帶一個變量 i)诞挨。
這些函數只可以用于調試階段莉撇,在部署程序的時候務必將它們替換成 fmt 中的相關函數。
當被調用函數的代碼執(zhí)行到結束符 } 或返回語句時就會返回惶傻,然后程序繼續(xù)執(zhí)行調用該函數之后的代碼棍郎。
程序正常退出的代碼為 0 即 Program exited with code 0;如果程序因為異常而被終止银室,則會返回非零值涂佃,如:1。這個數值可以用來測試是否成功執(zhí)行一個程序蜈敢。
注釋
hello_world2.go
package main
import "fmt" // Package implementing formatted I/O.
func main() {
fmt.Printf("Καλημ?ρα κ?σμε; or こんにちは 世界\n")
}
上面這個例子通過打印Καλημ?ρα κ?σμε; or こんにちは 世界
展示了如何在 Go 中使用國際化字符辜荠,以及如何使用注釋。
注釋不會被編譯扶认,但可以通過 godoc 來使用侨拦。
單行注釋是最常見的注釋形式殊橙,你可以在任何地方使用以 // 開頭的單行注釋辐宾。多行注釋也叫塊注釋狱从,均已以 /* 開頭,并以 */ 結尾叠纹,且不可以嵌套使用季研,多行注釋一般用于包的文檔描述或注釋成塊的代碼片段。
每一個包應該有相關注釋誉察,在 package 語句之前的塊注釋將被默認認為是這個包的文檔說明与涡,其中應該提供一些相關信息并對整體功能做簡要的介紹。一個包可以分散在多個文件中持偏,但是只需要在其中一個進行注釋說明即可驼卖。當開發(fā)人員需要了解包的一些情況時,自然會用 godoc 來顯示包的文檔說明鸿秆,在首行的簡要注釋之后可以用成段的注釋來進行更詳細的說明酌畜,而不必擁擠在一起。另外卿叽,在多段注釋之間應以空行分隔加以區(qū)分桥胞。
示例:
// Package superman implements methods for saving the world.
//
// Experience has shown that a small number of procedures can prove
// helpful when attempting to save the world.
package superman
幾乎所有全局作用域的類型、常量考婴、變量贩虾、函數和被導出的對象都應該有一個合理的注釋。如果這種注釋(稱為文檔注釋)出現在函數前面沥阱,例如函數 Abcd缎罢,則要以 "Abcd..." 作為開頭。
示例:
// enterOrbit causes Superman to fly into low Earth orbit, a position
// that presents several possibilities for planet salvation.
func enterOrbit() error {
...
}
godoc 工具會收集這些注釋并產生一個技術文檔考杉。
類型
可以包含數據的變量(或常量)屁使,可以使用不同的數據類型或類型來保存數據。使用 var 聲明的變量的值會自動初始化為該類型的零值奔则。類型定義了某個變量的值的集合與可對其進行操作的集合蛮寂。
類型可以是基本類型,如:int易茬、float酬蹋、bool、string抽莱;結構化的(復合的)范抓,如:struct、array食铐、slice匕垫、map、channel虐呻;只描述類型的行為的象泵,如:interface寞秃。
結構化的類型沒有真正的值,它使用 nil 作為默認值(在 Objective-C 中是 nil偶惠,在 Java 中是 null春寿,在 C 和 C++ 中是NULL或 0)。值得注意的是忽孽,Go 語言中不存在類型繼承绑改。
函數也可以是一個確定的類型,就是以函數作為返回類型兄一。這種類型的聲明要寫在函數名和可選的參數列表之后厘线,例如:
func FunctionName (a typea, b typeb) typeFunc
你可以在函數體中的某處返回使用類型為 typeFunc 的變量 var:
return var
一個函數可以擁有多返回值,返回類型之間需要使用逗號分割出革,并使用小括號 () 將它們括起來皆的,如:
func FunctionName (a typea, b typeb) (t1 type1, t2 type2)
示例: 函數 Atoi:
func Atoi(s string) (i int, err error)
返回的形式:
return var1, var2
這種多返回值一般用于判斷某個函數是否執(zhí)行成功(true/false)或與其它返回值一同返回錯誤消息(詳見之后的并行賦值)。
使用 type 關鍵字可以定義你自己的類型蹋盆,你可能想要定義一個結構體费薄,但是也可以定義一個已經存在的類型的別名,如:
type IZ int
這里并不是真正意義上的別名栖雾,因為使用這種方法定義之后的類型可以擁有更多的特性楞抡,且在類型轉換時必須顯式轉換。
然后我們可以使用下面的方式聲明變量:
var a IZ = 5
這里我們可以看到 int 是變量 a 的底層類型析藕,這也使得它們之間存在相互轉換的可能召廷。
如果你有多個類型需要定義,可以使用因式分解關鍵字的方式账胧,例如:
type (
IZ int
FZ float64
STR string
)
每個值都必須在經過編譯后屬于某個類型(編譯器必須能夠推斷出所有值的類型)竞慢,因為 Go 語言是一種靜態(tài)類型語言。
Go 程序的一般結構
下面的程序可以被順利編譯但什么都做不了治泥,不過這很好地展示了一個 Go 程序的首選結構筹煮。這種結構并沒有被強制要求,編譯器也不關心 main 函數在前還是變量的聲明在前居夹,但使用統(tǒng)一的結構能夠在從上至下閱讀 Go 代碼時有更好的體驗败潦。
所有的結構將在這一章或接下來的章節(jié)中進一步地解釋說明,但總體思路如下:
- 在完成包的 import 之后准脂,開始對常量劫扒、變量和類型的定義或聲明狸膏。
- 如果存在 init 函數的話沟饥,則對該函數進行定義(這是一個特殊的函數,每個含有該函數的包都會首先執(zhí)行這個函數)。
- 如果當前包是 main 包贤旷,則定義 main 函數广料。
- 然后定義其余的函數,首先是類型的方法遮晚,接著是按照 main 函數中先后調用的順序來定義相關函數,如果有很多函數拦止,則可以按照字母順序來進行排序县遣。
示例 gotemplate.go
package main
import (
"fmt"
)
const c = "C"
var v int = 5
type T struct{}
func init() { // initialization of package
}
func main() {
var a int
Func1()
// ...
fmt.Println(a)
}
func (t T) Method1() {
//...
}
func Func1() { // exported function Func1
//...
}
Go 程序的執(zhí)行(程序啟動)順序如下:
- 按順序導入所有被 main 包引用的其它包,然后在每個包中執(zhí)行如下流程:
- 如果該包又導入了其它的包汹族,則從第一步開始遞歸執(zhí)行萧求,但是每個包只會被導入一次。
- 然后以相反的順序在每個包中初始化常量和變量顶瞒,如果該包含有 init 函數的話夸政,則調用該函數。
- 在完成這一切之后榴徐,main 也執(zhí)行同樣的過程守问,最后調用 main 函數開始執(zhí)行程序。
類型轉換
在必要以及可行的情況下坑资,一個類型的值可以被轉換成另一種類型的值耗帕。由于 Go 語言不存在隱式類型轉換,因此所有的轉換都必須顯式說明袱贮,就像調用一個函數一樣(類型在這里的作用可以看作是一種函數):
valueOfTypeB = typeB(valueOfTypeA)
類型 B 的值 = 類型 B(類型 A 的值)
示例:
a := 5.0
b := int(a)
但這只能在定義正確的情況下轉換成功仿便,例如從一個取值范圍較小的類型轉換到一個取值范圍較大的類型(例如將 int16 轉換為 int32)。當從一個取值范圍較大的轉換到取值范圍較小的類型時(例如將 int32 轉換為 int16 或將 float32 轉換為 int)攒巍,會發(fā)生精度丟失(截斷)的情況嗽仪。當編譯器捕捉到非法的類型轉換時會引發(fā)編譯時錯誤,否則將引發(fā)運行時錯誤柒莉。
具有相同底層類型的變量之間可以相互轉換:
var a IZ = 5
c := int(a)
d := IZ(c)
Go 命名規(guī)范
干凈闻坚、可讀的代碼和簡潔性是 Go 追求的主要目標。通過 gofmt 來強制實現統(tǒng)一的代碼風格兢孝。Go 語言中對象的命名也應該是簡潔且有意義的鲤氢。像 Java 和 Python 中那樣使用混合著大小寫和下劃線的冗長的名稱會嚴重降低代碼的可讀性。名稱不需要指出自己所屬的包西潘,因為在調用的時候會使用包名作為限定符卷玉。返回某個對象的函數或方法的名稱一般都是使用名詞,沒有 Get... 之類的字符喷市,如果是用于修改某個對象相种,則使用 SetName。有必須要的話可以使用大小寫混合的方式,如 MixedCaps 或 mixedCaps寝并,而不是使用下劃線來分割多個名稱箫措。