Golang 基础语法
- Go is an open source programming language supported by Google
- Easy to learn and get started with
- Built-in concurrency and a robust standard library
- Growing ecosystem of partners, communities, and tools
安装
下载地址:https://go.dev/dl/
Linux
$ wget https://studygolang.com/dl/golang/go1.13.6.linux-amd64.tar.gz
$ tar -zxvf go1.13.6.linux-amd64.tar.gz
$ sudo mv go /usr/local/
$ go version
go version go1.13.6 linux/amd64
从 Go 1.11
版本开始,Go 提供了 Go Modules 的机制,推荐设置以下环境变量,第三方包的下载将通过国内镜像,避免出现官方网址被屏蔽的问题。
$ go env -w GOPROXY=https://goproxy.cn,direct
或在 ~/.profile
中设置环境变量
export GOPROXY=https://goproxy.cn
Windows
官网下安装包就好啦~
默认安装路径:C:\Program Files\Go
需要新建两个环境变量配置
- GOROOT ,这是 Go 环境所在目录的配置:
C:\Program Files\Go
- GOPATH ,这个是 Go 项目的工作目录
- 最好建两个,第一个是默认第三方包的下载位置,第二个放自己的代码
C:\Users\<user_name>\go
然后在 path 环境变量中新建 %GOROOT%\bin

