前段時間有群友在群里問一個go語言的問題:
就是有一個main.go的main函數里調用了另一個demo.go里的hello()函數。其中main.go和hello.go同屬于main包。但是在main.go的目錄下執行go run main.go卻報hello函數沒有定義的錯:
代碼結構如下:
**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") }
當時我看了以為是他GOPATH配置的有問題,然后自己也按照這樣試了一下,報同樣的錯,在網上查了,也有兩篇文章是關于這個錯的,也提供了解決方法,即用go run main.go hello.go,試了確實是可以的。
雖然是個很簡單的問題,但是也涉及到了go語言底層對于命令行參數的解析。那就來分析一下語言底層的實現吧,看一下底層做了什么,為什么報這個錯?
分析:
以下使用到的Go SDK版本為1.8.3
該版本中go支持的基本命令有以下16個:
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數組對該16個命令提供了支持:
我們首先知道go語言的初始化流程如下:
在執行main.go中的主函數main之前,對import進來的包按順序初始化,最后初始化main.go中的類型和變量,當初始化到commands數組時,由于cmdRun定義在于main.go同包下的run.go中,那么就先去初始化run.go中的變量和init方法,如下代碼,先把cmdRun初始化為Command類型,然后執行init()函數。
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(其實類型是一個方法,用于處理run后的參數)賦值給cmdRu.run,addBuildFlags(cmdRun)主要是給run后面增加命令行參數(如:-x是打印其執行過程中用到的所有命令,同時執行它們)。其他15個命令和cmdRun類似,各有各的run實現。
下來主要看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數組,當遍歷到cmdRun時,cmd.Name()其實就是拿到cmdRun.UsageLine的第一個單詞run
就會進入if分支,由于cmd.CustomFlags沒有初始化故為false,走else分支,然后開始解析args命令行參數,args[1:]即取run后的所有參數。然后去執行cmd.Run(cmd, args),對于cmdRun來說,這里執行的就是run.go中init()的第一行賦值cmdRun.run(上面說了,這是一個函數,不同命令實現方式不同),即去執行run.go中的runRun函數,該函數主要是將命令行參數當文件去處理,如果是_test為后綴的,即測試文件,直接報錯。如果是目錄也直接報錯(而且go run后面只能包含一個含main函數的go文件)。注意到有這么一行:
p := goFilesPackage(files)
goFilesPackage(files)除了校驗文件類型和后綴,還會入棧,加載,出棧等操作,由于啟動的時候沒有傳遞hello.go,所以系統加載main.go時找不到hello函數,導致報錯。
審核編輯:劉清
-
main
+關注
關注
0文章
38瀏覽量
6140 -
go語言
+關注
關注
1文章
158瀏覽量
9029
發布評論請先 登錄
相關推薦
評論