GO语言基础篇(二) - 源码文件及代码包

1,167 阅读10分钟

这是我参与8月更文挑战的第 2 天,活动详情查看: 8月更文挑战

本文主要分享GO源码文件的分类和含义,以及代码包的相关内容,主要都是为了在学习go的基础语法时,不至于对很多内容不知所以然。如果你对GO环境变量相关内容还不熟悉,戳这里

工作区和GOPATH

工作区是放置项目源码文件的目录,一般情况下,项目源码文件都需要存放到工作区中。但是对命令源码文件来说,并不是必须的(但是推荐放到工作区中)。每一个工作区的结构都类似这样:

我的工作区:/Users/shulv/studySpace/GolangProject

src/
pkg/
bin/

image.png

官方推荐:所有项目和第三方库,都放在同一个GOPATH下(当然可以不这样做,可以将每个项目放在不同的GOPATH下)

src目录

用于存放项目源码文件,以代码包为组织形式。也就是说,src目录下,还可以有若干级的子目录,每级子目录,其实就是一个代码包(后边会专门介绍)

pkg目录

用于存放归档文件(名称以.a为后缀的文件),所有归档文件都会被存放到该目录下的 平台相关目录中,同样以代码包为组织形式

平台相关目录

GO环境搭建的文章中,有提到GOOS和GOARCH这两个环境变量,他们分别代表 为其编译代码的操作系统为其编译代码的架构或处理器

平台相关目录以GOOS_GOARCH为命名方式,如我的电脑是64位的mac OS,那么平台相关目录就是:darwin_amd64

所有我们编译后的,或安装之后的函数库或者代码包,会被安装到 工作区目录/pkg/平台相关目录/一级代码包/二级代码包/末级代码包.a

bin目录

用于存放当前工作区中的GO程序的可执行文件。我们在配置环境变量时,有一个GOBIN,它的值就是工作区中bin目录的路径

有两种情况,这个bin目录会变得没有意义

  1. 当环境变量GOBIN已经设置了有效的目录时,该目录会变得没意义
  2. 当GOPATH的值中包含多个工作区的路径时,必须设置GOBIN(此时每一个工作区中的bin目录,其实就没什么用了),否则无法成功安装GO程序的可执行文件

源码文件的分类和含义

GO源码文件

go源码文件,就是以.go为后缀,内容以go语言代码组织的文件。多个go源码文件是需要代码包组织起来的

GO源码文件分类

分三类:命令源码文件、库源码文件、测试源码文件

命令源码文件和库源码文件是有一定功能的,也就是我们通常说的go源码程序。测试源码文件是用来测试上边这两类文件的,它是一种辅助源码文件

命令源码文件

声明自己属于main代码包,包含无参数声明和结果声明的main函数。满足这个条件的源码文件,就可以将其归类为命令源码文件。比如

package main //**声明自己属于main代码包**

import "fmt"

func main()  { //**包含无参数声明和结果声明的main函数**
	fmt.Println("Just a test")
}

