Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。
Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。
相关网站:
go官网:https://golang.org/pkg/
go依赖包搜索:https://godoc.org/
安装包下载地址为:https://golang.org/dl/。
window安装目录默认为:c:/go,bin目录自动指向bin目录。
linux解压tgz包,将bin目录添加到PATH变量中,假设go解压在/opt/go目录下
cat << 'EOF' >> ~/.bash_profile
PATH=$PATH:/opt/go/bin
export PATH
EOF
测试go是否正常安装
C:\Users\GVT>go version
go version go1.14.2 windows/amd64
直接在终端中输入 go help 即可显示所有的 go 命令以及相应命令功能简介,主要有下面这些:
在运行 go help 时,不仅仅打印了这些命令的基本信息,还给出了一些概念的帮助信息:
同样使用 go help 来查看这些概念的的信息。
就像其他静态类型语言一样,要执行 go 程序,需要先编译,然后在执行产生的可执行文件。go build 命令就是用来编译 go程序生成可执行文件的。但并不是所以的 go 程序都可以编译生成可执行文件的, 要生成可执行文件,go程序需要满足两个条件:
也就是说go程序的入口就是 main.main, 即main包下的main函数, 例子(hello.go):
cat <<EOF > hello.go
package main
import "fmt"
func main(){
fmt.Println("Hello World")
}
EOF
编译hello.go,然后运行可执行程序:
go build hello.go
当前目录下生成了hello.exe,运行
GVT@DESKTOP-V14R68B MINGW64 ~/go
$ ./hello.exe
Hello World
而 go run 命令可以将上面两步并为一步执行(不会产生中间文件)。
$ go run hello.go
Hello World!
上面两个命令都是在开发中非常常用的。
此外 go clean 命令,可以用于将清除产生的可执行程序:
$ go clean # 不加参数,可以删除当前目录下的所有可执行文件
$ go clean sourcefile.go # 会删除对应的可执行文件
go 语言有一个褒贬不一的特性,就是对格式的要求很严格,我是很喜欢这个特性的,因为可以保持代码的清晰一致,编译组合开发,并且go还提供了一个非常强大的工具来格式化代码,它就是 go fmt sourcefile.go, 不过通常其实不需要我们手动调用,各种编辑器都可以帮助我们自动完成格式化。
go doc 命令可以方便我们快速查看包文档,go doc package 命令将会在终端中打印出指定 package 的文档。
如查看fmt文档
go doc fmt
另外有一个与 go doc 命令相关的命令是 godoc, 可以通过它启动我们自己的文档服务器:
godoc -http=:8080
然后我们就可与在浏览器localhost:8080中查看go文档了
godoc默认不带可执行程序,生成可执行程序步骤
git clone https://github.com/golang/tools golang.org/x/tools
go安装目录/src新建golang.org\x\tools目录 拷贝源代码到该目录
cd C:\Go\src\golang.org\x\tools\godoc
go build golang.org/x/tools/cmd/godoc
go install golang.org/x/tools/cmd/godoc 自动拷贝到bin目录
用来编译和安装go程序,我们可以将它与 build 命令对比:
生成的可执行文件路径 工作目录下的bin目录下 当前目录下
可执行文件的名字 与源码所在目录同名 默认与源程序同名,可以使用-o选项指定
依赖 将依赖的包放到工作目录下的pkg文件夹下 -
build | build | |
---|---|---|
生成的可执行文件路径 | 工作目录下的bin目录下 | 当前目录下 |
可执行文件的名字 | 与源码所在目录同名 | 默认与源程序同名,可以使用-o选项指定 |
依赖 | 将依赖的包放到工作目录下的pkg文件夹下 | - |
查看所有环境变量
go env
go env | grep GOROOT
修改环境变量(设置中国区代理或者阿里云代理:https://mirrors.aliyun.com/goproxy/)
go env -w GOPROXY=https://goproxy.cn
go get 命令可以借助代码管理工具通过远程拉取或更新代码包及其依赖包,并自动完成编译和安装。整个过程就像安装一个 App 一样简单。
这个命令可以动态获取远程代码包,目前支持的有 BitBucket、GitHub、Google Code 和 Launchpad。在使用 go get 命令前,需要安装与远程包匹配的代码管理工具,如 Git、SVN、HG 等,参数中需要提供一个包名。
这个命令在内部实际上分成了两步操作:第一步是下载源码包,第二步是执行 go install。下载源码包的 go 工具会自动根据不同的域名调用不同的源码工具,对应关系如下:
BitBucket (Mercurial Git)
GitHub (Git)
Google Code Project Hosting (Git, Mercurial, Subversion)
Launchpad (Bazaar)
所以为了 go get 命令能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的 PATH 中。其实 go get 支持自定义域名的功能。
参数介绍:
Go语言的代码被托管于 Github.com 网站,该网站是基于 Git 代码管理工具的,很多有名的项目都在该网站托管代码。其他类似的托管网站还有 code.google.com、bitbucket.org 等。
这些网站的项目包路径都有一个共同的标准,参见下图所示
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lZFb5vnB-1589278910682)(images/1.jpg)]
图中的远程包路径是 Go语言的源码,这个路径共由 3 个部分组成:
默认情况下,go get 可以直接使用。例如,想获取 go 的源码并编译,使用下面的命令行即可:
go get github.com/davyxu/cellnet
获取前,请确保 GOPATH 已经设置。Go 1.8 版本之后,GOPATH 默认在用户目录的 go 文件夹下。
cellnet 只是一个网络库,并没有可执行文件,因此在 go get 操作成功后 GOPATH 下的 bin 目录下不会有任何编译好的二进制文件。
需要测试获取并编译二进制的,可以尝试下面的这个命令。当获取完成后,就会自动在 GOPATH 的 bin 目录下生成编译好的二进制文件。
go get -u github.com/gpmgo/gopm
查看你的GOPATH/bin目录下是否生成gopm.exe,src是否有gpmgo源码。
Go语言的包借助了目录树的组织形式,一般包的名称就是其源文件所在目录的名称,虽然Go语言没有强制要求包名必须和其所在的目录名同名,但还是建议包名和所在目录同名,这样结构更清晰。
包可以定义在很深的目录中,包名的定义是不包括目录路径的,但是包在引用时一般使用全路径引用。比如在GOPATH/src/a/b/ 下定义一个包 c。在包 c 的源码中只需声明为package c,而不是声明为package a/b/c,但是在导入 c 包时,需要带上路径,例如import “a/b/c”。
包的习惯用法:
单行导入
import "包 1 的路径"
import "包 2 的路径"
多行导入
import (
"包 1 的路径"
"包 2 的路径"
)
别名
package main
import F "fmt"
func main() {
F.Println("C语言中文网")
}
省略引用
package main
import . "fmt"
func main() {
//不需要加前缀 fmt.
Println("C语言中文网")
}
标准的Go语言代码库中包含了大量的包,并且在安装 Go 的时候多数会自动安装到系统中。我们可以在 $GOROOT/src/pkg 目录中查看这些包。下面简单介绍一些我们开发中常用的包。
log 包中提供了三类日志输出接口,Print、Fatal 和 Panic。
除了go工具链自带的工具比如,go build 、go vet 、go get 、 go doc 等等,还有包依赖管理工具。比如 dep等等,go 1.11 1.12 还添加了 go modules 。
一直依赖go语言被人吐槽的就是包依赖管理 和 错误处理方式。 社区出现了一批包依赖管理工具。
两个概念:GOROOT 和GOPATH
依赖,分为内部依赖和外部依赖。
GOPATH和GOROOT,GOROOT并不是必须要设置的,但是GOPATH必须要设置,但并不是固定不变的。本项目内部依赖就会在GOPATH 所配置的路径下去寻找,编译器如果找不到会报错。总的来说内部依赖不需要太操心。
当我们要实现一些功能的时候,不可避免的需要一些第三方包,也统称为外部依赖包。go1.5之前只支持使用GOPATH来管理外部依赖包的,对比java的maven 和gradle等 简直不太方便。
在go1.5release之前,我们要管理多个依赖包版本时,只能通过设置多个GOPATH,拷贝代码来解决。比如,如果两个工程都依赖了Beego,一个1.5,一个1.8,那么必须设置俩GOPATH,并且还要记得切换。
go语言原生包缺陷:
能拉取源码的平台很有限,绝大多数依赖的是 github.com
不能区分版本,以至于令开发者以最后一项包名作为版本划分
依赖 列表/关系 无法持久化到本地,需要找出所有依赖包然后一个个 go get
只能依赖本地全局仓库(GOPATH/GOROOT),无法将库放置于局部仓库($PROJECT_HOME/vendor)
简单说,就是在你项目中多了一个vendor文件夹,go会把它默认作为GOPATH。让go编译时,优先从项目源码树根目录下的vendor目录查找代码(可以理解为切了一次GOPATH),如果vendor中有,则不再去GOPATH中去查找。
社区支持vendor的包管理库有很多,官方推荐的就有15种。
用的比较多的有dep(官方)、Godep、Govendor等等
go官方的包管理工具是dep,目前来看也是用的最多的,是官方建议使用的。
官方wiki各种对比: https://github.com/golang/go/wiki/PackageManagementTools
安装方式也比较简单,下载对应平台可执行文件:https://github.com/golang/dep/releases,拷贝到GOROOT/bin目录
在自己工作目录下,使用 dep 初始化会报错:
$ dep init
init failed: unable to detect the containing GOPATH: /home/zhongwei/work/my_project/go is not within a known GOPATH/src
也就是说项目开发定义在GOPATH目录才能使用
不希望将新项目的目录 my_project/go 加入到 GOPATH 中,觉得非常麻烦,另外看了consul源码发现都切换到了go module,此时只好放弃dep。
使用 go module 管理依赖后会在项目根目录下生成两个文件 go.mod 和 go.sum。
go.mod 中会记录当前项目的所依赖,文件格式如下所示:
module github.com/gosoon/audit-webhook
go 1.12
require (
github.com/elastic/go-elasticsearch v0.0.0
github.com/gorilla/mux v1.7.2
github.com/gosoon/glog v0.0.0-20180521124921-a5fbfb162a81
)
go.sum记录每个依赖库的版本和哈希值,文件格式如下所示:
github.com/elastic/go-elasticsearch v0.0.0 h1:Pd5fqOuBxKxv83b0+xOAJDAkziWYwFinWnBO0y+TZaA=
github.com/elastic/go-elasticsearch v0.0.0/go.mod h1:TkBSJBuTyFdBnrNqoPc54FN0vKf5c04IdM4zuStJ7xg=
github.com/gorilla/mux v1.7.2 h1:zoNxOV7WjqXptQOVngLmcSQgXmgk4NMz1HibBchjl/I=
github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gosoon/glog v0.0.0-20180521124921-a5fbfb162a81 h1:JP0LU0ajeawW2xySrbhDqtSUfVWohZ505Q4LXo+hCmg=
github.com/gosoon/glog v0.0.0-20180521124921-a5fbfb162a81/go.mod h1:1e0N9vBl2wPF6qYa+JCRNIZnhxSkXkOJfD2iFw3eOfg=
(1) go 版本 >= v1.11
(2) 设置GO111MODULE环境变量
要使用go module 首先要设置GO111MODULE=on,GO111MODULE 有三个值,off、on、auto,off 和 on 即关闭和开启,auto 则会根据当前目录下是否有 go.mod 文件来判断是否使用 modules 功能。无论使用哪种模式,module 功能默认不在 GOPATH 目录下查找依赖文件,所以使用 modules 功能时请设置好代理。
在使用 go module 时,将 GO111MODULE 全局环境变量设置为 off,在需要使用的时候再开启,避免在已有项目中意外引入 go module。
对于新建项目使用 go module:
go mod init github.com/作者/项目名称
构建项目
go build hello.go
首先需要使用 go mod vendor 将项目所有的依赖下载到本地 vendor 目录中然后进行编译.
如代码中添加import
package main
import . "fmt"
import "github.com/google/uuid"
func main() {
Println("helloword")
uuid:=uuid.New()
Println(uuid)
}
执行命令(自動下載到vendor目录)
F:\code\go\helloworld>go mod vendor
go: finding module for package github.com/google/uuid
go: downloading github.com/google/uuid v1.1.1
go: found github.com/google/uuid in github.com/google/uuid v1.1.1
go.mod中多了一行依赖
github.com/google/uuid v1.1.1
go.sum中多了
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
使用 Go 的其他包管理工具 godep、govendor、glide、dep 等都避免不了的问题,Go Modules 也是一样,但在go.mod中可以使用replace将特定的库替换成其他库:
replace (
golang.org/x/text v0.3.0 => github.com/golang/text v0.3.0
)
也可以使用阿里云的镜像站:
go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/
或者设置环境变量
export GOPROXY=https://mirrors.aliyun.com/goproxy/
package main
import "fmt"
type byte int8 //定义一个新的类型byte
type byteAlias = int8 //定义一个别名指向int8 实际类型还是int8
func main() {
var i, k int = 10, 100
var ife bool = false
i = 100
j := 100.5 //新变量根据值判断类型使用:= 必须要var关键字 后续赋值还是使用=
j = 100.6
fmt.Print(i + k)
fmt.Print(ife)
fmt.Println(j)
ks := &i //引用指向 改了ks就等于改了i
*ks = 10
fmt.Println(*ks, i)
const js int = 100 //常量
fmt.Println(js)
const (
a = iota //引用一次累加一次 第一次是0
b = iota //1
c = iota //2
)
fmt.Println(a, b, c)
var an byte = 1
fmt.Printf("%T\n", an) //类型是main.byte
var an1 byteAlias = 1
fmt.Printf("%T\n", an1) //类型是int8
}
package main
import "fmt"
/**
结构体类似于类
*/
type User struct {
userName string
userEmail string
userSex int
}
/**
定义接口,定义方法参数是string类型返回值是int类型
*/
type Phone interface {
call(string) int
}
/**
定义实现结构
*/
type Iphone struct {
}
/**
Iphone实现call方法
*/
func (iphone Iphone) call(message string) int {
fmt.Println("iphone说了:" + message)
return 1
}
func main() {
user := User{
"zs", "[email protected]", 0}
fmt.Println(user.userEmail)
user1 := User{
userEmail: "[email protected]"}
fmt.Println(user1.userEmail)
phone := new(Iphone)
phone.call("helloworld")
}
package main
import (
"fmt"
"strings"
)
func main() {
//字符串切割
str := "a_b_c"
var result []string = strings.Split(str, "_")
fmt.Println(result)
//字符串包含
fmt.Println(strings.Contains(str, "c"))
//字符串在另一个字符串位置 ,下标从0开始
fmt.Println(strings.Index(str, "_"))
//比较字符串 相等返回0 ab返回1
fmt.Println(strings.Compare(str, "a_b"))
//返回某个字符串出现次数
fmt.Println(strings.Count(str, "_"))
//找到最后一个匹配字符的位置
fmt.Println(strings.LastIndex(str, "_"))
//是否某个字符开头
fmt.Println(strings.HasPrefix(str, "a_"))
//是否某个字符结尾
fmt.Println(strings.HasSuffix(str, "a_"))
//join数组元素拼接成sep分格字符串
//[长度]定义指定长度数组,...根据值确定长度
var strlist = []string{
"a", "b"}
var strlist1 = [...]string{
"a", "b"}
var strlist2 [2]string
strlist2[0] = "zs"
strlist2[1] = "ls"
cc := strings.Join(strlist, ",")
fmt.Println(cc, strlist1)
//将某个字符串替换成目标字符串多少次
fmt.Println(strings.Replace(str, "_", "-", strings.Count(str, "_")))
//转换大小写
fmt.Println(strings.ToUpper(str), strings.ToLower(str))
//去除左右空格,去掉指定字符
fmt.Println(strings.TrimSpace(" a b "), strings.TrimLeft("_abc_", "_"))
//截取字符串 从开始索引到结束索引,包含开头不包含结尾。
fmt.Println(str[1:2])
}
package main
import "fmt"
func main() {
var k string = "a"
//初始化带值
var arr = [...]string{
"zs", "ls"}
arr1 := []string{
"zs", "ls"}
arr2 := [2]string{
"zs", "ls"}
fmt.Println(k, arr, arr1, arr2)
//只定义不初始化
var arr3 [3]string
arr3[0] = "zs"
//修改数组值
arr3[1] = "ls"
fmt.Println(arr3)
//获取数组值
fmt.Println(arr3[1])
//获取数组长度
fmt.Println(len(arr3))
//循环数组
for i := 0; i < len(arr3); i++ {
fmt.Println(i, arr3[i])
}
}
package main
import "fmt"
func main() {
//可以声明一个未指定大小的数组来定义切片:
var idList []int
//定义切片
numbers := []int{
0, 1, 2, 3, 4, 5, 6, 7, 8}
//追加numbers和10到一个新的切片中,bumbers本身不变
idList = append(numbers, 10)
fmt.Println(numbers)
fmt.Println(idList)
/* 创建切片 numbers1 是之前切片的两倍容量*/
numbers1 := make([]int, len(numbers), (cap(numbers))*2)
/* 拷贝 numbers 的内容到 numbers1 */
copy(numbers1, numbers)
fmt.Println(numbers1)
}
package main
import "fmt"
func main() {
//var map_variable map[key_data_type]value_data_type
kvs := map[string]string{
"id": "1", "name": "zs"}
fmt.Println(kvs)
a := 1
var b int = 10
for k, v := range kvs {
fmt.Println(k, v)
}
}
package main
import (
"container/list"
"fmt"
)
func main() {
//列表是一种非连续的存储容器,由多个节点组成,节点通过一些变量记录彼此之间的关系,列表有多种实现方法,如单链表、双链表等。
userList:=list.New()
userList.PushBack("zs")
userList.PushFront("ls")
fmt.Println(userList.Len())
for i:=userList.Front();i!=nil;i=i.Next(){
fmt.Println(i.Value)
}
}
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
in := bufio.NewReader(os.Stdin)
str, _, err := in.ReadLine()
if err != nil {
fmt.Println(err.Error())
}
if string(str) == "1" {
fmt.Print("boy")
} else {
fmt.Println("girl")
}
}
package main
import "fmt"
func main() {
//初始化带值
var arr = [...]string{
"zs", "ls"}
//循环数组
for i := 0; i < len(arr); i++ {
fmt.Println(i, arr[i])
}
//range类似于foreach 参数1是索引参数2是指,不需要某个值可以使用_ ,一般用于map类型
for index, value := range arr {
fmt.Println(index, value)
}
for _, value := range arr {
fmt.Println(value)
}
//条件循环,打印1-10所有奇数
i := 0
for i < 10 {
i++
if i%2 == 0 {
continue
}
fmt.Printf("%v ", i)
}
fmt.Println()
i = 0
LOOP:
for i < 10 {
i++
if i%2 == 0 {
goto LOOP //等价于continue,也可以在任意地方定义label 根据逻辑goto
}
fmt.Printf("%v ", i)
}
}
package main
import "fmt"
//func function_name( [parameter list] ) [返回值类型] {
func add(i int, j int) int {
return i + j
}
func calc(i int, j int) (int, int) {
return i + j, i - j
}
func main() {
fmt.Println(add(100, 34))
addresult, minusResult := calc(100, 34)
fmt.Println(addresult, minusResult)
}
package main
import (
"errors"
"fmt"
)
/**
error的接口定义
type error interface {
Error() string
}
*/
/**
一般可以在函数最后一个参数添加一个错误参数,通过errors.New创建
*/
func div(num1 int,num2 int) (int,error){
if(num2==0){
return 0,errors.New("除数不能为0")
}
return num1/num2,nil
}
/**
自定义异常,比如
*/
type Sex struct {
sex int;//性别只能为0和1
}
func (sex Sex) Error() string{
return fmt.Sprintf("性别字段%v只能为0和1", sex.sex)
}
func setSex(sex Sex)(string){
if(sex.sex!=1 && sex.sex !=0){
return sex.Error();
}
return ""
}
func main() {
result,err:=div(100,0)
if(err!=nil){
fmt.Println(err)
}else{
fmt.Println(result)
}
sex:=Sex{
2}
errorMsg:=setSex(sex)
fmt.Println(errorMsg)
}