# 1. Go语言简述
# 1.1 环境变量与工程结构
安装好Go程序后,在开始写程序前,我们需要对Go的环境变量以及工程目录结构有一定的了解,这样会帮助我们明了以及梳理Go的一些运行目录以及文件依赖下载目录。
其中GOROOT
GOPATH
这二个环境变量的概念以及目录结构尤其重要,我们在命令行模式下输入: go env
命令,就可以得到环境变量的值,如下图:
GOARCH 目标处理器架构
GOBIN 编译器和链接器安装位置
GOOS 目标操作系统
GOROOT
Go语言安装根目录,也是Go的安装路径;
Windows系统默认安装为:
C:/go
目录, Linux系统则一般会为:/usr/local/go
目录;在GOROOT所在目录下,会有下列子目录,其作用分别如下表:
目录名 说明 api 存放每个版本的api变更差异 bin 存放所有有官方提供的go语言相关工具的可执行文件,默认情况下,该目录包含go和gofmt这两个工具 pkg 存放Go语言标准库的所有归档文件;
(1)pkg文件夹包含一个与Go安装平台相关的子目录,我们称之为“平台相关目录”。例如,在针对Linux 32bit操作系统的二进制安装包中,平台相关目录的名字就是linux_386;而在针对Windows 64bit操作系统的安装包中,平台相关目录的名字则为windows_amd64。
(2)Go源码文件对应于以“.a”为结尾的归档文件,它们就存储在pkg文件夹下的平台相关目录中
(3) pkg文件夹下有一个名叫tool的子文件夹,该子文件夹下也有一个平台相关目录,其中存放了很多可执行文件lib 引用的一些库文件 src 存放所有标准库、Go语言工具,以及相关底层库(C语言实现)的源码 doc 英文版的 Go 文档 misc 杂项用途的文件,例如 [Android]平台的编译、git 的提交钩子等 test 存放测试Go语言自身代码的文件 GOPATH
Go语言中跟工作空间相关的环境变量,这个变量指定Go语言的工作空间位置。 直白的说就是你程序开发的主要目录。
假如执行
go env
命令查看 GOPATH出现-bash: go: command not found
或者GOPATH值为空,则需要对GOPATH进行设置,方法如下:- Windows系统下,右键点击
我的电脑
,选择属性
->高级系统设置
->环境变量(N)
; 在弹出的菜单中添加 变量名为 GOPATH,变量值为你需要开发程序的目录,例如:D:\Go即可,保存后完成。 - Linux系统下,执行
vim /etc/profile
命令,在文件尾部添加export GOPATH=/root/go
(/root/go为演示路径, 用户可自行定义理想路径)和 export PATH=$PATH:$GOPATH/bin即可,然后执行
source /etc/profile` 完成操作。 - Mac系统下,执行
vi ~/.bash_profile
, 在文件尾部加上export GOPATH=$HOME/go
($Home/go为演示路径, 用户可自行定义理想路径),最后执行source ~/.bash_profile
即可。
需要注意的是 GOPATH目录可以设置多个目录,例如Linux下
export GOPATH=/root/go:/root/go2
, Windows下则直接设置变量名为 GOPATH的值为C:/go;D:/go
(演示假设目录)。- Windows系统下,右键点击
在GOPATH所在目录下,也会几个重要的子目录,其作用分别如下表:
目录名 | 说明 |
---|---|
src | 用于以代码包的形式组织并保存Go源码文件,这里的代码包,与src下的子目录一一对应。 这里指的代码包即项目代码包以及项目所依赖的第三方包 例如,若一个源码文件被声明为属于代码包logging,那么它就应当被保存在src目录下名为logging的子目录中。当然,我们也可以把Go源码文件直接放于src目录下,但这样的Go源码文件就只能被声明为属于main代码包了。除非用于临时测试或演示,一般还是建议把Go源码文件放入特定的代码包中。 |
pkg | 用于存放经由go install命令构建安装后的代码包(包含Go库源码文件)的“.a”归档文件。 该目录与GOROOT目录下的pkg功能类似。区别在于,工作区中的pkg目录专门用来存放用户(也就是程序开发者)代码的归档文件。构建和安装用户源码的过程一般会以代码包为单位进行,比如logging包被编译安装后,将生成一个名为logging.a的归档文件,并存放在当前工作区的pkg目录下的平台相关目录中。 |
bin | 与pkg目录类似,在通过go install命令完成安装后,保存由Go命令源码文件生成的可执行文件。 在Linux操作系统下,这个可执行文件一般是一个与源码文件同名的文件。而在Windows操作系统下,这个可执行文件的名称是源码文件名称加.exe后缀。 |
GOPATH目录结构图如下图所示:
# 1.2 依赖管理
在GO语言 1.6 版本以前,所有Go项目都需要的依赖代码包都必须在同一个工作空间 : $GOPATH/src
内 ,比如:
src/
github.com/golang/example/
.git/ # Git repository metadata
outyet/
main.go # command source
main_test.go # test source
stringutil/
reverse.go # package source
reverse_test.go # test source
这样就导致了Go里面没有 npm 之类的包管理工具,只有通过 go get
命令从支持的代码托管平台下载依赖,这样做会产生很多问题,比如同一个包只能保存一个版本的代码,如果你变更版本就只能重新下载。然后将原来包覆盖掉,操作起来会非常麻烦。为了解决这个问题,Go 1.6 版本将 vender
目录被添加到除GOPATH
和GOROOT
之外的依赖目录,如果项目目录下有 vendor 目录,那么Go语言编译器会优先使用 vendor 内的包进行编译、测试等。当然也可以使用 godep 或者 dep 等管理工具来对vendor目录进行有效管理。
介绍完Go依赖包管理的发展历程后,来重点讲下另外一个包依赖管理工具 go module , Go语言从 1.11 版本之后官方推出的该版本管理工具,并且从 Go1.13 版本开始,go module 成为了Go语言默认的依赖管理工具。
go env
获取环境变量后,有一个环境变量跟go module 状态息息相关,这个变量就是 : GO111MODULE , 要启用go module支持首先要设置环境变量GO111MODULE
,通过它可以开启或关闭模块支持,它有三个可选值:off
、on
、auto
,默认值是auto
。
MacOS 或者 Linux 下临时开启 GO111MODULE 的命令为:
export GO111MODULE=on
全局开启 GO111MODULE 的命令为:
go env -w GO111MODULE=on
GO111MODULE 开启后,我们需要对 GOPROXY进行设置,GOPROXY顾名思义就是代理服务器的意思。大家都知道,国内的网络有防火墙的存在,这导致有些Go语言的第三方包我们无法直接通过go get
命令获取。GOPROXY 是Go语言官方提供的一种通过中间代理商来为用户提供包下载服务的方式。要使用 GOPROXY 只需要设置环境变量 GOPROXY 即可,Go1.13之后GOPROXY
默认值为https://proxy.golang.org
。
目前国内较好公开的代理服务器的地址有:
- 阿里: https://mirrors.aliyun.com/goproxy/
- 七牛:https://goproxy.cn/
GOPROXY 设置命令为:
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct
在开启了 GO111MODULE 以及设置了 GOPROXY后就可以使用 go module 工具了,也就是说**在以后的开发中就没有必要在 GOPATH 中创建项目了**,并且还能够很好的管理项目依赖的第三方包信息。
使用 go module 最主要的流程有2个:
- 使用 go module 的
go mod init
命令 初始化生成 go.mod文件 - 执行
go run | go build | go install
等运行编译程序指令,自动下载依赖包后完成程序编译执行流程。
例如我们在 /root/go2/src/main 目录下,存放一个 main.go程序,代码如下:
package main
import (
"fmt"
"github.com/labstack/echo"
)
func main(){
e := echo.New()
fmt.Println(e.Start(":1323"))
}
然后
在main目录下使用 go module 的go mod init
命令后会在当前执行目录下生成一个 go. mod 文件,假如main目录不在 GOPATH 目录下,则需要 go mod init moduleName
, moduleName 为模块名。
在运行/编译程序或者使用 go get
等 命令时,会在当前目录下生成go.sum文件。这里需要说明一点的是,执行 go run | go build | go install
等运行编译程序指令时,go mod 会自动下载依赖库到 GOPATH/pkg/mod 目录下,而不是GOPATH/src目录下(编译运行相关命令,会具体在 1.4 介绍)
命令执行后的main目录结构如下所示:
[root@instance-vqcz8mru main]# ls
go.mod go.sum main.go
其中go.mod 文件记录了项目所有的依赖信息,其结构大致如下:
module main
go 1.15
require (
github.com/labstack/echo v3.3.10+incompatible // indirect
github.com/labstack/gommon v0.3.0 // indirect
golang.org/x/crypto v0.0.0-20201221181555-eec23a3978ad // indirect
)
module 为 go.mod 文件所属的包,require 为项目所依赖的包及版本号,indirect 表示间接引用。
还有一种情况,就是本地包或者外网无法下载的包,则需要手工修改 go.mod 文件,使用关键字 replace
指令来替代,例如 本地有个 mypackage 包,加载这个包需要在 go.mod文件中加入如下版本信息:
require "mypackage" v0.0.0
replace "mypackage" => "../mypackage"
而 go.sum 文件则是用来记录每个依赖包的版本及哈希值,如下所示 :
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
.................
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
GO111MODULE 除了 go mod init
命令外,还有一些其他命令也可以了解下:
命令 | 说明 |
---|---|
go mod init | 初始化当前文件夹,创建 go.mod 文件 |
go mod edit | 编辑 go.mod 文件 |
go mod download | 下载依赖包到本地(默认为 GOPATH/pkg/mod 目录) |
go mod tidy | 增加缺少的包,删除无用的包 |
go mod verify | 校验依赖 |
go mod why | 解释为什么需要依赖 |
go mod graph | 打印模块依赖图 |
go mod vendor | 将依赖复制到 vendor 目录下 |
# 1.3 第一个Go程序
下面开始写第一个Go语言程序吧,跟其他语言一样,来个 Hello World! 吧,代码如下 :
package main //声明包名
import "fmt" //导入fmt包
func main() { // 声明 main 主函数
fmt.Println("Hello World!") // 打印 Hello World!
}
下面就来一一介绍各个主要代码的含义吧
# package(创建包)
Go语言以“包”作为管理单位,每个 Go 源文件必须先声明它所属的包,所以我们会看到每个 Go 源文件的开头都是一个 package 声明,格式如下:
package name
其中 package 是声明包名的关键字,name 为包的名字。
Go语言的包与文件夹是一一对应的,它具有以下几点特性:
- 一个目录下的同级文件属于同一个包。
- 包名可以与其目录名不同。
- main 包是Go语言程序的入口包,一个Go语言程序必须有且仅有一个 main 包。如果一个程序没有 main 包,那么编译时将会出错,无法生成可执行文件。
关于包的具体内容,后面会单独整理成一个章节讲述。
# import(导入包)
在包声明之后,是 import 语句,用于导入程序中所依赖的包,导入的包名使用双引号
""
包围,格式如下:
import "name"
其中 import 是导入包的关键字,name 为所导入包的名字。
需要注意的一点是,导入的包中不能含有代码中没有使用到的包,否则Go编译器会报编译错误,例如imported and not used: "yyy"
,"yyy" 表示包名。
也可以使用一个 import 关键字导入多个包,此时需要用括号( )
将包的名字包围起来,并且每个包名占用一行,也就是写成下面的样子:
import( "name1" "name2" )
# main 函数
main 函数它是Go语言程序的入口函数,也即程序启动后运行的第一个函数。main 函数只能声明在 main 包中,不能声明在其他包中,并且,一个 main 包中也必须有且仅有一个 main 函数。
main 函数是自定义函数的一种,在Go语言中,所有函数都以关键字 func 开头的,定义格式如下所示:
func 函数名 (参数列表) (返回值列表){ 函数体 }
格式说明如下:
- 函数名:由字母、数字、下画线
_
组成,其中,函数名的第一个字母不能为数字,并且,在同一个包内,函数名称不能重名。 - 参数列表:一个参数由参数变量和参数类型组成,例如
func foo( a int, b string )
。 - 返回值列表:可以是返回值类型列表,也可以是参数列表那样变量名与类型的组合,函数有返回值时,必须在函数体中使用 return 语句返回。
- 函数体:能够被重复调用的代码片段。
# 1.4 编译与运行
Go语言是编译型的静态语言(和C语言一样),所以在运行Go语言程序之前,先要将其编译成二进制的可执行文件。
Go语言提供的go build
go run
go install
go get
等命令对Go语言程序进行编译。
go run
go run
命令将编译和执行指令合二为一,会在编译之后立即执行Go语言程序,但是不会生成可执行文件, 直接在命令行输出程序执行结果,方便用户调试。go run
命令的语法格式如下:go run fileName
其中 fileName 为所需要的参数,参数必须是同一main包下的所有源文件名,并且不能为空
执行 1.3 章节的第一个Go程序,结果如下:
[root@instance-vqcz8mru hello]# go run hello.go Hello World!
go build
go build 用于启动编译,检查是否会有编译错误,如果是一个可执行文件的源码(即是 main 包),就会在当前目录直接生成一个可执行文件。
其语法格式如下:
go build fileName
其中 fileName 为所需要的参数,可以是一个或者多个 Go 源文件名(当有多个参数时需要使用空格将两个相邻的参数隔开),也可以省略不写。
1) 当fileName参数不为空时
- 如果 fileName 不是main包文件,只是普通包文件,则只执行编译校验,不产生可执行文件
- 如果 fileName 是main包文件(可能有一个或者多个),编译器将生成一个与第一个 fileName 同名的可执行文件( 如执行
go build hello.go t.go ...
会生成一个 hello文件名或者hello.exe文件名的可执行文件【平台不同生成可执行文件类型不一样】)
2) 当参数为空时
- 如果当前目录为main包,则会在当前目录直接生成一个该包名称的可执行文件
- 如果当前目录为其他包, 则只执行编译校验,不产生可执行文件
go install
go install
命令基本与go build
功能是一致的,用于启动编译生成可执行文件,但两者也存在着区别:- go build 会生成可实行文件放在当前目录中, go install 则会把它放到 $GOPATH/bin 中
- go install 是建立在 GOPATH 上的,无法在独立的目录里使用 go install。 (必须配置GOPATH环境变量)
- go install 还会把导入的依赖包编译到 $GOPATH/pkg,并缓存,如果包未做更改,下次编译则直接使用缓存。
go build
命令加参数 -i 也能达到go install 的效果。
go install
命令基本语法:go install package
package 为包名
go get
go get
命令主要是用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行go install。所以为了 go get 能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的PATH,比如Git工具。
go get
命令基本语法:go get package
使用示例:
go get github.com/davyxu/cellnet
go get github.com/davyxu/tabtoy
等go get
还有一些辅助参数,配合附加参数显示更多的信息及实现特殊的下载和安装操作,详见下表所示:附加参数 说明 -v 显示操作流程的日志及信息,方便检查错误 -u 下载丢失的包,但不会更新已经存在的包 -d 只下载,不安装 -insecure 允许使用不安全的 HTTP 方式进行下载操作