失眠网,内容丰富有趣,生活中的好帮手!
失眠网 > [Go语言入门] 02 Go语言程序结构

[Go语言入门] 02 Go语言程序结构

时间:2020-09-05 07:42:18

相关推荐

[Go语言入门] 02 Go语言程序结构

文章目录

02 Go语言程序结构2.1 Go语言的一些基本概念2.2 go源文件的代码结构2.3 Go项目的基本文件结构2.4 实战:创建一个模块2.5 实战:从另一个模块调用刚创建的模块2.6 Go程序的初始化过程

02 Go语言程序结构

2.1 Go语言的一些基本概念

变量

Go语言变量源于数学中变量的概念。变量表示一块内存区域,用来存放数据。变量有一个名字,称作变量名。通过变量名可以访问变量,读写变量的内存数据。

举例:

var a = 1 // 变量a,存放数字1var b = 2 // 变量b,存放数字2var c = "hello"// 变量c,存放字符串"hello"

数据类型

Go语言的变量中存放的数据是有类型的。数据类型是对变量中存放的数据的某种解释方式,即程序把内存中的数据看做是什么样的值。

Go语言的数据类型可分为:基本数据类型和派生数据类型。

常见的基本数据类型有:布尔型(bool)、数值型(int,int32,int64,uint,uint32,uint64,…)、字符串型(string)。

函数

函数代表一系列先后执行的操作。函数有一个名字,称作函数名。通过函数名可以调用函数,执行其所代表的操作。

举例:

package mainimport "fmt"// 函数sumOfSquares返回两个数的平方和func sumOfSquares(a int, b int) int {var c = a*a + b*breturn c}func main() {var sum intsum = sumOfSquares(3, 5)// 调用函数sumOfSquaresfmt.Printf("sum = %d\n", sum)sum = sumOfSquares(5, 6)fmt.Printf("sum = %d\n", sum)// 调用函数sumOfSquares}

注释

/*这是多行注释。这是它的第二行。这是它的第三行。*/// 这是单行注释

包由一个或多个go源文件聚合而成。这些go源文件存放在同一目录下,这个目录称作包目录。

包是对这些go源文件中声明的数据类型、变量、函数等的封装。

可以设置包中的一些名称是导出的,以便外部包使用;非导出的名称,仅可在包内部使用。

包给包中的代码提供了独立的命名空间,不同包中的相同名称不会冲突。

模块

模块由一个或多个包聚合而成。这些包的包目录放在同一顶层目录下,这个顶层目录称作模块目录。

Go语言是以模块为单位定义编译时需要的上下文的,包括:使用的Go版本、依赖的其他模块以及版本。

2.2 go源文件的代码结构

// 当前程序的包声明,说明当前程序所属的包package main// 导入其他包import "fmt"// 常量定义const PI = 3.14// 全局变量的声明和赋值var name = "gopher"// 一般类型声明type newType int// 结构的声明type gopher struct{}// 接口的声明type golang interface{}// 函数的声明func circleArea(r float32) float32 {return PI*r*r;}// 由main函数作为程序入口点启动func main() {var text = "Hello, World!"fmt.Println(text)var area = circleArea(5)fmt.Println(area)}

2.3 Go项目的基本文件结构

Go项目的文件组织结构一般如下:

+ 项目目录+ 模块1目录 (可以含有go文件,此时也作为一个包目录)+ uvw.go+ xyz.go+ 包a目录+ a.go+ 包b目录+ b.go+ b2.go+ 包c目录+ c.go+ 子包d目录+ d.go+ 模块2目录+ ...

Go项目的组织结构:

项目目录下包含一个或多个模块目录;模块目录下包含一个或多个go源文件/包目录;包目录下面包含一个多个go源文件/子包目录;子包目录还可以再包含go源文件和子包目录。

2.4 实战:创建一个模块

创建一个项目目录,在其下再创建一个模块目录greetings:

$ mkdir $ cd $ mkdir greetings$ cd greetings