命令源码文件被安装之后(源码文件如何被安装,会在后边介绍go中常用命令时分享),相应的可执行文件会被存放到GOBIN指向的目录:当前工作区/bin目录下(如果只有一个工作区的话,会被安装到当前工作区的bin目录下

命令源码文件其实就是go程序的入口,但不建议将程序都写在一个文件中。注意:同一个代码包中(暂时可以将代码包理解成一个目录),强烈不建议直接包含多个命令源码文件(即go程序入口)。命令源码文件应该是被单独放置到一个代码包中的。多个命令源码文件虽然可以分开单独 go run 运行起来,但是无法通过 go build 和 go install(go的常用命令,会在后边单独的文章中分享)

库源码文件

其实就是不具备命令源码文件的那两个特征的源码文件。库源码文件被安装之后,相应的归档文件会被存放到:当前工作区目录/pkg/平台相关目录

一般用于集中放置各种待被使用的程序实体(全局常量、全局变量、接口、结构体、函数等等)

测试源码文件

也是不具备命令源码文件的那两个特征的源码文件。名称以_test.go为后缀

测试源码文件中,肯定是要有测试函数的。测试函数有两种,一个是以Test为前缀的测试函数,叫功能测试函数,另一个是以Benchmark为前缀的函数,叫基准测试函数或性能测试函数。并且,他们分别需要接受一个类型为*testing.T 和 *testing.B 的参数

//功能测试函数
func TestFind(t *testing.T) {

}

//基准测试函数或性能测试函数
func BenchmarkFind(b *testing.B){

}

代码包相关知识

在上边多次提到了代码包,下边详细的分享一下代码包的相关内容

代码包的作用

编译和归档go程序的最基本单位。也就是说,我们可以将若干个go程序源码文件,放在一个代码包下边,然后对这个代码包进行编译或者归档

代码包也是代码划分、集结和依赖的有效组织形式,也是权限控制的辅助手段。通过对go语言后边知识的学习,会深入的了解这段话

代码包的规则

一个代码包实际上就是一个由导入路径代表的目录

导入路径实际上是一个子路径,这个子路径可以存在于,工作区目录/src 或 工作区目录/pkg/平台相关目录

例如:代码包study.cn,可以对应于
/Users/shulv/studySpace/GolangProject/src/study.cn 目录(其中,/Users/shulv/studySpace/GolangProject是我的工作区)

我有这样一个代码包,它的导入路径是study.cn,它对应的文件系统目录是/Users/shulv/studySpace/GolangProject/src/study.cn,我们知道/Users/shulv/studySpace/GolangProject是我的一个工作区,那依据上边的规则,study.cn这个代码包,可以对应到/Users/shulv/studySpace/GolangProject/src/study.cn 目录。当然,study.cn代码包,还可以存在到对应到其它的目录,可以将之前的工作区目录替换掉即可

代码包的声明

每个源码文件必须声明其所属的代码包。同一个代码包中的所有源码文件,声明的代码包应该是相同的

代码包声明与代码包导入路径的区别

代码包声明语句中的包名称应该是该代码包的导入路径的最右子路径。比如:

比如现在有这么一个代码包study.cn/learnTool,有一个源码文件a.go是属于这个代码包下边的,那么这个源码文件
里边的代码包声明语句应该就是: package learnTool

这样有一个好处就是,比如我们现在想将learnTool这个子代码包迁移到其它的地方,就可以直接迁移,而不需要修改源代码文件中的代码包声明语句

代码包的导入

代码包导入语句中使用的包名称应该与其导入路径一致。如:

假设现在有三个代码包
flag
fmt
strings
这三个代码包要导入到当前的源码文件中,需要这么写
import(
		"flag"
		"fmt"
		"strings"
)
可以发现,代码包的导入路径和写的导入语句中的字符串是完全一致的

代码包的导入方法

  1. 带别名的导入
import str "strings"
将要导入的strings代码包起了一个别名叫str,那么在当前源码中,使用strings代码包中的方法时,就可以这样写
str.HasPrefix("abc","a")
  1. 本地化的导入
import . "strings"

此时假设我需要调用strings代码包中的HasPrefix()函数,就可以在源代码文件中直接使用HasPrefix("abc","a")

发现可以不写前缀了,这个本地化导入意思就是说,我们可以调用strings代码包下的程序实体,就像调用当前代码包下的程序实体那样,不写任何前缀

  1. 仅仅初始化
import _ "strings"

在程序源码中导入了这个代码包,但是并不想调用该代码包中的任何程序实体,仅执行代码包中的初始化函数

代码包的初始化

代码包的初始化,是由代码包初始化函数来提供功能的,即:无参数声明和结果声明的init函数

init函数可以被声明在任何文件中,且可以有多个

init函数的执行时机 —— 单一代码包内

在一个单一的代码包内,当导入一个代码包的时候,首先这个代码包中的全局变量会被求值,在所有全局变量求值之后,代码包中的所有init函数会被执行。同一个代码包中的多个init函数的执行顺序是不确定的

init函数的执行时机 —— 不同代码包之间

如果当前代码包和它导入的代码包里边都有init函数,首先执行被导入的代码包中的init函数,然后再执行导入它的那个代码包的init函数

假设现在有三个代码包,分别是A、B、C。它们的导入顺序是,A中导入了B,B中导入了C。那么此时,这三个代码包中init函数的执行顺序是:C、B、A

我们不应该对在同一个代码包中被导入的多个代码包的init函数的执行顺序做出假设。比如我们现在有三个代码包a、b、c,在代码包a中导入了b和c,此时我们不应该假设b和c中哪一个代码包中的init函数先被执行

init函数的执行时机 —— 所有涉及到的代码包

在go程序中所有被导入的代码包的init函数,他们的执行,都是在程序入口(也就是main函数)之前 执行,也就是说init函数先执行,main函数才会被执行。不管有多少个init函数,每一个init函数都只会被执行一次

go语言编译时,查找依赖包的原理

go语言在编译的时候,会去不同的GOPATH中找到自己所依赖的包。例如:

假设现在我有一个study.go文件,里边引入了
import (
		"fmt"
		"study.cn/learnGo"
)

study.go中引入了fmt和study.cn/learnGo这两个包。那么,在编译study.go的时候,它会先去GOROOT指定的目录的src下去找相关依赖的包,此时它会找到fmt这个系统包,如果发现还有依赖的包没有找到,它就回去GOPATH指定的目录下去找相关依赖的包

感谢以下优秀文章

工作区和GOPATH以及最基本的go语言命令详解

源码文件的分类和含义