Go Base
blog.golang.org
pkg.go.dev
文档-Go语言
编程语言规范
Download
https://golang.google.cn/dl/
windows 三步骤
- GOROOT
设置环境变量,定义go安装程序的目录位置。例如:c:/go
- GOPATH
设置环境变量,定义一个全局的包安装目录(目录自定义,最好与GOROOT区分开)
- PATH
设置go的环境变量,目录为GOROOT/bin

Go 命令
项目目录:

报错内容:
GOROOT=C:\All\Tools\Go #gosetup
GOPATH=F:\workSpace\htdosc_go\study\src;C:\All\Tools\GOPATH #gosetup
C:\All\Tools\Go\bin\go.exe build -o C:\Users\admin-mxp\AppData\Local\Temp\___go_build_main_go.exe F:/workSpace/htdosc_go/study/src/main.go #gosetup
main.go:4:8: cannot find package "project1/hello" in any of:
C:\All\Tools\Go\src\project1\hello (from $GOROOT)
F:\workSpace\htdosc_go\study\src\src\project1\hello (from $GOPATH)
C:\All\Tools\GOPATH\src\project1\hello
错误原因:
GOPATH=F:\workSpace\htdosc_go\study\src;
修改为
GOPATH=F:\workSpace\htdosc_go\study;
编译 && 安装到GOOPATH的bin目录下

