go語(yǔ)言中特殊注釋使用(go語(yǔ)言特性)

目錄:

1礁扮、go-build

2太伊、go-embed

3、go-generate


1锰提、go-build:

構(gòu)建約束也稱之為條件編譯立肘,就是可以對(duì)某些源代碼文件指定在特定的平臺(tái)芭概,架構(gòu),編譯器甚至Go版本下面進(jìn)行編譯踢故,在其他環(huán)境中會(huì)自動(dòng)忽略這些文件惹苗。go支持兩種方式的構(gòu)建約束桩蓉,一種是文件后綴名的方式一種是在文件的頭部通過(guò)注釋的方式。

文件后綴名構(gòu)建約束的格式為:格式就是文件名_系統(tǒng)名_架構(gòu)名.go

例如:
user_windows_amd64.go //在 windows 中 amd64 架構(gòu)下才會(huì)編譯洽瞬,其他的環(huán)境中會(huì)自動(dòng)忽略
user_linux_arm.go // 在 linux 中的 arm 架構(gòu)下才會(huì)編譯伙窃,其他環(huán)境中會(huì)自動(dòng)忽略

注釋的構(gòu)建約束則如下規(guī)則:

1)样漆、1.16之前老版本構(gòu)建約束:

go 1.16之前的構(gòu)建約束格式為構(gòu)建約束的語(yǔ)法是// +build這種形式,如果多個(gè)條件組合鳍怨,通過(guò)空格鞋喇、逗號(hào)或多行構(gòu)建約束表示,逗號(hào)表示 AND醒串,空格表示OR芜赌,!表示NOT伴逸,換行表示AND。

// +build linux,386 darwin,!cgo

它表示的意思是:(linux AND 386) OR (darwin AND (NOT cgo)) 洲愤,有些時(shí)候柬赐,多個(gè)約束分成多行書(shū)寫(xiě)官紫,會(huì)更易讀些:

// +build linux darwin
// +build amd64

這相當(dāng)于:(linux OR darwin) AND amd64

2)束世、1.17新版本構(gòu)建約束:

新版的構(gòu)建約束,注意"http://"和"go"之間不能有空格:

//go:build

同時(shí)新版語(yǔ)法使用布爾表達(dá)式沉帮,而不是逗號(hào)贫堰、空格等。布爾表達(dá)式粱檀,會(huì)更清晰易懂,出錯(cuò)可能性大大降低。

采用新語(yǔ)法后渗常,一個(gè)文件只能有一行構(gòu)建語(yǔ)句皱碘,而不是像舊版那樣有多行隐孽。這樣可以避免多行的關(guān)系到底是什么的問(wèn)題菱阵。

Go1.17 中,gofmt 工具會(huì)自動(dòng)根據(jù)舊版語(yǔ)法生成對(duì)應(yīng)的新版語(yǔ)法都办,為了兼容性虑稼,兩者都會(huì)保留蛛倦。比如原來(lái)是這樣的:

// +build !windows,!plan9

執(zhí)行 Go1.17 的 gofmt 后溯壶,變成了這樣:

//go:build !windows && !plan9
// +build !windows,!plan9

如果文件中已經(jīng)有了這兩種約束形式,gofmt 會(huì)根據(jù) //go:buid 自動(dòng)覆蓋 // +build 的形式躲庄,確保兩者表示的意思一致钾虐。如果只有新版語(yǔ)法效扫,不會(huì)自動(dòng)生成舊版的,這時(shí)浩习,你需要注意谱秽,它不兼容舊版本了。


2郊供、go-embed:

//go:embed指令是Go 1.16版本新增的官方編譯指令近哟,它可以將任何文件或者文件夾的內(nèi)容打包到編譯出的可執(zhí)行文件中吉执。

簡(jiǎn)單來(lái)說(shuō),我們可以給代碼添加一行特殊的注釋來(lái)熙掺,Go 編譯器知道是要嵌入文件還是嵌入文件夾适掰。注釋長(zhǎng)得像"http://go:embed 文件名"荠列。注釋占一行肌似,緊接著是一個(gè)變量。如果要嵌入文件力细,變量的類型得是 string 或者 []byte固额,如果要嵌入一組文件斗躏,變量的類型得是embed.FS啄糙。指令格式有三種形式:

  • //go:embed path…:path… 是需要嵌入的文件或目錄,可以為多個(gè)沈堡,用空格分隔燕雁;
  • //go:embed regexp:regexp 是需要嵌入的文件名或目錄名的正則表達(dá)式;
  • //go:embed dir/.ext:dir/.ext 是需要嵌入的某個(gè)目錄下特定擴(kuò)展名的文件崩泡;

