go基礎系列:簡介

1.Go簡介

Go語言是編譯型、靜態類型的類C的語言,並帶有GC(垃圾收集器,garbage collection)。這意味着什麼?程序員

另外,Go是一種很是嚴格的語言,它幾乎老是要求咱們"以標準答案去答題",在其它語言能夠容忍的不規範編碼方式在Go語言中幾乎都會拋異常。例如導入了包卻沒有使用這個包,Go不會去編譯它並報錯。再例如,定義了一個變量但歷來沒用過,也會報錯。golang

初學Go的時候,這多是件無比的苦惱事情,但習慣了以後,編寫出來的程序天然是無比規範的。這也正是Go和很多語言的區別:其它語言編碼、調試階段可能很快,但維護和優化階段可能會很是長;而Go的編碼週期可能稍長,但編碼完成後幾乎都是足夠優化的,維護和優化週期足夠短。編程

編譯型

編譯表示的是將你所寫的源代碼轉換爲低層次的語言,例如彙編語言(go採用此底層語言),或者其它中間的語言(如Java、C#編譯成字節碼)。數組

編譯型語言可能不太友好,由於編譯的過程速度很慢。若是一個程序的編譯過程就須要花幾分鐘甚至幾小時,那麼程序的版本迭代可能會很難進行下去。編譯速度是Go語言的一個主要設計目標,值得慶幸的是,Go的編譯速度很快,即使對於習慣於使用解釋型語言的人來講,它也仍是快。瀏覽器

編譯型語言雖然編譯過程慢,但這類語言在運行階段可能會更快,並且運行時再也不須要加載額外的依賴。ide

靜態類型

靜態語言意味着變量必需要指定數據類型(int,string,bool,[]byte等)。雖然必須指定數據類型,但除了在聲明變量的時候顯式指定數據類型,也可讓Go本身去推斷數據類型(稍後有示例)。函數

對於習慣於使用動態型語言的人來講,可能會感受靜態型語言很笨重,事實確實如此。但靜態有靜態的好處,特別是配合編譯操做的時候。工具

關於靜態和動態數據類型,要說的內容其實不少不少,畢竟對於一門語言來講,數據類型牽一髮而動全身,不管是靜態、仍是動態型語言,都所以而衍生出無數的優、缺點。性能

類C型的語言

當咱們說一門語言是類C型(C-like)的語言時,意味着這門語言裏有一些語法和特性和C語言是相似的。例如,&&表示布爾的AND,==表示等值比較,數組索引從0開始計算,{...}表示一段代碼塊,也表示它屬於一個做用域範圍,等等。優化

類C型語言也意味着每行的語句要使用分號";"結束,條件表達式要使用括號包圍。但Go語言不採用這兩種方式,儘管仍是可使用括號包圍條件表達式以改變優先級。例如:

if name == "malongshuai" {
    print("name rigth!")
}

一個更復雜一點的條件表達式,使用括號改變優先級:

if (name == "longshuai" && age > 23) || (name == "xiaofang" && age < 22) {
    print("yeyeye!!!")
}

GC

每當建立一個變量後,這個變量都會有其生命週期。例如,函數內部的本地變量將在函數退出的時候消逝。對於非函數內部的變量生命週期,不管是對程序員仍是對編譯器來講,變量的生命週期都沒有那麼顯而易見。

沒有garbage collection,意味着要讓程序員本身來決定變量所佔用內存的釋放,這是很艱鉅的任務,並且很容易出錯致使程序崩潰。

帶有GC的語言能夠對變量進行跟蹤,而且在它們再也不被須要的時候自動釋放它們。雖然GC帶來了一點點的負載,會影響一點點的性能,但對於如今高性能的計算機來講,這點影響相比它帶來的優勢而言,徹底能夠將其無視。

嘗試寫一個簡單的Go程序

按照國際管理,每一門語言老是以hello world開篇。這裏就算了,由於我有個人慣例。

先安裝Go,so easy...

目前尚未必要涉及Go的工做空間,因此隨意找個地方建立一個test.go文件,內容以下:

package main

func main() {
    println("Let's Go")
}

而後運行:

go run test.go

顯然,它將輸出Let's Go。可是Go的編譯過程呢?go run命令同時進行了編譯和運行兩個過程:它將使用一個臨時目錄保存構建的程序,而後執行它,最後自動清理構建出來的臨時程序。

可使用go run --work查看下具體狀況:

$ go run --work test.go
WORK=/tmp/go-build267589647
Let's Go

構建的臨時目錄位於/tmp/go-buildXXXX中(我這是Linux),在此目錄下會有一個二進制程序(對於Windows則是.exe文件):

$ tree /tmp/go-build267589647/
/tmp/go-build267589647/
├── command-line-arguments
│   └── _obj
│       └── exe
│           └── test    # 這是可執行二進制程序
└── command-line-arguments.a

那個test文件就是編譯後獲得的二進制程序,能夠直接用來執行:

$ /tmp/go-build267589647/command-line-arguments/_obj/exe/test 
Let's Go

若是要顯式編譯,使用go build命令:

go build test.go

它將在當前目錄下生成一個名爲test的二進制文件,能夠直接拿來運行,就像前面/tmp中的同樣。

$ ./tese
Let's Go

在開發階段,用go build仍是用go run,隨意便可。但在部署的時候,通常先go build,再go run。

main包和main函數

在上面的代碼中,聲明瞭這個包的名稱爲main,而後建立一個函數,並在此函數中使用println輸出了一個字符串,可是go run如何知道要去執行什麼?在Go中,程序的入口是main包中的main函數,這兩名稱都是固定的。

對於一個從沒編程過的人,可能不理解程序的入口。它表示程序今後處開始執行,函數main中可能會包含不少其它函數的調用,這些函數可能放在其它文件(包)中。經過一次次、一層層的調用,從而將整個程序的全部代碼、邏輯都鏈接在一塊兒並運行。

若是你願意,能夠試着修改一下package後面的main關鍵字,而後go rungo build都運行一下。再試着修改一下func mainmain關鍵字,go rungo build再運行一下。

關於包的內容,後面再作介紹。目前來講,須要理解的只是些基礎,對於基礎階段來講,咱們將老是在main包中寫代碼。

import

Go有一些內置的函數,例如上面的println,內置函數無需額外的引用就可直接調用。但內置函數畢竟不多,因此得從已經寫好的Go標準庫和其它第三方庫中找出一些工具來使用。

在Go中,import關鍵字用於定義要導入到當前文件的包名,導入某個包後,這個包中的屬性就能在當前文件中去訪問,例如調用屬於這個包的函數。

例如,將前面的代碼改改:

package main

import (
    "fmt"
    "os"
)

func main (){
    if len(os.Args) != 2{
        os.Exit(1)
    }
    fmt.Println("Arg0: ",os.Args[0])
    fmt.Println("Arg1: ",os.Args[1])
}

執行一下:

$ go run test.go
exit status 1

$ go run test.go a b
exit status 1

$ go run test.go a 
Arg0:  /tmp/go-build730099388/command-line-arguments/_obj/exe/test
Arg1:  a

上面的import導入了兩個標準包:fmtos,還使用了另外一個內置函數len()

len()函數返回字符串的長度、字典的元素個數以及數組的元素個數。上面使用len()判斷了該Go程序的參數個數必須爲2,不然就以狀態碼1退出該程序。看上面的運行結果,好像只有給一個參數的時候纔是正確的,這是由於第一個參數(Args[0])表明的老是當前正在運行的程序名稱,正如上面結果所顯示的那樣。

你可能還注意到了fmt.Println,前綴fmt正好是導入的一個包名,這表示使用fmt包中的Println函數。

本文的開頭就說過了,Go是一門很是嚴格的語言,若是這裏導入了fmt包,但卻沒有使用它,它將報錯。

# command-line-arguments
./test.go:4:5: imported and not used: "fmt"

關於Go文檔

關於fmt的Println函數詳細用法,可去參考Go的官方文檔:https://golang.org/pkg/fmt/#Println。固然,現階段去看官方手冊,那是在找死

還可使用go doc命令去查找各幫助文檔。

例如,查看fmt包的幫助文檔:

go doc fmt

查看fmt.Println函數的用法:

go doc fmt.Println

完整用法:

go doc
go doc <pkg>
go doc <sym>[.<method>]
go doc [<pkg>].<sym>[.<method>]
go doc <pkg> <sym>[.<method>]

此外,還能夠構建本地的網頁版官方手冊,在斷網的時候能夠訪問:

godoc -http=:6060

而後就能夠在瀏覽器中經過http://localhost:6060/訪問官方手冊。

變量和變量聲明

不少語言中,要爲變量賦值只需一個語句:

x=10

這個語句中實際上包含了兩個過程:變量的聲明和變量的賦值。聲明通常也被稱爲"定義"

在Go中,必須先聲明變量,再賦值或使用變量。最複雜的聲明+賦值操做爲:

package main

import ( "fmt" )

func main(){
    var x int
    x=10
    fmt.Println("x =",x)
}

此處聲明瞭一個變量x,其數據類型爲int。默認狀況下,Go在變量的聲明期間會爲其作初始化賦值:int類型初始化賦值爲0,booleans初始化賦值爲false,strings初始化賦值爲"",等等。

能夠將聲明和賦值操做合併:

var x int = 10

還有一種更方便的聲明+賦值方式:

x := 10

經過這種變量的定義方式,還能夠將函數執行結果(返回值)賦值給變量。例如:

func main() {
    x := getAdd(10)
}

func getAdd(x int) int {
    return x+1
}

:=在Go中屬於類型推斷操做,它包含了變量聲明和變量賦值兩個過程。

須要注意的是,變量聲明以後不能再次聲明(除非在不一樣的做用域),以後只能使用=進行賦值。例如,執行下面的代碼將報錯:

package main

import ("fmt")

func main(){
    x:=10
    fmt.Println("x =",x)
    x:=11
    fmt.Println("x =",x)
}

錯誤以下:

# command-line-arguments
.\test.go:8:3: no new variables on left side of :=

報錯信息很明顯,:=左邊沒有新變量。

若是仔細看上面的報錯信息,會發現no new variables是一個複數。實際上,Go容許咱們使用:=一次性聲明、賦值多個變量,並且只要左邊有任何一個新變量,語法就是正確的。

func main(){
    name,age := "longshuai",23
    fmt.Println("name:",name,"age:",age)
    
    // name從新賦值,由於有一個新變量weight
    weight,name := 90,"malongshuai"
    fmt.Println("name:",name,"weight:",weight)
}

須要注意,name第二次被:=賦值,Go第一次推斷出該變量的數據類型以後,就不容許:=再改變它的數據類型,由於只有第一次:=對name進行聲明,以後全部的:=對name都只是簡單的賦值操做。

例如,下面將報錯:

weight,name := 90,80

錯誤信息:

.\test.go:11:14: cannot use 80 (type int) as type string in assignment

另外,變量聲明以後必須使用,不然會報錯,由於Go對規範的要求很是嚴格。例如,下面定義了weight但卻沒使用:

weight,name := 90,"malongshuai"
fmt.Println("name:",name)

錯誤信息:

.\test.go:11:2: weight declared and not used

能夠一次性聲明、聲明並賦值多個變量:

var x, y, z int

x, y, z := 10, 20, 30

var (
    x = 10
    y = 20
    z = 30
)

函數定義

Go的函數容許有多個返回值。例如:

// 該函數有一個參數,沒有返回值
func log(message string){
    ...CODE...
}

// 該函數有兩個參數,一個返回值,返回值的類型爲int
func add(a int,b int) int {
    ...CODE...
}

// 該函數一個參數,兩個返回值,分別是int和bool
func power(name string) (int,bool) {
    ...CODE...
}

既然函數能夠有返回值,就能夠將其返回值賦值給變量:

value, exists := power("malongshuai")
if exists == false {
    ...CODE...
}

有些時候,咱們可能並不須要全部的返回值。例如,咱們只想要取得power()的第二個返回值。這時能夠將不想要的返回值丟給特殊符號下劃線_,它表示丟棄這部分結果。

_,exists := power("longshuai")
if exists == false {
    ...CODE...
}

若是函數的參數類型相同,則不一樣的參數能夠共享數據類型。

func (a,b int) int {
    ...CODE...
}
相關文章
相關標籤/搜索