使用go mod init初始化greetings模块:

$ go mod init /greetingsgo: creating new go.mod: module /greetings

上面的go mod init命令指定了/greetings作为模块路径。当模块发布以后,别人的项目将通过这个URL来下载模块。

go mod init命令会在当前目录生成一个go.mod文件。如果一个目录中有go.mod文件,Go就把该目录当作是一个模块目录。新生成的go.mod文件中仅定义了模块路径和使用的Go版本,如下:

module /greetingsgo 1.13

以后,当你需要在你的模块中使用其他模块时,这个文件中还会增加对其他模块的依赖,并且可以指定具体依赖的版本。

在greetings目录下新建代码文件greetings.go:

package greetingsimport "fmt"// Hello returns a greeting for the named person.func Hello(name string) string {// Return a greeting that embeds the name in a message.message := fmt.Sprintf("Hi, %v. Welcome!", name)return message}

2.5 实战:从另一个模块调用刚创建的模块

回到目录,在其下创建另一个模块目录hello:

cd ..mkdir hellocd hello

在hello目录下新建代码文件hello.go:

package mainimport ("fmt""/greetings")func main() {// Get a greeting message and print it.message := greetings.Hello("Gladys")fmt.Println(message)}

把hello目录初始化为模块:

$ go mod init hellogo: creating new go.mod: module hello

设置hello模块使用本地的greetings模块:

如果greetings模块是作为一个产品发布的话,通常会有一个URL,该URL可以是互联网的地址,也可以是公司内部服务器的地址,Go会自动从该URL下载模块代码。但是现在,我们的greetings项目并不是作为产品发布的,而是在本地文件系统中。因此,需要在hello模块中将URL替换为本地路径。

在hello项目的go.mod中添加replace指令,

module hellogo 1.13replace /greetings => ../greetings

replace指令告诉Go,查找模块时候使用后面本地路径替换前面的模块路径。

在hello目录执行go build。go build编译代码的时候检测到代码中依赖了"/greetings"包,然后定位到本地的greetings模块,并把它作为依赖项添加到go.mod中。

执行完go build之后,go.mod变为这样:

module hellogo 1.13replace /greetings => ../greetingsrequire /greetings v0.0.0-00010101000000-000000000000

将来如果greetings作为产品发布了,想让hello模块切换为使用已发布的greetings,只需要移除hello模块的go.mod文件中的replace指令,并且修改require指令使其指定一个具体的模块版本号。

module hellogo 1.13require /greetings v1.1.0

在hello目录执行刚刚生成的二进制文件

$ ./hello Hi, Gladys. Welcome!

2.6 Go程序的初始化过程

思考

下面的代码执行后输出的内容:

package mainimport "fmt"var g = globalVar()func main() {fmt.Println("main")fmt.Printf("g = %d\n", g)}func init() {fmt.Println("init")}func globalVar() int {fmt.Println("globalVar")return 3}

实测结果:

globalVarinitmaing = 3

从以上结果可以得出:

全局变量初始化最先被执行;全局变量初始化之后,init()函数执行。但奇怪的是,代码中并没有调用它;在init()函数执行之后,main()函数执行。

init()函数:

在Go语言中,init()函数是一个特殊的函数。每个go源文件中都可以定义一个init()函数,当然,go源文件中也可以不定义init()函数。

init()函数不需要显式调用,它会在包初始化的时候被自动调用。

如果某个包中的多个go源文件都定义了init()函数,并不会导致命名冲突,但这些init()函数的执行先后顺序是不确定的。

单个包的初始化过程:

(当在代码中import一个包时,会执行这个包的初始化。无论一个包在其他地方被import了多少次,都只会初始化一次。)

初始化一个包时,首先对各个go源文件中import导入的其他包执行初始化(每个被导入包的初始化也遵循本过程)。单个源文件中的import语句按出现顺序执行,多个源文件中的import语句执行顺序不确定。当本包中的所有import语句都执行完毕后,对各个源文件中的全局变量执行初始化,单个源文件中的全局变量按出现顺序执行,多个源文件中的全局变量执行顺序不确定。当本包中所有全局变量初始化完毕后,执行各个源文件中的init()函数。多个源文件中的init()函数执行顺序不确定。

如果一个包导入了其他包,被导入的包又导入了其他包,层层导入。那么按照依赖优先原则,最里层被导入的包要首先进行初始化,然后返回外层包,层层向外返回。每单个包的初始化都遵循上面的过程。

main包的初始化:

(main包的初始化也遵照上面的过程)

从执行main包中的所有import语句开始。import语句会触发被import包的初始化。当main包中的所有import语句执行完毕,main包中的全局变量开始初始化。当main包中的所有全局变量初始化完毕,main包中的所有init()函数得到执行。当main包中的所有init()函数执行完毕,main()函数得到执行。

小练习,测试Go程序的初始化过程:

创建一个目录seeinit,进入该目录,执行go mod init初始化模块:

$ mkdir seeinit$ cd seeinit$ go mod init /seeinitgo: creating new go.mod: module /seeinit

在seeinit目录下创建子目录apple,该目录用来存放包apple的源码,在apple目录下创建源码文件apple.go、apple2.go。

apple.go:

package appleimport "fmt"var Count = valueOfCount()func valueOfCount() int {fmt.Println("apple.valueOfCount()")return 1}func init() {fmt.Println("apple.init() 111")}

apple2.go:

package appleimport "fmt"var count2 = valueOfCount2()func valueOfCount2() int {fmt.Println("apple.valueOfCount2()")return 2}func init() {fmt.Println("apple.init() 222")}

在seeinit目录下创建子目录orange,该子目录用来存放包orange的源码。在orange目录下创建源码文件orange.go、orange2.go。

orange.go:

package orangeimport "fmt"var Count = valueOfCount()func valueOfCount() int {fmt.Println("orange.valueOfCount()")return 2}func init() {fmt.Println("orange.init() 111")}

orange2.go:

package orangeimport "fmt"var count2 = valueOfCount2()func valueOfCount2() int {fmt.Println("orange.valueOfCount2()")return 3}func init() {fmt.Println("orange.init() 222")}

在seeinit目录下创建子目录banana,该子目录下存放包banana的代码,在banana目录下创建源码文件banana.go:

package bananaimport "fmt"var Count = valueOfCount()func valueOfCount() int {fmt.Println("banana.valueOfCount")return 5}func init() {fmt.Println("banana init")}

在seeinit目录下创建main包文件seeit.go、seeinit2.go:

seeinit.go:

package mainimport ("fmt""/seeinit/apple""/seeinit/orange")var base = valueOfBase()func valueOfBase() int {fmt.Println("main.valueOfBase()")return 5}var sum intfunc init() {fmt.Println("main.init() 111")sum = base + apple.Count + orange.Count}func main() {fmt.Println("main()")fmt.Printf("sum = %d\n", sum)}

seeinit2.go:

package mainimport ("fmt"_ "/seeinit/banana")var base2 = valueOfBase2()func valueOfBase2() int {fmt.Println("main.valueOfBase2()")return 0}func init() {fmt.Println("main.init() 222")}

以上程序编写完毕后,在seeinit目录执行go run seeit.go,将看到如下结果:

从结果中可以印证Go程序的初始化顺序。

$ go run seeit.goapple.valueOfCount()apple.valueOfCount2()apple.init() 111apple.init() 222orange.valueOfCount()orange.valueOfCount2()orange.init() 111orange.init() 222banana.valueOfCountbanana initmain.valueOfBase()main.valueOfBase2()main.init() 111main.init() 222main()sum = 8

Copyright@ , 359152155@

如果觉得《[Go语言入门] 02 Go语言程序结构》对你有帮助,请点赞、收藏,并留下你的观点哦!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。