前段時(shí)間有群友在群里問(wèn)一個(gè)go語(yǔ)言的問(wèn)題:
就是有一個(gè)main.go的main函數(shù)里調(diào)用了另一個(gè)demo.go里的hello()函數(shù)。其中main.go和hello.go同屬于main包。但是在main.go的目錄下執(zhí)行g(shù)o run main.go卻報(bào)hello函數(shù)沒(méi)有定義的錯(cuò):
代碼結(jié)構(gòu)如下:
**gopath ---- src** **----gohello** **----hello.go** **----main.go**
main.go如下:
package main import "fmt" func main() { fmt.Println("my name is main") hello() }
hello.go如下:
package main import "fmt" func hello() { fmt.Println("my name is hello") }
當(dāng)時(shí)我看了以為是他GOPATH配置的有問(wèn)題,然后自己也按照這樣試了一下,報(bào)同樣的錯(cuò),在網(wǎng)上查了,也有兩篇文章是關(guān)于這個(gè)錯(cuò)的,也提供了解決方法,即用go run main.go hello.go,試了確實(shí)是可以的。
雖然是個(gè)很簡(jiǎn)單的問(wèn)題,但是也涉及到了go語(yǔ)言底層對(duì)于命令行參數(shù)的解析。那就來(lái)分析一下語(yǔ)言底層的實(shí)現(xiàn)吧,看一下底層做了什么,為什么報(bào)這個(gè)錯(cuò)?
分析:
以下使用到的Go SDK版本為1.8.3
該版本中g(shù)o支持的基本命令有以下16個(gè):
build compile packages and dependencies clean remove object files doc show documentation for package or symbol env print Go environment information bug start a bug report fix run go tool fix on packages fmt run gofmt on package sources generate generate Go files by processing source get download and install packages and dependencies install compile and install packages and dependencies list list packages run compile and run Go program test test packages tool run specified go tool version print Go version vet run go tool vet on packages
在Go SDK的src/cmd/go包下有main.go文件中,Command類型的commands數(shù)組對(duì)該16個(gè)命令提供了支持:
我們首先知道go語(yǔ)言的初始化流程如下:
在執(zhí)行main.go中的主函數(shù)main之前,對(duì)import進(jìn)來(lái)的包按順序初始化,最后初始化main.go中的類型和變量,當(dāng)初始化到commands數(shù)組時(shí),由于cmdRun定義在于main.go同包下的run.go中,那么就先去初始化run.go中的變量和init方法,如下代碼,先把cmdRun初始化為Command類型,然后執(zhí)行init()函數(shù)。
var cmdRun = &Command{ UsageLine: "run [build flags] [-exec xprog] gofiles... [arguments...]", Short: "compile and run Go program", Long: ` Run compiles and runs the main package comprising the named Go source files. A Go source file is defined to be a file ending in a literal ".go" suffix. By default, 'go run' runs the compiled binary directly: 'a.out arguments...'. If the -exec flag is given, 'go run' invokes the binary using xprog: 'xprog a.out arguments...'. If the -exec flag is not given, GOOS or GOARCH is different from the system default, and a program named go_$GOOS_$GOARCH_exec can be found on the current search path, 'go run' invokes the binary using that program, for example 'go_nacl_386_exec a.out arguments...'. This allows execution of cross-compiled programs when a simulator or other execution method is available. For more about build flags, see 'go help build'. See also: go build. `, } func init() { cmdRun.Run = runRun // break init loop addBuildFlags(cmdRun) cmdRun.Flag.Var((*stringsFlag)(&execCmd), "exec", "") }
init()中,將runRun(其實(shí)類型是一個(gè)方法,用于處理run后的參數(shù))賦值給cmdRu.run,addBuildFlags(cmdRun)主要是給run后面增加命令行參數(shù)(如:-x是打印其執(zhí)行過(guò)程中用到的所有命令,同時(shí)執(zhí)行它們)。其他15個(gè)命令和cmdRun類似,各有各的run實(shí)現(xiàn)。
下來(lái)主要看main.go中main的這塊代碼:
for _, cmd := range commands { if cmd.Name() == args[0] && cmd.Runnable() { cmd.Flag.Usage = func() { cmd.Usage() } if cmd.CustomFlags { args = args[1:] } else { cmd.Flag.Parse(args[1:]) args = cmd.Flag.Args() } cmd.Run(cmd, args) exit() return } }
這塊代碼遍歷commands數(shù)組,當(dāng)遍歷到cmdRun時(shí),cmd.Name()其實(shí)就是拿到cmdRun.UsageLine的第一個(gè)單詞run
就會(huì)進(jìn)入if分支,由于cmd.CustomFlags沒(méi)有初始化故為false,走else分支,然后開(kāi)始解析args命令行參數(shù),args[1:]即取run后的所有參數(shù)。然后去執(zhí)行cmd.Run(cmd, args),對(duì)于cmdRun來(lái)說(shuō),這里執(zhí)行的就是run.go中init()的第一行賦值cmdRun.run(上面說(shuō)了,這是一個(gè)函數(shù),不同命令實(shí)現(xiàn)方式不同),即去執(zhí)行run.go中的runRun函數(shù),該函數(shù)主要是將命令行參數(shù)當(dāng)文件去處理,如果是_test為后綴的,即測(cè)試文件,直接報(bào)錯(cuò)。如果是目錄也直接報(bào)錯(cuò)(而且go run后面只能包含一個(gè)含main函數(shù)的go文件)。注意到有這么一行:
p := goFilesPackage(files)
goFilesPackage(files)除了校驗(yàn)文件類型和后綴,還會(huì)入棧,加載,出棧等操作,由于啟動(dòng)的時(shí)候沒(méi)有傳遞hello.go,所以系統(tǒng)加載main.go時(shí)找不到hello函數(shù),導(dǎo)致報(bào)錯(cuò)。
審核編輯:劉清
-
main
+關(guān)注
關(guān)注
0文章
38瀏覽量
6168 -
go語(yǔ)言
+關(guān)注
關(guān)注
1文章
158瀏覽量
9050
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論