基本结构
package main
import "fmt"
func main() {
fmt.Println("Hello World!")
}
运行
执行 go run main.go
或 go run .
,将会输出
$ go run .
Hello World!
go run main.go
,其实是 2 步:
- go build main.go:编译成二进制可执行程序
- ./main:执行该程序
如果强制启用了 Go Modules 机制,即环境变量中设置了 GO111MODULE=on,则需要先初始化模块 go mod init hello 否则会报错误:go: cannot find main module; see ‘go help modules’
或者直接编译生成可执行文件
go build main.go
# or
go build -o hello.exe ./main.go # 指定可执行文件名
基本结构
package main
:声明了 main.go 所在的包,Go 语言中使用包来组织代码。一般一个文件夹即一个包,包内可以暴露类型或方法供其他包使用。import “fmt”
:fmt 是 Go 语言的一个标准库/包,用来处理标准输入输出。func main
:main 函数是整个程序的入口,main 函数所在的包名也必须为main
。fmt.Println(“Hello World!”)
:调用 fmt 包的 Println 方法,打印出 “Hello World!”
一些命令
go build
这个命令主要用于编译代码。在包的编译过程中,若有必要,会同时编译与之相关联的包。
-
如果是普通应用包,执行后不会产生任何文件。如果你需要在
$GOPATH/pkg
下生成相应的文件,那就得执行go install
。 -
如果是
main
包,执行后会在当前目录下生成一个可执行文件。如果你需要在$GOPATH/bin
下生成相应的文件,需要执行go install
,或者使用go build -o 路径/a.exe
。 -
如果某个项目文件夹下有多个文件,而你只想编译某个文件,就可在
go build
之后加上文件名,例如go build a.go
;go build
命令默认会编译当前目录下的所有 go 文件。 -
你也可以指定编译输出的文件名。
- 例如
go build -o hello.exe ./main.go
- 例如
-
go build 会忽略目录下以 “_” 或 “.” 开头的 go 文件。
-
如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件:
array_linux.go, array_darwin.go, array_windows.go, array_freebsd.go
go build
的时候会选择性地编译以系统名结尾的文件(Linux、Darwin、Windows、Freebsd)。例如 Linux 系统下面编译只会选择 array_linux.go 文件,其它系统命名后缀文件全部忽略。
参数
-o
指定输出的文件名,可以带上路径,例如go build -o a/b/c
-i
安装相应的包,编译 +go install
-a
更新全部已经是最新的包的,但是对标准包不适用-n
把需要执行的编译命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的-p n
指定可以并行可运行的编译数目,默认是 CPU 数目-race
开启编译的时候自动检测数据竞争的情况,目前只支持 64 位的机器-v
打印出来我们正在编译的包名-work
打印出来编译时候的临时文件夹名称,并且如果已经存在的话就不要删除-x
打印出来执行的命令,其实就是和-n
的结果类似,只是这个会执行-ccflags 'arg list'
传递参数给 5c, 6c, 8c 调用-compiler name
指定相应的编译器,gccgo 还是 gc-gccgoflags 'arg list'
传递参数给 gccgo 编译连接调用-gcflags 'arg list'
传递参数给 5g, 6g, 8g 调用-installsuffix suffix
为了和默认的安装包区别开来,采用这个前缀来重新安装那些依赖的包,-race
的时候默认已经是-installsuffix race
,大家可以通过-n
命令来验证-ldflags 'flag list'
传递参数给 5l, 6l, 8l 调用-tags 'tag list'
设置在编译的时候可以适配的那些 tag
go clean
移除当前源码包和关联源码包里面编译生成的文件。这些文件包括
_obj/ 旧的object目录,由Makefiles遗留
_test/ 旧的test目录,由Makefiles遗留
_testmain.go 旧的gotest文件,由Makefiles遗留
test.out 旧的test记录,由Makefiles遗留
build.out 旧的test记录,由Makefiles遗留
*.[568ao] object文件,由Makefiles遗留
DIR(.exe) 由go build产生
DIR.test(.exe) 由go test -c产生
MAINFILE(.exe) 由go build MAINFILE.go产生
*.so 由 SWIG 产生
可以利用这个命令清除编译文件,然后 git 递交源码,在本机测试的时候这些编译文件都是和系统相关的,但是对于源码管理来说没必要。
$ go clean -i -n
cd /Users/astaxie/develop/gopath/src/mathapp
rm -f mathapp mathapp.exe mathapp.test mathapp.test.exe app app.exe
rm -f /Users/astaxie/develop/gopath/bin/mathapp
参数
-i
清除关联的安装的包和可运行文件,也就是通过 go install 安装的文件-n
把需要执行的清除命令打印出来,但是不执行,这样就可以很容易的知道底层是如何运行的-r
循环的清除在 import 中引入的包-x
打印出来执行的详细命令,其实就是-n
打印的执行版本
go get
这个命令是用来动态获取远程代码包的,目前支持的有 BitBucket、GitHub、Google Code 和 Launchpad。
这个命令在内部实际上分成了两步操作
- 下载源码包
- 执行
go install
下载源码包的 go 工具会自动根据不同的域名调用不同的源码工具,对应关系如下:
BitBucket (Mercurial Git)
GitHub (Git)
Google Code Project Hosting (Git, Mercurial, Subversion)
Launchpad (Bazaar)
所以为了 go get
能正常工作,你必须确保安装了合适的源码管理工具,并同时把这些命令加入你的 PATH 中。
参数
-d
只下载不安装-f
只有在你包含了-u
参数的时候才有效,不让-u
去验证 import 中的每一个都已经获取了,这对于本地 fork 的包特别有用-fix
在获取源码之后先运行 fix,然后再去做其他的事情-t
同时也下载需要为运行测试所需要的包-u
强制使用网络去更新包和它的依赖包-v
显示执行的命令
go install
这个命令在内部实际上分成了两步操作:第一步是生成结果文件(可执行文件或者 .a 包),第二步会把编译好的结果移到 $GOPATH/pkg
或者 $GOPATH/bin
。
参数支持 go build
的编译参数。只要记住一个参数 -v
就好了,这个随时随地的可以查看底层的执行信息。
go test
执行这个命令,会自动读取源码目录下面名为 *_test.go
的文件,生成并运行测试用的可执行文件。
输出的信息类似
ok archive/tar 0.011s
FAIL archive/zip 0.022s
ok compress/gzip 0.033s
...
默认的情况下,不需要任何的参数,它会自动把你源码包下面所有 test 文件测试完毕
参数
-bench regexp
执行相应的 benchmarks,例如-bench=.
-cover
开启测试覆盖率-run regexp
只运行 regexp 匹配的函数,例如-run=Array
那么就执行包含有 Array 开头的函数-v
显示测试的详细命令
go get 和 go install 职责的改变
在 1.16 之后,go install
被设计为“用于构建和安装二进制文件”, go get
则被设计为 “用于编辑 go.mod 变更依赖”,并且使用时,应该与 -d
参数共用,在将来版本中 -d
可能会默认启用;
基本上 go install <package>@<version>
是用于命令的全局安装,例如:go install sigs.k8s.io/kind@v0.9.0
;
而 go get
安装二进制的功能,后续版本将会删除,它主要被设计为修改 go.mod
追加依赖之类的,但还存在类似 go mod tidy
之类的命令,所以使用频率可能不会很高;
项目
GOPATH
go 命令依赖一个重要的环境变量:$GOPATH
Windows 系统中环境变量的形式为
%GOPATH%
GOPATH 允许多个目录,当有多个目录时,请注意分隔符,Windows 是分号,Linux 是冒号,当有多个 GOPATH 时,默认会将 go get 的内容放在第一个目录下
$GOPATH 目录有三个子目录:
- src 存放源代码(比如:.go .c .h .s 等)
- pkg 编译后生成的文件(比如:.a)
- bin 编译后生成的可执行文件
GOPATH 下的 src 目录就是接下来开发程序的主要目录,所有的源码都是放在这个目录下面。一般情况下,一个文件夹就是一个项目
- 例如: $GOPATH/src/mymath 表示 mymath 是个应用包或者可执行应用(根据 package 是 main 还是其他来决定,main 的话就是可执行应用)
- 允许多级目录,例如在 src 下面新建了目录 $GOPATH/src/github.com/astaxie/beedb 那么这个包路径就是 "github.com/astaxie/beedb",包名称是最后一个目录 beedb
文件结构举例
bin/
mathapp
pkg/
平台名/ 如:darwin_amd64、linux_amd64
mymath.a
github.com/
astaxie/
beedb.a
src/
mathapp
main.go
mymath/
sqrt.go
github.com/
astaxie/
beedb/
beedb.go
util.go
编写应用包例
cd $GOPATH/src
mkdir mymath
新建文件 sqrt.go,内容如下
// $GOPATH/src/mymath/sqrt.go源码如下:
package mymath
func Sqrt(x float64) float64 {
z := 0.0
for i := 0; i < 1000; i++ {
z -= (z*z - x) / (2 * x)
}
return z
}
这样 sqrt 应用包目录和代码已经新建完毕
建议 package 的名称和目录名保持一致
编译安装应用包
- 在任意的目录执行
go install [pkg_name]
- 进入对应的应用包目录,然后执行
go install
,即完成对应应用包的安装
在 pkg 目录可以看到安装好的应用包(.a 结尾)
编译程序
进入该应用目录,然后执行 go build
,将在此目录下生成一个同名可执行文件
进入该目录,执行 go install
,将在 $GOPATH/bin/ 下生成一个同名可执行文件
在命令行输入同名就能运行
Go Module
基本操作
初始化
go mod init [module 名称]
检测和清理依赖
go mod tidy
执行
go run XX.go
第一次执行时,go mod 会自动查找依赖自动下载
go module 安装 package 的原則是先拉最新的 release tag,若无 tag 则拉最新的 commit
go 会自动生成一个 go.sum 文件来记录 dependency tree
安装指定包
go get -v github.com/go-ego/gse@v0.60.0-rc4.2
检查可以升级的 package
go list -m -u all
下载依赖文件
go mod download
更新依赖
go get -u
更新指定包依赖:
go get -u github.com/go-ego/gse
指定版本:
go get -u github/com/go-ego/gse@v0.60.0-rc4.2
替换无法直接获取的 pkg
modules 可以通过在 go.mod 文件中使用 replace 指令替换成 github 上对应的库,比如:
replace (
golang.org/x/crypto v0.0.0-20190313024323-a1f597ede03a => github.com/golang/crypto v0.0.0-20190313024323-a1f597ede03a
)
使用七牛的镜像源
go env -w GOPROXY=https://goproxy.cn
数据类型
变量 Variable
Go 语言是静态类型的,变量声明时必须明确变量的类型。
Go 语言的类型在变量后面。
var a int // 如果没有赋值,默认为0
var a int = 1 // 声明时赋值
var a = 1 // 声明时赋值
var a = 1
,因为 1 是 int 类型的,所以赋值时,a 自动被确定为 int 类型,所以类型名可以省略不写,这种方式还有一种更简单的表达:
a := 1
msg := "Hello World!"
:=
只能用在函数内部;在函数外部使用则会无法编译通过,所以一般用var
方式来定义全局变量。
_
(下划线)是个特殊的变量名,任何赋予它的值都会被丢弃。在这个例子中,我们将值 35
赋予 b
,并同时丢弃 34
:
_, b := 34, 35
Go 对于已声明但未使用的变量会在编译阶段报错
常量
常量可定义为数值、布尔值或字符串等类型
const constantName = value
//如果需要,也可以明确指定常量的类型:
const Pi float32 = 3.1415926
// example
const Pi = 3.1415926
const i = 10000
const MaxThread = 10
const prefix = "astaxie_"
Go 常量和一般程序语言不同的是,可以指定相当多的小数位数 (例如 200 位), 若指定给 float32 自动缩短为 32bit,指定给 float64 自动缩短为 64bit
内置基本类型
Go 支持如下内置基本类型:
- 一种内置布尔类型:
bool
。 - 11 种内置整数类型:
int8
、uint8
、int16
、uint16
、int32
、uint32
、int64
、uint64
、int
、uint
和uintptr
。 - 两种内置浮点数类型:
float32
和float64
。 - 两种内置复数类型:
complex64
和complex128
。 - 一种内置字符串类型:
string
。
内置类型也称为预声明类型
除了 bool
和 string
类型,其它的 15 种内置基本类型都称为数值类型(整型、浮点数型和复数型)。
Go 中有两种内置类型别名(type alias):
byte
是uint8
的内置别名。 它们是同一个类型。rune
是int32
的内置别名。 它们是同一个类型。
任一个类型的所有值的尺寸都是相同的,所以一个值的尺寸也常称为它的类型的尺寸。
uintptr
、int
以及 uint
类型的值的尺寸依赖于具体编译器实现。
- 通常地,在 64 位的架构上,
int
和uint
类型的值是 64 位的; - 在 32 位的架构上,它们是 32 位的。
- 编译器必须保证
uintptr
类型的值的尺寸能够存下任意一个内存地址。
一个 complex64
复数值的实部和虚部都是 float32
类型的值。 一个 complex128
复数值的实部和虚部都是 float64
类型的值。
复数的形式为 RE + IMi
,其中 RE
是实数部分,IM
是虚数部分,而最后的 i
是虚数单位。下面是一个使用复数的例子:
var c complex64 = 5+5i
fmt.Printf("Value is: %v", c) //output: (5+5i)
在内存中,所有的浮点数都使用 IEEE-754 格式存储。
从逻辑上说,一 个字符串值表示一段文本。 在内存中,一个字符串存储为一个字节序列。 此字节序列体现了此字符串所表示的文本的 UTF-8 编码形式。
字符串
//示例代码
var frenchHello string // 声明变量为字符串的一般方法
var emptyString string = "" // 声明了一个字符串变量,初始化为空字符串
func test() {
no, yes, maybe := "no", "yes", "maybe" // 简短声明,同时声明多个变量
japaneseHello := "Konichiwa" // 同上
frenchHello = "Bonjour" // 常规赋值
}
utf-8
在 Go 语言中,字符串使用 UTF8 编码
UTF8 的好处在于,如果基本是英文,每个字符占 1 byte,和 ASCII 编码是一样的,非常节省空间,如果是中文,一般占 3 字节。包含中文的字符串的处理方式与纯 ASCII 码构成的字符串有点区别。
package main
import (
"fmt"
"reflect"
)
func main() {
str1 := "Golang"
str2 := "Go语言"
fmt.Println(reflect.TypeOf(str2[2]).Kind()) // uint8
fmt.Println(str1[2], string(str1[2])) // 108 l
fmt.Printf("%d %c\n", str2[2], str2[2]) // 232 è
fmt.Println("len(str2):", len(str2)) // len(str2): 8
}
reflect.TypeOf().Kind()
可以知道某个变量的类型,我们可以看到,字符串是以 byte 数组形式保存的,类型是 uint8,占 1 个 byte,打印时需要用 string 进行类型转换,否则打印的是编码值。- 因为字符串是以 byte 数组的形式存储的,所以,
str2[2]
的值并不等于语
。str2 的长度len(str2)
也不是 4,而是 8(Go
占 2 byte,语言
占 6 byte)。
特性
使用 +
操作符来连接两个字符串:
s := "hello,"
m := " world"
a := s + m
fmt.Printf("%s\n", a)
修改字符串也可写为:
s := "hello"
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
fmt.Printf("%s\n", s)
使用反引号来声明多行字符串(没有字符转义)
m := `hello
world`
处理中文
正确的处理方式是将 string 转为 rune 数组
package main
import (
"fmt"
"unicode/utf8"
)
func main() {
var str = "hello 你好"
//golang中string底层是通过byte数组实现的,座椅直接求len 实际是在按字节长度计算 所以一个汉字占3个字节算了3个长度
fmt.Println("len(str):", len(str))
//以下两种都可以得到str的字符串长度
//golang中的unicode/utf8包提供了用utf-8获取长度的方法
fmt.Println("RuneCountInString:", utf8.RuneCountInString(str))
//通过rune类型处理unicode字符
fmt.Println("rune:", len([]rune(str)))
}
转换成 []rune
类型后,字符串中的每个字符,无论占多少个字节都用 int32 来表示,因而可以正确处理中文。
byte
表示一个字节,可以 表示英文字符等占用一个字节的字符,占用多于一个字节的字符就无法正确表示,例如占用 3 个字节的汉字rune
表示一个字符,用来表示任何一个字符
修改字符串
在 Go 中字符串是不可变的,如果真的想要改:
s := "hello"
c := []byte(s) // 将字符串 s 转换为 []byte 类型
c[0] = 'c'
s2 := string(c) // 再转换回 string 类型
fmt.Printf("%s\n", s2)
// or
s := "hello"
s = "c" + s[1:] // 字符串虽不能更改,但可进行切片操作
fmt.Printf("%s\n", s)
错误类型
Go 内置有一个 error
类型,专门用来处理错误信息,Go 的 package
里面还专门有一个包 errors
来处理错误:
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
fmt.Print(err)
}
数据类型之间的转换
转换数据类型的方式很简单。
valueOfTypeB = typeB(valueOfTypeA)
例如:
// 浮点数
a := 5.0
// 转换为int类型
b := int(a)
Go 允许在底层结构相同的两个类型之间互转。例如:
// IT类型的底层是int类型
type IT int
// a的类型为IT,底层是int
var a IT = 5
// 将a(IT)转换为int,b现在是int类型
b := int(5)
// 将b(int)转换为IT,c现在是IT类型
c := IT(b)
但注意:
- 不是所有数据类型都能转换的,例如字母格式的 string 类型 "abcd" 转换为 int 肯定会失败
- 低精度转换为高精度时是安全的,高精度的值转换为低精度时会丢失精度。例如 int32 转换为 int16,float32 转换为 int
- 这种简单的转换方式不能对 int(float) 和 string 进行互转,要跨大类型转换,可以使用
strconv
包提供的函数
数组 array
- 数组是值。将一个数组赋予另一个数组会复制其所有元素。
- 特别地,若将某个数组传入某个函数,它将接收到该数组的一份副本而非指针。
- 数组的大小是其类型的一部分。类型
[10]int
和[20]int
是不同的。
var arr [n]type
声明数组
var arr [5]int // 一维
var arr2 [5][5]int // 二维
var arr = [5]int{1, 2, 3, 4, 5} // 声明时初始化
使用 :=
声明
a := [3]int{1, 2, 3}
b := [10]int{1, 2, 3} // 前三个元素初始化为1、2、3,其它默认为0
c := [...]int{4, 5, 6} // 可以省略长度而采用`...`的方式,Go会自动根据元素个数来计算长度
声明嵌套数组
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有4个int类型的元素
doubleArray := [2][4]int{[4]int{1, 2, 3, 4}, [4]int{5, 6, 7, 8}}
// 上面的声明可以简化,直接忽略内部的类型
easyArray := [2][4]int{{1, 2, 3, 4}, {5, 6, 7, 8}}
使用 []
索引/修改数组
arr := [5]int{1, 2, 3, 4, 5}
for i := 0; i < len(arr); i++ {
arr[i] += 100
}
fmt.Println(arr) // [101 102 103 104 105]
由于长度也是数组类型的一部分,因此
[3]int
与[4]int
是不同的类型,数组也就不能改变长度。数组之间的赋值是值的赋值,即当把一个数组作为参数传入函数的时候,传入的其实是该数组的副本,而不是它的指针。要传指针得使用
slice
切片 slice
数组的长度不能改变,如果想拼接 2 个数组,或是获取子数组,需要使用切片
切片通过对数组进行封装,为数据序列提供了更通用、强大而方便的接口。 除了矩阵变换这类需要明确维度的情况外,Go 中的大部分数组编程都是通过切片来完成的。
Go 和 Python 的切片在底层实现上完全不同
python 的切片产生的是新的对象,对新对象的成员的操作不影响旧对象;
go 的切片产生的是旧对象一部分的引用,对其成员的操作会影响旧对象。
这其实也体现了脚本语言和编译语言的不同。虽然两个语言都有类似的切片操作;但是 python 主要目标是方便;go 主要目标却是快速(并弥补丢弃指针运算的缺陷)