注意事項(xiàng):

  • //go:embed是Go語(yǔ)言中的指令猬膨,看起來(lái)很像注釋但是并非是注釋,其中//和go:embed兩者之間不能有空格呛伴,必須挨在一起勃痴;
  • //go:embed后面接要嵌入的文件路徑,以相對(duì)路徑形式聲明文件路徑热康,文件路徑和//go:embed指令之間相隔一個(gè)空格沛申,這里文件相對(duì)路徑;相對(duì)的是當(dāng)前源代碼文件的路徑姐军,并且這個(gè)路徑不能以/或者./開(kāi)頭铁材;
  • 必須要導(dǎo)入embed包才能夠使用//go:embed指令;
  • 如果嵌入的文件夾中包含有以.或者_(dá)開(kāi)頭的文件奕锌,這些文件就會(huì)被視為隱藏文件,會(huì)被排除惊暴,不會(huì)被嵌入饼丘;
  • 我們還可以使用通配符形式嵌入文件夾,例如://go:embed resource/*辽话,使用通配符形式時(shí)肄鸽,隱藏文件也會(huì)被嵌入,并且文件夾本身也會(huì)被嵌入油啤;

1)典徘、嵌入單個(gè)文件:

例如:假設(shè)我們有一個(gè)文件叫 data.txt,然后我們希望在程序中引用它益咬,通過(guò) //go:embed 指令即可嵌入逮诲。

data.txt文件內(nèi)容如下:


hello go embed!

將data.txt文件嵌入到程序中賦值到data變量中


package main

import (

"embed"

"fmt"

)

//go:embed data.txt

var data string

func main() {

fmt.Println("embed data info:", data)

}

在這個(gè)示例中,我們使用//go:embed data.txt將 data.txt 文件嵌入到了可執(zhí)行文件中础废,在go build時(shí)編譯器會(huì)把data.txt內(nèi)容直接附給變量data汛骂,然后在 main() 函數(shù)中輸出了 data 變量的值。

2)评腺、嵌入多個(gè)文件:


package main

import (

"embed"

"fmt"

)

// 嵌入多個(gè)文件并作為embed.FS類型

// 將當(dāng)前目錄下test.txt和demo.txt嵌入至可執(zhí)行文件帘瞭,并存放到embed.FS對(duì)象中


//go:embed test.txt demo.txt

var embedFiles embed.FS

func main() {

// 讀取嵌入的文件,返回字節(jié)切片

testContent, _ := embedFiles.ReadFile("test.txt")

demoContent, _ := embedFiles.ReadFile("demo.txt")

// 將讀取到的字節(jié)切片轉(zhuǎn)換成字符串輸出

fmt.Println(string(testContent))

fmt.Println(string(demoContent))

}

指令部分并不需要改蒿讥,將接收變量類型改成embed.FS即可蝶念,這樣可以同時(shí)嵌入多個(gè)文件抛腕,在//go:embed指令后接多個(gè)要嵌入的文件路徑即可,多個(gè)文件路徑之間使用空格隔開(kāi)媒殉。最后通過(guò)embed.FS對(duì)象的ReadFile方法担敌,即可讀取指定的嵌入的文件的內(nèi)容,參數(shù)為嵌入的文件名廷蓉,返回讀取到的文件內(nèi)容(byte切片形式)和錯(cuò)誤對(duì)象全封。

所以,我們完全就可以把embed.FS對(duì)象想象成一個(gè)文件夾桃犬,只不過(guò)它是個(gè)特殊的文件夾刹悴,它位于編譯后的可執(zhí)行文件內(nèi)部。那么使用ReadFile函數(shù)讀取文件時(shí)攒暇,也是指定讀取這個(gè)內(nèi)部的文件夾中的文件土匀,上述我們使用//go:embed指令嵌入了兩個(gè)文件,就可以視為這兩個(gè)文件在編譯時(shí)被放入到這個(gè)特殊的“文件夾”中去了形用,只不過(guò)文件放進(jìn)去后文件名是不會(huì)改變的就轧。


3、go-generate:

Go 語(yǔ)言注釋的另一個(gè)有趣的用法是通過(guò) go generate 命令工具生成代碼田度。 go generate 是 Go 語(yǔ)言標(biāo)準(zhǔn)工具包的一部分妒御,它通過(guò)運(yùn)行用戶指定的外部命令以編程方式生成源 (或其他) 文件。go generate 的工作方式是掃描 .go 程序每币,尋找其中包含要運(yùn)行的命令的特殊注釋携丁,然后執(zhí)行它們。

具體來(lái)說(shuō)兰怠,go generate 查找以 go:generate 開(kāi)頭的注釋(注釋標(biāo)記和文本開(kāi)始之間沒(méi)有空格)梦鉴,如下:

//go:generate <command> <arguments>

3.1、generate命令工具使用:

//打印當(dāng)前目錄下所有文件揭保,將被執(zhí)行的命令
$go generate -n ./...

// 對(duì)包下所有Go文件進(jìn)行處理
$go generate github.com/ysqi/repo

// 打印包下所有文件肥橙,將被執(zhí)行的命令
$go generate -n runtime

3.2、go-generate注釋使用:

需在的代碼中配置generate標(biāo)記秸侣,則在執(zhí)行g(shù)o generate時(shí)可被檢測(cè)到存筏。go generate執(zhí)行時(shí),實(shí)際在掃描如下內(nèi)容:


//go:generate command argument...

generate命令不是解析文件味榛,而是逐行匹配以//go:generate 開(kāi)頭的行(前面不要有空格)椭坚。故命令可以寫(xiě)在任何位置,也可存在多個(gè)命令行搏色。

//go:generate后跟隨具體的命令善茎。命令為可執(zhí)行程序,形同在Shell下執(zhí)行频轿。所以命令是在環(huán)境變量中存在垂涯,也可是完整路徑烁焙。如:


package main

import "fmt"

//go:generate echo hello

//go:generate go run main.go

//go:generate echo file=$GOFILE pkg=$GOPACKAGE

func main() {

fmt.Println("main func")

}

// 執(zhí)行后輸出如下結(jié)果:
$ go generate
hello
man func
file=main.go pkg=main

在執(zhí)行g(shù)o generate時(shí)將會(huì)加入些信息到環(huán)境變量,可在命令程序中使用,相關(guān)變量如下:

  • $GOARCH:架構(gòu) (arm, amd64, etc.)耕赘;
  • $GOOS:linux, windows等等骄蝇;
  • $GOFILE:當(dāng)前處理中的文件名;
  • $GOLINE:當(dāng)前命令在文件中的行號(hào)操骡;
  • $GOPACKAGE:當(dāng)前處理文件的包名九火;
  • $DOLLAR:固定的"$",不清楚用途;

3.2.1当娱、使用示例:

比如我們定義一個(gè)對(duì)象后吃既,為了打印友好內(nèi)容,我們經(jīng)常手工定義對(duì)應(yīng)枚舉常量的String方法或映射跨细,用于輸出對(duì)應(yīng)枚舉常量的友好信息。當(dāng)增加一個(gè)枚舉常量的時(shí)候我們都需增加對(duì)應(yīng)的字符映射河质。

type Status int
const (
    Offline Status = iota
    Online
    Disable
    Deleted
)
var statusText = []string{"Offline", "Online", "Desable", "Deleted"}
func (s Status) String() string {
    v := int(s)
    if v < 0 || v > len(statusText) {
        return fmt.Sprintf("Status(%d)", s)
    }
    return statusText[v]
}

這里我們可以使用generate來(lái)生成對(duì)枚舉常量的友好輸出信息冀惭。

1)、編寫(xiě)使用go generate工具根據(jù)注釋生成代碼的go程序:

package main

import (
    "bytes"
    "flag"
    "fmt"
    "go/ast"
    "go/build"
    "go/format"
    "go/parser"
    "go/token"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "strings"
    "text/template"
)

var (
    pkgInfo *build.Package
)
var (
    typeNames = flag.String("type", "", "必填掀鹅,逗號(hào)連接的多個(gè)Type名")
)

