【跟着咱們學Golang】基礎結構

鑑於上篇文章咱們已經講過Go語言環境的安裝,如今咱們已經有了一個能夠運行Go程序的環境,並且,咱們還運行了'Hello World'跑出了咱們的第一個Go程序。 這節咱們就以'Hello World爲例,講解Go的基礎結構,詳細的解釋一下Hello World中的每一行都表明了什麼。mysql

Go語言是一門靜態語言,在編譯前都會對代碼進行嚴格的語法校驗,若是語法錯誤是編譯不過去的,因此基礎結構是很是重要的一個環節。git

相似Java中的package、class、interface、函數、常量和變量等,Go也有package、struct、interface、func、常量、變量等內容。github

struct相似Java中的class,是Go中的基本結構題。 interface也相似Java中的接口,可定義函數以供其餘struct或func實現(可能不太好理解,後面會講)。golang

這裏咱們按照由外而內,從上至下的的順序進行講解。sql

GOPATH

上節說到GOPATH指定了存放項目相關的文件路徑,下面包含'bin'、'pkg'、'src'三個目錄。數據庫

1.src 存放項目源代碼編程

2.pkg 存放編譯後生成的文件json

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

package的存在,是爲了解決文件過多而形成的絮亂等問題,太多的文件都放在項目根目錄下看着老是讓人以爲不舒服,對開發、後期維護等都是問題。

做爲代碼結構化方式之一, 每一個Go程序都有package的概念。

Go語法規定

  1. 每一個Go文件的第一行(不包含註釋)都必須是package的定義
  2. 可執行的程序package定義必須是main
  3. package默認採用當前文件夾的名字,不採用相似Java中package的級聯定義

以下面的代碼指定該文件屬於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"這個用戶全部。

package的獲取

在引用某個項目以前,須要先獲取其源碼,將其放置到$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的方式進行獲取。

package的引用

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中,有

  • 基本類型:int(整型)、float(浮點型)、 bool(布爾型)、 string(字符串類型)
  • 集合類型:array(數組)、 slice(切片)、 map(map)、 channel(通道)
  • 自定義類型: struct(結構體)、func(函數)等
  • 指針類型

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
print 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

版權歸做者全部,任何形式轉載請聯繫做者。

做者:搜雲庫技術團隊 出處:gostack.souyunku.com/2019/04/22/…

相關文章
相關標籤/搜索