鑑於上篇文章咱們已經講過Go語言環境的安裝,如今咱們已經有了一個能夠運行Go程序的環境,並且,咱們還運行了'Hello World'跑出了咱們的第一個Go程序。
這節咱們就以'Hello World爲例,講解Go的基礎結構,詳細的解釋一下Hello World中的每一行都表明了什麼。mysql
Go語言是一門靜態語言,在編譯前都會對代碼進行嚴格的語法校驗,若是語法錯誤是編譯不過去的,因此基礎結構是很是重要的一個環節。git
<!--more-->github
相似Java中的package、class、interface、函數、常量和變量等,Go也有package、struct、interface、func、常量、變量等內容。golang
struct相似Java中的class,是Go中的基本結構題。
interface也相似Java中的接口,可定義函數以供其餘struct或func實現(可能不太好理解,後面會講)。sql
這裏咱們按照由外而內,從上至下的的順序進行講解。數據庫
上節說到GOPATH指定了存放項目相關的文件路徑,下面包含'bin'、'pkg'、'src'三個目錄。編程
1.src 存放項目源代碼json
2.pkg 存放編譯後生成的文件數組
3.bin 存放編譯後生成的可執行文件安全
目錄結構以下
GOPATH \_ src \_ projectA \_ projectB \_ pkg \_ projectA \_ projectB \_ bin \_ commandA \_ commandB
src目錄是咱們用的最多的,由於咱們全部的項目都會放到這個目錄中。後續項目的引用也是相對於該目錄。
一個Go文件,咱們對其的第一個認識,就是其的後綴,Go文件以'.go'結尾(Windows用戶必定要文件後綴的問題),這個很容易理解。
不能以數字開頭、不能包含運算符、不能使用Go的關鍵字等對比其餘語言也很是容易理解。
有效的標識符必須以字母(可使用任何 UTF-8 編碼的字符或 _)開頭加上任意個數字或字符,如:'hello_world.go'、'router.go'、'base58.go'等。
Go自然支持UTF8
不過在這裏有一些細節須要注意,就是Go文件在命名的時候,跟其餘語言不太同樣。
Go文件都是小寫字母命名(雖然大寫也支持,可是大寫文件名並不規範),若是須要多個單詞進行拼接,那單詞之間以_
下劃線進行鏈接。
特別是編寫測試用例和多平臺支持時對Go文件的命名。
如:'math_test.go'、'cpu_arm.go'、'cpu_x86.go'等。
Go能夠說是很是乾淨整潔的代碼,因此Go的命名很是簡潔並有意義。雖然支持大小寫混寫和帶下劃線的命名方式,可是這樣真的是很是的不規範,Go也不推薦這樣作。Go更喜歡使用駝峯式的命名方式。如'BlockHeight'、'txHash'這樣的定義。另外Go的定義能夠直接經過'package.Xxx'這樣的方式進行使用,因此也不推薦使用GetXXX這樣的定義。
package的存在,是爲了解決文件過多而形成的絮亂等問題,太多的文件都放在項目根目錄下看着老是讓人以爲不舒服,對開發、後期維護等都是問題。
做爲代碼結構化方式之一, 每一個Go程序都有package的概念。
Go語法規定
以下面的代碼指定該文件屬於learning_go這個package。
package learning_go ...
Go對package的定義並不嚴格,在文件夾下的Go文件能夠不使用文件夾名稱做爲package的定義(默認使用),可是同一個文件夾下的全部Go文件必須使用同一個package定義,這個是嚴格要求的。
tips: package的定義均應該採用小寫字母,因此文件夾的定義也應該都是小寫字母。
爲了更好的區分不一樣的項目,Go定義的項目結構中都包含開源站點的信息,如github、gitee等開源站點中的全部開源Go項目均可以直接拿來使用。
Go的背後是全部開源世界
拿在GitHub下面建立的Go項目gosample項目爲例。其項目路徑爲:"github.com/souyunkutech/gosample"。"github.com"表示其開源站點信息。"souyunkutech/gosample"就是項目的名稱。
文件的路徑就對應爲"$GOPATH/src/github.com/souyunkutech/gosample"。
"souyunkutech"表示了gosample這個項目屬於"souyunkutech"這個用戶全部。
在引用某個項目以前,須要先獲取其源碼,將其放置到$GOPATH/src
下,這樣咱們才能對其進行引用從而正確的進行編譯。
Go獲取源碼能夠手動將源碼根據其相對路徑放到正確的路徑下,也能夠經過go get path
的方式自動獲取。
如獲取'gosample'項目,能夠經過git clone
的方式下載到$GOPATH/src/github.com/souyunkutech/
也能夠經過go get github.com/souyunkutech/gosamp
的方式進行獲取,go會本身把項目方到$GOPATH/src/github.com/sirupsen/
目錄下。
如獲取Go語言中一個很是知名的第三方日誌組件logrus,能夠將其經過git clone
的方式下載到$GOPATH/src/github.com/sirupsen/
也能夠經過go get github.com/sirupsen/logrus
的方式進行獲取。
import關鍵字的做用就是package的引用。做爲外部引用的最小單位,Go以package爲基礎,不像Java那樣,以對象爲基礎,供其餘程序進行引用,import引用了某個package那就是引用了這個package的全部(可見的)內容。
語法上須要注意的就是在引用時須要雙引號包裹。沒有使用過的package引用要不刪除,要不定義爲隱式引用。不然的話,在運行或者編譯程序的時候就會報錯:imported and not used:...
如HelloWorld代碼中引用的'fmt'就是Go語言內建的程序package。fmt這個package下包含'doc.go'、'format.go'等(這個能夠經過IDE的方式進行查看)這些Go文件中全部的(可見的)內容均可以使用。
前面說到import的引用默認都是相對於$GOPATH/src
目錄的。因此咱們要想使用某個開源項目,就須要使用其相對於GOPATH/src
的相對路徑進行引用。(Go系統內建的項目除外)
import引用的語法有兩種方式
方式1,默認的引用方式,每一個引用單獨佔一行, 如:
import "fmt"
方式2,經過括號將全部的引用寫在一個import中,每一個引用單獨佔一行。經過這種方式,Go在格式化的時候也會將全部的引用按照字母的順序進行排序,因此看起來特別的清晰。如:
import ( "fmt" "math" )
好比,logrus項目結構是'github.com/sirupsen/logrus',因此在引用這個日誌組件時,就要寫成下面這樣
import "github.com/sirupsen/logrus"
若是隻想使用logrus項目中的某一個package的話,能夠單引用該package,而不用引用logrus全項目。這也是很是的方便。
好比要使用logrus項目中'hook/syslog/syslog.go',就能夠像下面這樣寫import
import "github.com/sirupsen/logrus/hooks/syslog"
Go的引用還支持以文件路徑的方式。如'./utils'引用當前目錄下的util這個package時,就能夠寫成下面這樣,固然按照項目路徑的方式寫纔是最合適最規範的,並不推薦使用文件路徑進行引用。
import "./utils"
Go的引用有一個特別另類的支持,那就是隱式引用,須要在引用前面加上_
下劃線標識。這種類型的引用通常會發生在加載數據庫驅動的時候。如加載MySQL數據庫驅動時。由於這些驅動在項目中並不會直接拿來使用,但不引用又不行。因此被稱爲隱式引用。
import _ "github.com/go-sql-driver/mysql"
package在引用的過程須要注意不能同時引用兩個相同的項目,即無論項目的站點和項目所屬,只要引用的項目package名稱相同,都是不被容許的,在編譯時會提示'XXX redeclared as imported package name'錯誤。但隱式引用除外。
import "encoding/json" import "github.com/gin-gonic/gin/json"//!!! 不容許 !!!
可是能不能使用還要看這個package,就是這個package的可見性。
可見行能夠理解爲Java 中的私有和公有的意思。以首字母大寫的結構體、結構字段、常量、變量、函數都是外部可見的,能夠被外部包進行引用。如"fmt"中的"Println"函數,其首字母大寫,就是能夠被其餘package引用的,"fmt"中還有"free"函數,其首字母小寫,就是不能被其餘package引用。
可是無論對外部package是否可見,同一個package下的全部定義,都是可見並能夠引用、使用的。
在Go語言中,函數的使用是最頻繁的,畢竟要將代碼寫的清晰易懂嘛,並且好像全部的編程語言都有函數的概念(目前沒據說過沒有函數概念的語言)
在Go中,函數的定義支持多個參數,一樣也支持多個返回值(比Java要厲害哦),多個參數和多個返回值之間使用英文逗號進行分隔。
一樣與Java相似,Go的函數體使用'{}'大括號進行包裹,可是,Go的定義更爲嚴格,Go要求左大括號'{'必須與函數定義同行,不然就會報錯:'build-error: syntax error: unexpected semicolon or newline before {'。
func methodName(param1 type, param type2, param type3){ ... } //簡寫方式,能夠將相同類型的參數並列定義 func methodName(param1, param2 type, param3 type2) { ... }
函數返回值的定義能夠只定義返回類型,也能夠直接定義返回的對象。
定義了返回的對象後,就能夠在函數內部直接使用該定義,避免了在函數中屢次定義變量的問題。同時,在返回的時候也能夠單單使用一個'return'關鍵字來代替 'return flag'這樣的返回語句。
須要注意返回值要不都定義返回對象,要不都不定義,Go不容許只定義部分函數返回對象。
//最簡單的函數定義 func methodName(){ ... } //僅定義返回的類型 func methodName() bool{ ... return false } // 定義返回的對象+類型 func methodName() (flag bool){ ... return } //定義多個返回類型 func methodName()(bool, int) { ... return false, 0 } //定義多個返回對象+類型 func methodName()(flag bool, index int) { ... return } // !!! 不容許的定義 !!! func methodName()(bool, index int){ ... return } // !!! 不容許的定義 !!! func methodName() { ... return }
在Go中有兩個特別的函數,'main'函數和'init'函數。
'main'函數是程序的主入口,package main必須包含該函數,不然的話是沒法運行的。在其餘的package中,該函數之能是一個普通的函數。這點要特別注意。
它的定義以下,不帶也不能帶任何的參數和返回值
func main(){ ... }
'init'函數是package的初始化函數,會在執行'main'函數以前執行。
同一個package中能夠包含多個init函數,可是多個init函數的執行順序Go並無給出明確的定義,並且對於後期的維護也不方便,因此一個package中儘量的只定義一個init函數比較合適。
多個package中的init函數,按照被import的導入順序進行執行。先導入的package,其init函數先執行。若是多個package同時引用一個package,那其也只會被導入一次,其init函數也只會執行一次。
它的定義和main函數相同,也是不帶也不能帶任何的參數和返回值
func init(){ ... }
在Go中,有
Go的集合類型並無Java那麼複雜,什麼線程安全的集合、有序的集合都沒有(全都須要本身實現^_^)!
array和slice這兩種集合都相似Java中的數組,他們不管在使用上都是同樣的。以致於會常常忘記他們兩個到底哪裏不同。其實真的是很是的細節,array是有長度的,slice是沒有長度的。他們的區別就是這麼小。
channel是Go中特有的一種集合,是Go實現併發的最核心的內容。與Unix中的管道也是很是的相似。
struct結構體簡單理解就是對象了,能夠本身定義本身須要的對象進行結構化的使用。和Java中的class不一樣,Java中函數是寫在class中的,在Go中,struct的函數是要寫在struct外的。
結構體定義須要使用type關鍵字結合struct來定義。struct前的字段爲新的結構體的名稱。內部字段可使用大寫字母開頭設置成對外部可見的,也可使用小寫字母開頭設置成對外部不可見。格式以下:
type Student struct { Name string age int classId int } func main(){ var student Student = Student{Name:"小明",age: 18, classId: 1} fmt.Printf("學生信息: 學生姓名: %s, 學生年齡: %d, 學生班級號: %d ", student.Name, student.age, student.classId) }
針對結構體,Go還支持以下的定義
type MyInt int
這是自定義的int類型,這樣作的目的是,MyInt既包含了現有int的特性,還能夠在其基礎上添加本身所須要的函數。這就涉及到結構體的高級語法了,後續咱們會詳細的介紹。
Go的做者以前設計過C語言,或許這是Go一樣有指針類型的緣由吧,不過講真的,Go中的指針比C中的指針要好理解的多。在定義時簡單在類型前面加上*
星號就行,使用時正常使用,不須要加星號。對其的賦值須要使用&
將其地址複製給該指針字段。
var log *logrus.Logger func init(){ log = logrus.New() } func main(){ log.Println("hello world") }
類型的內容仍是不少的,不一樣的類型不管是在定義上仍是使用上等都有不一樣的語境,後續會專門對其進行介紹。今天先介紹一下類型的定義。
Go中,不管是常量仍是變量等的定義語法都是同樣的。
常量的定義使用 const 關鍵字,支持隱式的定義,也能夠進行多個常量的同時定義。
const PI float = 3.1415926 //顯式定義 const SIZE = 10 //隱式定義 //多個常量同時定義 const ( LENGTH = 20 WIDTH = 15 HEIGHT = 20 ) //另外一種寫法的常量同時定義 const ADDRESS, STREET = "北京市朝陽區望京SOHO", "望京街1號"
變量的定義使用 var 關鍵字,一樣支持隱式的定義和多個變量的同時定義
var word = "hello" var size int = 10 var ( length = 20 width = 15 height = 20 ) var address, street = "北京市朝陽區望京SOHO", "望京街1號"
Go還支持在定義變量時把聲明和賦值兩步操做結合成一步來作。以下:
size := length * width * height
這樣省了定義變量這一步,代碼更簡潔,看着也更清晰,是比較推薦的方法。(常量不能這樣用哦)
爲了保證Go語言的簡潔,關鍵字在設計時也是很是的少,只有25個。
break | case | chan | const | continue |
---|---|---|---|---|
default | defer | else | fallthrough | for |
func | go | goto | if | import |
interface | map | package | range | return |
select | struct | switch | type | var |
固然,除了關鍵字,Go還保留了一部分基本類型的名稱、內置函數的名稱做爲標識符,共計36個。
append | bool | byte | cap | close | complex |
---|---|---|---|---|---|
complex64 | complex128 | copy | false | float32 | float64 |
imag | int | int8 | int16 | int32 | int64 |
iota | len | make | new | nil | panic |
println | real | recover | string | true | |
uint16 | uint32 | uint64 | uint | uint8 | uintptr |
另外,_
下劃線也是一個特殊的標識符,被稱爲空白標識符。因此,他能夠像其餘標識符那樣接收變量的聲明和賦值。但他的做用比較特殊,用來丟棄那些不想要的賦值,因此,使用_
下劃線來聲明的值,在後續的代碼中是沒法使用的,固然也不能再付給其餘值,也不能進行計算。這些變量也統一被稱爲匿名變量。
到這裏,本篇內容講解了Go中的package、func以及類型三部分的內容。也就是這三部份內容,構成了Go語言的基礎結構。到這,我們也能對 Hello World的代碼有了一個清晰的認識。也能夠嘗試着動手寫一寫簡單的例子來加深印象。下面是使用變量、常量、以及函數等基礎結構來實現的程序,能夠參考來理解。源碼能夠經過'github.com/souyunkutech/gosample'獲取。
//源碼路徑:github.com/souyunkutech/gosample/chapter3/main.go package main //定義package爲main才能執行下面的main函數,由於main函數只能在package main 中執行 //簡寫版的import導入依賴的項目 import ( "fmt" //使用其下的Println函數 "os" //使用其下的Stdout常量定義 "time" // 使用time包下的時間格式常量定義RFC3339 "github.com/sirupsen/logrus" //日誌組件 "github.com/souyunkutech/gosample/util" //本身寫的工具包,下面有自定義的函數統一使用 ) //聲明log變量是logrus.Logger的指針類型,使用時不須要帶指針 var log *logrus.Logger // 初始化函數,先於main函數執行 func init() { log = logrus.New() //使用logrus包下的New()函數進行logrus組件的初始化 log.Level = logrus.DebugLevel //將log變量中的Level字段設置爲logrus下的DebugLevel log.Out = os.Stdout log.Formatter = &logrus.TextFormatter{ //由於log.Formatter被聲明爲指針類型,因此對其賦值也是須要使用‘&’關鍵字將其地址賦值給該字段 TimestampFormat: time.RFC3339, //使用time包下的RFC3339常量,賦值時若是字段與大括號不在一行須要在賦值後面添加逗號,包括最後一個字段的賦值!!! } } //定義常量PI const PI = 3.1415926 //定義Student結構體,能夠統一使用該結構來生命學生信息 type Student struct { Name string //姓名對外可見(首字母大寫) age int //年齡不能隨便讓人知道,因此對外不可見 classId int //班級也是 } //main函數,程序執行的入口 func main() { var hello = "hello world" //定義hello變量,省略了其類型string的聲明 fmt.Println(hello) //使用fmt包下的Println函數打印hello變量 //多個變量的定義和賦值,使用外部函數生成 length, width, height := util.RandomShape() //使用其餘package的函數 //多個變量做爲外部函數的參數 size := util.CalSize(length, width, height) log.Infof("length=%d, width=%d, height=%d, size=%d", length, width, height, size) //使用日誌組件logrus的函數進行打印長寬高和size var student = Student{Name: "小明", age: 18, classId: 1} //聲明學生信息,最後一個字段的賦值不須要添加逗號 log.Debugf("學生信息: 學生姓名: %s, 學生年齡: %d, 學生班級號: %d ", student.Name, student.age, student.classId) //使用日誌組件logrus的函數進行打印學生信息 }
運行結果以下:
hello world INFO[0000] length=10, width=15, height=20, size=3000 DEBU[0000] 學生信息: 學生姓名: 小明, 學生年齡: 18, 學生班級號: 1
若是還有不理解的內容能夠經過搜雲庫技術羣進行討論或者留言,咱們都會進行解答。
源碼能夠經過'github.com/souyunkutech/gosample'獲取。
首發微信公衆號:Go技術棧,ID:GoStack
版權歸做者全部,任何形式轉載請聯繫做者。
做者:搜雲庫技術團隊
出處:https://gostack.souyunku.com/...