func main() {
    flag.Parse()
    if len(*typeNames) == 0 {
        log.Fatal("-type 必填")
    }
    consts := getConsts()
    src := genString(consts)
    //保存到文件
    outputName := "./status2str_gen.go"
    if outputName == "" {
        types := strings.Split(*typeNames, ",")
        baseName := fmt.Sprintf("%s_string.go", types[0])
        outputName = filepath.Join(".", strings.ToLower(baseName))
    }
    err := ioutil.WriteFile(outputName, src, 0644)
    if err != nil {
        log.Fatalf("writing output: %s", err)
    }
}
func getConsts() map[string][]string {
    //獲得待處理的Type
    types := strings.Split(*typeNames, ",")
    typesMap := make(map[string][]string, len(types))
    for _, v := range types {
        typesMap[strings.TrimSpace(v)] = []string{}
    }
    //解析當(dāng)前目錄下包信息,即獲取當(dāng)前目錄下所有g(shù)o文件信息用于語(yǔ)法樹(shù)解析
    var err error
    pkgInfo, err = build.ImportDir(".", 0)
    if err != nil {
        log.Fatal(err)
    }
    fset := token.NewFileSet()
    for _, file := range pkgInfo.GoFiles {
        //解析go文件內(nèi)容
        f, err := parser.ParseFile(fset, file, nil, 0)
        if err != nil {
            log.Fatal(err)
        }
        typ := ""
        //遍歷每個(gè)樹(shù)節(jié)點(diǎn)
        ast.Inspect(f, func(n ast.Node) bool {
            decl, ok := n.(*ast.GenDecl)
            // 只需要const
            if !ok || decl.Tok != token.CONST {
                return true
            }
            for _, spec := range decl.Specs {
                vspec := spec.(*ast.ValueSpec)
                if vspec.Type == nil && len(vspec.Values) > 0 {
                    // 排除 v = 1 這種結(jié)構(gòu)
                    typ = ""
                    continue
                }
                //如果Type不為空散休,則確認(rèn)typ
                if vspec.Type != nil {
                    ident, ok := vspec.Type.(*ast.Ident)
                    if !ok {
                        continue
                    }
                    typ = ident.Name
                }
                //typ是否是需處理的類型
                consts, ok := typesMap[typ]
                if !ok {
                    continue
                }
                //將所有const變量名保存
                for _, n := range vspec.Names {
                    consts = append(consts, n.Name)
                }
                typesMap[typ] = consts
            }
            return true
        })
    }
    return typesMap
}
func genString(types map[string][]string) []byte {
    const strTmp = `
    package {{.pkg}}
    import "fmt"
    
    {{range $typ,$consts :=.types}}
    func (c {{$typ}}) String() string{
        switch c { {{range $consts}}
            case {{.}}:return "{{.}}"{{end}}
        }
        return fmt.Sprintf("Status(%d)", c) 
    }
    {{end}}
    `
    pkgName := os.Getenv("GOPACKAGE")
    if pkgName == "" {
        pkgName = pkgInfo.Name
    }
    data := map[string]interface{}{
        "pkg":   pkgName,
        "types": types,
    }
    //利用模板庫(kù),生成代碼文件
    t, err := template.New("").Parse(strTmp)
    if err != nil {
        log.Fatal(err)
    }
    buff := bytes.NewBufferString("")
    err = t.Execute(buff, data)
    if err != nil {
        log.Fatal(err)
    }
    //格式化
    src, err := format.Source(buff.Bytes())
    if err != nil {
        log.Fatal(err)
    }
    return src
}


// 將上面的程序編譯為myenumstr
$go build -o myenumstr

2)乐尊、為要生成對(duì)應(yīng)友好輸出的對(duì)象添加generate注釋:

package main

type Status int

//go:generate ./myenumstr -type Status,Color
const (
    Offline Status = iota
    Online
    Disable
    Deleted
)

type Color int

const (
    Write Color = iota
    Red
    Blue
)

// 執(zhí)行g(shù)o工具的generate的命令
$go generate


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末戚丸,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子扔嵌,更是在濱河造成了極大的恐慌限府,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,372評(píng)論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件痢缎,死亡現(xiàn)場(chǎng)離奇詭異胁勺,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)独旷,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén)署穗,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人嵌洼,你說(shuō)我怎么就攤上這事案疲。” “怎么了麻养?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,415評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵褐啡,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我回溺,道長(zhǎng)春贸,這世上最難降的妖魔是什么混萝? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,157評(píng)論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮萍恕,結(jié)果婚禮上逸嘀,老公的妹妹穿的比我還像新娘。我一直安慰自己允粤,他們只是感情好崭倘,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,171評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著类垫,像睡著了一般司光。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上悉患,一...
    開(kāi)封第一講書(shū)人閱讀 51,125評(píng)論 1 297
  • 那天残家,我揣著相機(jī)與錄音,去河邊找鬼售躁。 笑死坞淮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的陪捷。 我是一名探鬼主播回窘,決...
    沈念sama閱讀 40,028評(píng)論 3 417
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼市袖!你這毒婦竟也來(lái)了啡直?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 38,887評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤苍碟,失蹤者是張志新(化名)和其女友劉穎酒觅,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體驰怎,經(jīng)...
    沈念sama閱讀 45,310評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡阐滩,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,533評(píng)論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了县忌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片掂榔。...
    茶點(diǎn)故事閱讀 39,690評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖症杏,靈堂內(nèi)的尸體忽然破棺而出装获,到底是詐尸還是另有隱情,我是刑警寧澤厉颤,帶...
    沈念sama閱讀 35,411評(píng)論 5 343
  • 正文 年R本政府宣布穴豫,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏精肃。R本人自食惡果不足惜秤涩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,004評(píng)論 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望司抱。 院中可真熱鬧筐眷,春花似錦、人聲如沸习柠。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,659評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)资溃。三九已至武翎,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間溶锭,已是汗流浹背宝恶。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,812評(píng)論 1 268
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留趴捅,地道東北人卑惜。 一個(gè)月前我還...
    沈念sama閱讀 47,693評(píng)論 2 368
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像驻售,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子更米,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,577評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容