Go 指南
变量,函数, 类型
函数
func add(x int, y int) int {
return x + y
}
// 当2个甚至多个参数都是同类型,则除了最后一个类型,其余都可用省略
func add(x, y int) int {
return x + y
}
// 函数可用返回多个值,已逗号[,]隔离
func add(x, y int) (int, int) {
return x + y, x, y
}
a, b := add(1, 5)
命名的返回值
// go的返回值可以被命名,就像变量那样使用。返回值的名称应该具有一定的意义,可以作为文档使用。
// 没有参数的return 语句返回结果的当前值。也就是`直接`返回。
// 直接返回语句仅应当用在像下面这样的短函数中,在长函数中使用它们会影响代码的可读性
func split(sum int) (x, y int) {
x = sum * 4 / 9
y = sum - x
return
}
//a1, b1 := split(15)
//fmt.Println(a1, b1)
fmt.Println(split(15))
变量
// var 语句定义一个变量列表;跟函数的参数列表一样,类型在后
// var 语句可以定义在包或函数级别
var c, python bool
func main() {
var i int
fmt.Println(i, c, python)
}
// 变量可以包含初始值,如果初始化是使用表达式,则可以省略类型;变量从初始值中获取类型
var j int = 1
var php, golang bool = false, true
var java = "no" // 省略类型
`:=` 短声明变量
// 在函数中, `:=`简洁赋值语句在明确类型的地方,可以用于替代var定义。
// 函数外的每个语句都必须以关键字开始(`var`、`func`、等等),`:=` 结构不能使用在函数外
func test() {
d, e := 1, 2
fmt.Println(d, e, java)
// 变量交换
var d1, e1 = d, e
fmt.Println(d1, e1)
}
基本类型
?bool
string
int, uint // 分别对应特定 CPU 平台的字长(机器字大小)
int8 int16 int32 int64 //8、16、32、64 bit(二进制位)大小的有符号整数
uint8 uint16 uint32 uint64 //无符号整数类型
uintptr //它没有指定具体的 bit 大小但是足以容纳指针。uintptr 类型只有在底层编程时才需要,特别是Go语言和C语言函数库或操作系统接口相交互的地方。
//byte类型一般用于强调数值是一个原始的数据而不是一个小的整数。
byte // uint8 的别名
rune // int32 的别名
// 代表一个Unicode码
float32 可以提供大约 6 个十进制数的精度
float64 可以提供约 15 个十进制数的精度
通常优先使用 float64 类型
//常量 math.MaxFloat32 表示 float32 能取到的最大数值,大约是 3.4e38;
//常量 math.MaxFloat64 表示 float64 能取到的最大数值,大约是 1.8e308;
//float32 和 float64 能表示的最小值分别为 1.4e-45 和 4.9e-324。
// 复数是由两个浮点数表示的,其中一个表示实部(real),一个表示虚部(imag)
complex64(32 位实数和虚数)
complex128(64 位实数和虚数). 其中 complex128 为复数的默认类型。
Go语言中有符号整数采用 2 的补码形式表示,也就是最高 bit 位用来表示符号位,一个 n-bit 的有符号数的取值范围是从 -2(n-1) 到 2(n-1)-1。
无符号整数的所有 bit 位都用于表示非负数,取值范围是 0 到 2n-1。
例如,int8 类型整数的取值范围是从 -128 到 127,而 uint8 类型整数的取值范围是从 0 到 255。
零值
// 变量在定义时没有明确的初始化时会赋值为_零值_。
//零值是:
// 数值类型为 `0`,
// 布尔类型为 `false`,
// 字符串为 `""`(空字符串)
var i float64
var j int
var s string
fmt.Printf("%v %v %q\n", i, j, s)
// 结果:0 0 ""
类型转换
// 必须显式转换
var i int = 42
var f float64 = float64(i)
var u uint = uint(f)
// OR
i := 42
f := float64(i)
u := uint(f)
// 与 C 不同的是 Go 的在不同类型之间的项目赋值时需要显式转换。 试着移除例子中 float64 或 int 的转换看看会发生什么。
类型推导
//在定义一个变量但不指定其类型时, 变量的类型由右值推导得出。
var i int
j := i // j 也是一个 int
// 右边包含了未指名类型的数字常量时, 取决于常量
i := 42 // int
f := 3.142 // float64
g := 0.867 + 0.5i // complex128
常量
const 关键字:可以是字符,字符串,布尔或数字类型的值,常量类型不能用短声明变量【:=】语法定义
// 数值常量
const small = 1 >> 100
// 常量
const World = "世界"
流程控制语句
for
sum := 0
for i:=0; i< 10; i++ {
sum += 1
}
// 可以让前置、后置语句为空
for sum < 100 {
sum += sum
}
// for 是 Go 的 “while”
//死循环 ,省略了循环条件,循环就不会结束
for {}
if
var x int = -1
if x < 0 {
fmt.Println("jj")
}
// 跟for一样,if语句可以在条件之前执行一个简单的语句
var x, y float64 = 3, 1
var lim = 10.
if v := math.Pow(x, y); v < lim {
fmt.Println(v)
}
switch
一个结构体(struct
)就是一个字段的集合
除非以 fallthrough
语句结束,否则分支会自动终止。
fmt.Print("GO RUNS ON ")
switch os := runtime.GOOS; os {
case "linux":
fmt.Printf("linux.")
case "WINDOWS":
fmt.Printf("windows")
default:
fmt.Printf("%s.", os)
}
// 结果: GO RUNS ON windows.
执行顺序
switch 的条件从上到下的执行,当匹配成功的时候停止。
没有条件的 switch
同 switch true
一样。 这一构造使得可以用更清晰的形式来编写长的 if-then-else 链。
var t int = time.Now().Hour()
println(t) //16
switch {
case t < 17:
fmt.Println("afternoon")
case t < 12:
fmt.Println("morning")
default:
fmt.Println("evening")
}
defer
defer语句会延迟函数的执行直到上层函数返回。
延迟调用的参数会立刻生成,但是在上层函数返回前函数都不会被调用。
func test() {
defer fmt.Println("world") // 被延迟
fmt.Println("hello") // 先执行
}
延迟的函数调用被压入一个栈中 。 当函数返回时, 会按照后进先出的顺序调用被延迟的函数调用。
复杂类型
指针
Go 具有指针。 指针保存了变量的内存地址 。
// 类型 `*T` 是指向类型 `T` 的值的指针。其零值是 `nil`。
var p *int
// & 符号会生成一个指向其作用对象的指针。
i := 42
p = &i
// * 符号表示指针指向的底层的值。
fmt.Println(*p) // 通过指针 p 读取 i
*p = 21 // 通过指针 p 设置 i
fmt.Println(*p)
i = 99
fmt.Println(*p)
//这也就是通常所说的“间接引用”或“非直接引用”。
//与 C 不同,Go 没有指针运算
结构体
一个结构体(struct
)就是一个字段的集合。 (type
的含义跟其字面意思相符)
package main
import "fmt"
type Vertex struct {
X int
Y int
}
func main() {
fmt.Println(Vertex{1, 2})
}
// 输出: {1 2}
结构体字段使用点号来访问
func main() {
v := Vertex{1, 2}
v.X = 4
fmt.Println(v.X, v.Y)
}
// 输出: 4, 2
结构体指针:结构体字段可以通过结构体指针来访问。通过指针间接的访问是透明的。
func main() {
v := Vertex{1, 2}
p := &v
p.X = 1e9
fmt.Println(*p) // 等同于fmt.Println(v)
}
// 输出:{1000000000 2}
结构体文法
结构体文法:表示通过结构体字段的值作为列表来新分配一个结构体。
使用Name
语法可以仅列出部分字段。(字段名的顺序无关)。
特殊的前缀&
返回一个指向结构体的指针
var (
v1 = Vertex{1, 2} // 类型为 Vertex
v2 = Vertex{X: 1} // Y:0被省略
v3 = Vertex{} // X:0 和 Y:0 省略
p = &Vertex{1, 2} // 类型为 *Vertex
)
func main() {
fmt.Println(v1, p)
fmt.Println(v2, v3)
}
// 输出: {1 2} &{1 2}
// {1 0} {0 0}
数组
类型 [n]T
是一个有 n
个类型为 T
的值的数组。 example:var a [10]int
,定义变量a 是一个有10个整数的数组
数组的长度是其类型的一部分,因此数组不能改变大小。这看起来是一个制约,但是不用担心,Go提供了更加方便的方式来使用数组。
func main() {
var a [2]string
a[0] = "hello"
a[1] = "world"
fmt.Println(a[0], a[1])
fmt.Println(a)
}
// 输出: hello world
// [hello world]
slice(切片)
了解更多关于slice
的内容,可以阅读官方文章 slice: 用法和内部原理
切片是数组段的描述符。它由一个指向数组的指针,段的长度及其容量(段的最大长度)组成 。 长度是切片所引用的元素数 。 容量是基础数组中元素的数量(从切片指针所指的元素开始)
切片(slice)是对数组的一个连续片段的引用,所以切片是一个引用类型 (有点像Python的list
类型)。这个片段可以是整个数组,也可以是由起始和终止索引标识的一些项的子集。注意:终止索引标识的项不包含在切片内。
一个 slice 会 指向一个序列的值,并且包含了长度信息 。 []T
是一个元素类型为 T
的 slice。
func main() {
p := []int{2, 3}
fmt.Println("p ==", p)
for i:= 0; i < len(p); i++ {
fmt.Println("p[%d] == %d\n", i, p[i])
}
}
/* 输出:
p == [2 3]
p[%d] == %d
0 2
p[%d] == %d
1 3
*/
// 也可以对数组进行切片处理
var s [4]int
s[0] = 1
s[1] = 22
s[2] = 44
s[3] = 99
fmt.Println("s[0:2]", s[0:2])
// 输出:s[0:2] [1 22]
对slice
切片
slice
可以重新切片,创建一个新的slice
值来指向相同的数组。 表达式:s[lo:hi]
从开始lo
到结束hi -1
的slice
元素,含两端。
s[lo:lo]
表示空s[lo:lo+1]
表示有一个元素s[0:0]
表示切片复位, 等效于空切片
p := []int{11, 32, 3, 555}
p[1:] // 从上标到len(p)结束
p[:]
p[:2] // 从0开始
构造slice
slice
有函数make
创建。这会分配一个零长度的数组并返回<u>一个slice
指向这个数组</u>(引用该数组的切片);
func make([] T,len,cap)[] T
// 其中T代表要创建的切片的元素类型。该make函数具有类型,长度和可选容量(Capacity)
a :=make([]int ,5) //如果省略Capacity参数,则默认为指定的长度
fmt.Println(a, len(a), cap(a)) // [0 0 0 0 0] 5 5
//为了指定容量,可传递第三个参数到 `make`:
b := make([]int, 0, 5)
fmt.Println(b, len(b), cap(b)) //[] 0 5
c := b[:2]
fmt.Println(c, len(c), cap(c)) //[0 0] 2 5
// cap(x) :the maximum length the slice can reach when resliced
d := c[1:cap(c)]
fmt.Println(d, len(d), cap(d)) //[0 0 0 0] 4 4
slice
的零值是nil
。一个nil
的slice
的长度和容积是0
var z []int
fmt.Println(z, len(z), cap(z))
if z == nil {
fmt.Println("nil!")
}
//输出
/*
[] 0 0
nil!
*/
向 slice 添加元素
向 slice 添加元素: 添加元素是一种常见的操作,因此 Go 提供了一个内建函数 append
。
func append(s [], elems ...T) []T
// append 的第一个参数 s 是一个类型为 T 的数组,其余类型为 T 的值将会添加到 slice
// 如果 s 的底层数组太小,而不能容纳所有值时,会分配一个更大的数组。返回的 slice 会指向这个新分配的数组。
p := []int{2, 3, 51, 44, 532}
p1 := []int{1, 3, 34}
p = append(p, p1...) // 等同于append(p, p1[0], p1[1], p1[2])
fmt.Println(p) // [2 3 51 44 532 1 3 34]
var a []int
a1 = append(a, 0)
fmt.Println(a) // [0]
range
for
循环的 range
格式可以对 slice 或者 map 进行迭代循环 。
var pow = []int{1, 2, 4, 8, 16, 32}
for i, v := range pow {
fmt.Println("2**%d = %d\n", i, v)
}
/*
2**%d = %d
0 1
2**%d = %d
1 2
....
*/
可以通过赋值给 _
来忽略序号和值。 如果只需要索引值,去掉“, value”的部分即可。
pow := make([]int, 10)
for i := range pow {
pow[i] = 1 << uint(i)
}
for _, value := range pow {
fmt.Printf("%d\n", value)
}
map
map
映射键到值。
map
在使用之前必须用make
而不是new
来创建;值为nil
的map
是空的,并且不能赋值。
type Vertex struct {
lat, long float64
}
var m map[string]Vertex
m = make(map[string]Vertex)
m["Bei Jin"] = Vertex{
lat: 40.68433,
long: 74.46454,
}
fmt.Println(m)
fmt.Println(m["Bei Jin"])
map
的文法
map的文法与结构体文法类型,不过必须有键名
type Vertex struct {
lat, long float64
}
var m = map[string]Vertex{
"Bei Jin": {
lat: 40.68433,
long: -74.46454,
},
"Chang Zhou": {
lat: 37.4512,
long: -122.08408,
},
}
fmt.Println(m)
fmt.Println(m["Bei Jin"])
修改map
m[key] = elem
elem = m[key]
delete(m, key)
var elem, ok = m[key]
如果 key
在 m
中,ok
为 true 。否则, ok
为 false
,并且 elem
是 map 的元素类型的零值
同样的,当从 map 中读取某个不存在的键时,结果是 map 的元素类型的零值。
m := make(map[string]int)
m["key"] = 12
fmt.Println("插入元素", m) // map[key:12]
m["key"] = 99
fmt.Println("修改元素", m) // map[key:99]
a := m["key"]
fmt.Println("获取元素", a) // 99
delete(m, "key")
fmt.Println("删除元素", m) // map[]
var elem, ok = m["key"]
fmt.Println("检测某个键存在",elem, ok) // 0 false
函数值
函数也是值
hook := func(x, y int) int {
return (x + y) * 2
}
fmt.Println(hook(1, 3))
函数的闭包
Go 函数可以是闭包的。闭包是一个函数值,它来自函数体的外部的变量引用。 函数可以对这个引用值进行访问和赋值;换句话说这个函数被“绑定”在这个变量上。
// 例如,函数 adder 返回一个闭包。每个闭包都被绑定到其各自的 sum 变量上。
func adder() func(int) int {
sum :=0
return func(x int) int {
sum = 2 * x
return sum
}
}
func main() {
pos := adder()
fmt.Println(pos(99))
}
方法和接口
学习如何为类型定义方法;如何定义接口;以及如何将所有内容贯通起来。
Go 没有类。然而,仍然可以在结构体类型上定义方法
方法接收者 出现在func
关键字和方法名之间的参数中
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func main() {
v:= &Vertex{3, 4} // &{3 4}
fmt.Println(v.Abs()) // 5
}
你可以对包中的 <u>任意 类型定义任意方法</u>,而不仅仅是针对结构体。
但是,不能对来自其他包的类型或基础类型定义方法。
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
func main() {
v:= MyFloat(-math.Sqrt2)
fmt.Println(v) //-1.4142135623730951
fmt.Println(v.Abs()) //1.4142135623730951
}
接收者为指针的方法
方法可以与命名类型或命名类型的指针关联。
刚刚看到的两个 Abs
方法。一个是在 *Vertex
指针类型上,而另一个在 MyFloat
值类型上。 有两个原因需要使用指针接收者。首先避免在每个方法调用中拷贝值(如果值类型是大的结构体的话会更有效率)。其次,方法可以修改接收者指向的值。
尝试修改 Abs
的定义,同时 Scale
方法使用 Vertex
代替 *Vertex
作为接收者。
当 v
是 Vertex
的时候 Scale
方法没有任何作用。Scale
修改 v
。当 v
是一个值(非指针),方法看到的是 Vertex
的副本,并且无法修改原始值。
Abs
的工作方式是一样的。只不过,仅仅读取 v
。所以读取的是原始值(通过指针)还是那个值的副本并没有关系。
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
func (v *Vertex) Scale(f float64) {
v.X = v.X * f
v.Y = v.Y * f
}
func main() {
v:= &Vertex{3, 4}
v.Scale(5.)
fmt.Println(v) // &{15, 20}
fmt.Println(v.Abs()) // 25
}
接口
接口类型是由一组方法定义的集合。
接口类型的值可以存放实现这些方法的任何值。
`type 接口类型名 interface{
方法名1( 参数列表1 ) 返回值列表1
方法名2( 参数列表2 ) 返回值列表2
}`
type MyFloat float64
func (f MyFloat) Abs() float64 {
if f < 0 {
return float64(-f)
}
return float64(f)
}
type Vertex struct {
X, Y float64
}
func (v *Vertex) Abs() float64 {
return math.Sqrt(v.X*v.X + v.Y*v.Y)
}
// 接口定义
type Abser interface {
Abs() float64
}
func main() {
var a Abser
f := MyFloat(-math.Sqrt2)
v := Vertex{3, 4}
a = f // a MyFloat 实现了 Abser
a = &v // a *Vertex 实现了Abser
/*
下面一行, v 是一个Vertex(而不是 *Vertex)
所以没有实现 Abser
*/
//a = v
fmt.Println(a.Abs())
}
隐式接口
类型通过实现那些方法来实现接口。 没有显式声明的必要;所以也就没有关键字“implements“。
隐式接口解藕了实现接口的包和定义接口的包:互不依赖。
因此,也就无需在每一个实现上增加新的接口名称,这样同时也鼓励了明确的接口定义。
Stringers
一个普遍存在的接口是fmt
包中定义的Stringer
。 Stringer
是一个可以用字符串描述自己的类型 。
`type Stringer struct {
String() string
}`
type Person struct {
Name string
Age int
}
// 我目前的理解是自定义的Person类型String 覆盖了包 fmt 中定义的String
func (p Person) String() string {
return fmt.Sprintf("%v (%v years)", p.Name, p.Age)
}
func main() {
a := Person{"mp", 42}
z := Person{"xx", 12}
fmt.Println(a, z)
}
错误
Go程序使用erro
值来表示错误状态。与fmt.String
类似,error
类型是一个内建接口;
`type error interface{
Error() string
}`
通常函数会返回一个 error
值,调用的它的代码应当判断这个错误是否等于 nil
, 来进行错误处理。 error
为 nil 时表示成功;非 nil 的 error
表示错误。
type Myerror struct {
When time.Time
What string
}
func (e *Myerror) Error() string {
return fmt.Sprintf("at %v, %s", e.When, e.What)
}
func run() error {
return &Myerror{time.Now(), "it didn't work."}
}
func main() {
//hello.Test("b.go is package main.")
err := run()
if err != nil {
fmt.Println(err)
}
}
并发
作为语言的核心部分,Go 提供了并发的特性。
这一部分 概览了 goroutein 和 channel,以及如何使用它们来实现不同的并发模式。
goroutine
goroutine 是由 Go 运行时环境管理的轻量级线程。 goroutine 在相同的地址空间中运行,因此访问共享内存必须进行同步。sync
提供了这种可能,不过在 Go 中并不经常用到,因为有其他的办法
go f(x, y, z)
channel
channel 是有类型的管道,可以用 channel 操作符 <-
对其发送或者接收值
ch <- v // 将 v 送入 channel ch。
v := <-ch // 从 ch 接收,并且赋值给 v。
channel 使用前必须创建: ch := make(chan int)
channel 可以是 _带缓冲的_。为 make
提供第二个参数作为缓冲长度来初始化一个缓冲 channel: ch := make(chan int, 100)
。
向缓冲 channel 发送数据的时候,只有在缓冲区满的时候才会阻塞。当缓冲区清空的时候接受阻塞。
注意: 只有发送者才能关闭 channel,而不是接收者。
func test(n int, c chan int) {
x, y := 0, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
//hello.Test("b.go is package main.")
c := make(chan int, 10)
go test(cap(c), c)
//循环不断从channel中接收值,直到它关闭
for i := range c{
fmt.Println(i)
}
// 发送完后, 检查channel通道是否关闭
v, ok := <-c
fmt.Println(v, ok) // 0 , false
}
select
没弄明白原理
select
语句使得一个 goroutine 在多个通讯操作上等待。 select
会阻塞,直到条件分支中的某个可以继续执行,这时就会执行那个条件分支 。
为了非阻塞的发送或者接收,可使用 default
分支 。 当 select
中的其他条件分支都没有准备好的时候,default
分支会被执行。
func fib(c, quit chan int) {
var x, y = 0, 1
for {
select {
case c <- x:
x, y = y, x+y
case <- quit:
fmt.Println("quit")
return
}
}
}
func main() {
//hello.Test("b.go is package main.")
c := make(chan int)
quit := make(chan int)
go func() {
for i := 0; i < 10; i++ {
fmt.Println(<-c)
}
quit <- 0
}()
fib(c, quit)
}