# Go学习资料
- Go语言高级编程 (opens new window)
- Go语言圣经 (opens new window)
- Go Web编程 (opens new window)
- Go的学习线路 (opens new window)
# 一、Go环境搭建
安装go环境
官方下载环境包直接next走就行
查看go的安装目录
# 其中GOROOT即为安装环境,GOPATH即GO项目工作目录 go env1
2
下载Go开发工具
我才用的是利用已有的Java Idea安装go插件,出现无法配置Go SDK的情况
- 解决(注意标点格式):https://www.jianshu.com/p/4dd25a1744aa
选择SDK-新建项目-新建main包

# 二、Go基础知识
# 25个关键字
break default func interface select
case defer go map struct
chan else goto package switch
const fallthrough if range type
continue for import return var
2
3
4
5
- 定义接口
- 定义结构体
- 定义新类型
- 定义类型的别名
- 定义函数的类型:函数式编程,感觉有点像接口
func main() { // 将返回值直接作为函数调用 res1 := fun1() fmt.Println(res1(10,20)) } type my_fun func (int,int)(string) //fun1()函数的返回值是my_func类型 func fun1 () my_fun{ fun := func(a,b int) string { s := strconv.Itoa(a) + strconv.Itoa(b) return s } return fun }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 内置函数
- len:获取长度
- make:用于内建类型(
map、slice和channel)的内存分配 - new:用于各种类型的内存分配
# 变量声明
变量声明
- 普通变量:
var关键字是 Go 最基本的定义变量方式
// 普通声明(包括类型及赋值)
var vname1, vname2, vname3 type= v1, v2, v3
// 省略类型的声明
var vname1, vname2, vname3 = v1, v2, v3
// 简短声明:省略var的声明(只能用在局部变量)
vname1, vname2, vname3 := v1, v2, v3
// 下划线是个特殊的变量,任何赋予的值都会被丢弃,下面是b赋值了35
_, b := 34, 35
// 字符串(可以通过“”和``声明)
s := "hello"
// 换行字符串
m := `hello
world`
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
- 常量
const constantName = value
// 如果需要,也可以明确指定常量的类型:
const Pi float32 = 3.1415926
2
3
- 数组变量(array、slice)
一维数组
var arr [3]int = {1, 2, 3} a := [3]int{1, 2, 3} // 声明了一个长度为3的int数组 b := [10]int{1, 2, 3} // 声明了一个长度为 10 的 int 数组,其中前三个元素初始化为 1、2、3,其它默认为 0 c := [...]int{4, 5, 6} // 可以省略长度而采用 `...` 的方式,Go 会自动根据元素个数来计算长度1
2
3
4
动态数组(slice):无需定义长度
var fslice []int slice := []byte {'a', 'b', 'c', 'd'} // 声明一个数组 var array = [10]byte{'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'} // 声明两个 slice var aSlice, bSlice []byte // 演示一些简便操作 aSlice = array[:3] // 等价于 aSlice = array[0:3] aSlice 包含元素: a,b,c aSlice = array[5:] // 等价于 aSlice = array[5:10] aSlice 包含元素: f,g,h,i,j aSlice = array[:] // 等价于 aSlice = array[0:10] 这样 aSlice 包含了全部的元素1
2
3
4
5
6
7
8
9
10
11
12
二维数组
// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有 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}}1
2
3
4
5
- map变量
// 声明一个 key 是字符串,值为 int 的字典, 这种方式的声明需要在使用之前使用 make 初始化
var numbers map[string]int
// 另一种 map 的声明方式
numbers := make(map[string]int)
numbers["one"] = 1 // 赋值
numbers["ten"] = 10 // 赋值
numbers["three"] = 3
fmt.Println("第三个数字是: ", numbers["three"]) // 读取数据
// 初始化map
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// 删除key为C的元素
delete(rating, "C")
// 遍历map的key,
for k,v:=range rating {
fmt.Println("map's key:",k)
fmt.Println("map's val:",v)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
分组声明
// 多个声明合为一组
import(
"fmt"
"os"
)
const(
i = 100
pi = 3.1415
prefix = "Go_"
)
var(
i int
pi float32
prefix string
)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 流程和函数
流程控制
If
// 计算获取值 x, 然后根据 x 返回的大小,判断是否大于 10。 if x := computedValue(); x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") }1
2
3
4
5
6
for
普通遍历
for index := 10; index>0; index-- { if index == 5{ break // 或者continue } fmt.Println(index) }1
2
3
4
5
6while方式遍历
sum := 1 for sum < 1000 { sum += sum }1
2
3
4map遍历:for+range
for k,v:=range map { fmt.Println("map's key:",k) fmt.Println("map's val:",v) } // go中声明而未被调用的变量会报错,可以同_取代 for _, v := range map{ fmt.Println("map's val:", v) }1
2
3
4
5
6
7
8
9
switch
// go中的case默认带break,若想强制下面的继续执行,可以使用fallthrough i := 10 switch i { case 1: fmt.Println("i is equal to 1") fallthrough case 2, 3, 4: fmt.Println("i is equal to 2, 3 or 4") case 10: fmt.Println("i is equal to 10") default: fmt.Println("All I know is that i is an integer") }1
2
3
4
5
6
7
8
9
10
11
12
13
函数
普通函数
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) { // 这里是处理逻辑代码 // 返回多个值 return value1, value2 } // 返回 A+B 和 A*B func SumAndProduct(A, B int) (int, int) { return A+B, A*B } // 官方推荐的写法,声明返回的变量名,这样可读性更强 func SumAndProduct(A, B int) (add int, Multiplied int) { add = A+B Multiplied = A*B return }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
指针函数
func add1(a *int) int { // 请注意, *a = *a+1 // 修改了a的值 return *a // 返回新值 } func main() { x := 3 fmt.Println("x = ", x) // 应该输出 "x = 3" x1 := add1(&x) // 调用 add1(&x) 传x的地址 fmt.Println("x+1 = ", x1) // 应该输出 "x+1 = 4" fmt.Println("x = ", x) // 应该输出 "x = 4" }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
延迟语句(defer):defer修饰的语句会在函数返回前调用,多个defer按照后进先出的顺序
func ReadWrite() bool { file.Open("file") defer file.Close() if failureX { return false } if failureY { return false } return true }1
2
3
4
5
6
7
8
9
10
11
类异常机制:Panic 和 Recover
// todo
保留函数:main和init
main和init是Go中的保留函数,main方法必须存在于main package中,init方法可以在package中声明,并且Go程序会自动调用该函数,通常只在package中定义一个init函数。
# struct类型
Struct
type Skills []string type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,struct Skills // 匿名字段,自定义的类型 string slice int // 内置类型作为匿名字段 speciality string } // 访问对应的字段 fmt.Println("His name is ", mark.name) // 修改对应的字段 mark.name = "Tom"1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 面向对象
Go语言
没有封装的特性,所有的方法和属性都是分散的,但是可以进行绑定
有继承的特性,可以以匿名struct的方式继承其他struct及其method
多态的特性,父类struct可以接收子类struct的值,并且通过父类或接口调用子类(继承父类重写)或(实现接口)的方法。
method定义
除了上述的函数之外,Go还可以创建自定义变量的方法,结构体内部不能拥有方法,方法通过以下格式与结构体绑定。这样相同方法名不同参数的写法类似于Java中的方法重载。
// funcName方法属于ReceiverType结构体,可以通过结构体.funcName调用该方法 func (r ReceiverType) funcName(parameters) (results) type Rectangle struct { width, height float64 } func (r Rectangle) area() float64 { return r.width*r.height } func main() { r1 := Rectangle{12, 2} fmt.Println("Area of r1 is: ", r1.area()) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
method继承
如果一个structA中有一个匿名structB,即A继承了B的字段+method,所以通过A也可以调用B的method
type Human struct { name string age int phone string } type Student struct { Human // 匿名字段 school string } func (h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } func main() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} mark.SayHi() }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# interface
interface是一组 method 签名的组合,我们通过 interface 来定义对象的一组行为。如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。
- interface多态:interface变量可以存放(赋值)任意类型实现了该interface的对象,类似Java中的多态
- 空 interface: 可以存储任意类型的数值,方法返回interface{},说明该方法可以返回任意值
// 定义 a 为空接口 var a interface{} var i int = 5 s := "Hello world" // a 可以存储任意类型的数值 a = i a = s1
2
3
4
5
6
7
- 判断interface变量存储的类型:todo
# 反射
- todo:深入学习
Go 语言实现了反射,所谓反射就是能检查程序在运行时的状态。我们一般用到的包是 reflect 包。
- 获取类型元数据:
t := reflect.TypeOf(i)- 获取对象存储值:
v := reflect.ValueOf(i)t := reflect.TypeOf(i) // 得到类型的元数据,通过t我们能获取类型定义里面的所有元素 v := reflect.ValueOf(i) // 得到实际的值,通过 v 我们获取存储在里面的值,还可以去改变值 name := t.Elem().Field(0).Name // 获取定义在 struct 里面第一个字段的字段名 value := v.Elem().Field(0).String() // 获取存储在第一个字段里面的值1
2
3
4
5
# 并发
Go语言支持并发编程,goroutine 是 Go 并行设计的核心。goroutine本质上就是协程,与进程和线程的关系类似,线程和协程师一对多的关系,协程共享线程的数据。特点是:
- 内存占用小:协程为2KB左右,而线程要几M,在大并发下创建线程会导致内存被大量占用
- 上下文切换开销小:共享线程的大部分数据,只切换协程小部分的数据
- 不需要做并发处理:在同个线程内,所以不需要做加锁和其他并发处理(可见性),协程间通过管道通信
- 需要手动控制调度
Go语言中通过
go关键字创建协程,runtime 包中有几个处理 goroutine 的函数:
Gosched():让出CPU给其他协程Goexit:退出当前执行的 goroutine,但是 defer 函数还会继续调用NumGoroutine:返回正在执行和排队的任务总数NumCPU:返回 CPU 核数量// 在本案例中,由于协程本身就是运行在单线程中,所以go创建的协程不一定会被执行,很可能还没执行 func say(s string) { for i := 0; i < 5; i++ { // 表示让 CPU 把时间片让给别人,下次某个时候继续恢复执行该 goroutine runtime.Gosched() fmt.Println(s) } } func main() { go say("world") // 开一个新的 Goroutines 执行 say("hello") // 当前 Goroutines 执行 }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
channels
goroutine间通过channel进行通信,channel是一个通信管道,提供了双向通信功能,可以通过他发送和接收数据。默认的channel的大小为0,每次发送/接收数据都会阻塞直到数据被接收/发送。
ci := make(chan int) cs := make(chan string) cf := make(chan interface{}) ch <- v // 发送 v 到 channel ch. v := <-ch // 从 ch 中接收数据,并赋值给v1
2
3
4
5
6func say(s string, ch chan int) { for i := 0; i < 5; i++ { ch <- i } // 有for + range如果不主动close会造成死锁 close(ch) } func main() { ch := make(chan int) go say("world", ch) // 开一个新的 Goroutines 执行 // 遍历channel中所有元素直到channel被close for i := range ch{ fmt.Print(i) } }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
select
select可以实现多路复用的功能,通过select可以监听多个channel,select默认是阻塞的,监听到消息后会随机选择一个管道执行。// 此处的变量声明采用省略式写法,等价于:(c chan int, quit chan int) func fibonacci(c, quit chan int) { x, y := 1, 1 for { select { // 向管道c发送元素,并阻塞直到有人接收则触发代码 case c <- x: // 等价于x=y,y=x+y x, y = y, x + y // 监听管道quit并阻塞,直到收到管道消息并触发代码 case <-quit: fmt.Println("quit") return } } } func main() { c := make(chan int) quit := make(chan int) go func() { for i := 0; i < 10; i++ { // 匿名式写法接收数据 fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
# 三、Go常见面试题
通过面试题快速学习Go语言
# 个人Go体验
- 代码简洁灵活
- 类的公私有:首字母大小写
- 变量定义:可以不指定具体类型
- 方法返回值:多值返回
- 继承非常方便:直接将某个struct结构体作为字段即可拥有其所有字段,通过类似
struct.field.field的方式读取变量
- 可读性较差
- 自定义method:Java是聚合到某个类中,有哪些方法一目了然,而Go可能分散在各个地方,可读性较差
- 灵活带来的可能的可读性差:比如返回值可以匿名多返回,而不声明返回类型及变量名可能导致可读性降低
- 函数编程让人头晕
# Go和Java有什么不同
- 性能上:golang的性能比Java更好,占用内存更少,使用goroutine避免内核态和用户态切换成本
- 编译部署:Java通过虚拟机编译,使用JVM跨平台编译;Go中不存在虚拟机,而是针对不同的平台,编译对应的机器码
- 功能上:
- 接口:java等面向对象编程的接口是侵入式接口,需要明确声明自己实现了某个接口。 而Golang的非侵入式接口不需要通过任何关键字,只要一个类型实现了接口的所有方法,就是这个接口的实现。
- 继承:Java的继承通过extends关键字完成,不支持多继承;Go语言的继承通过Struct的方式,子类只需要把基类作为成员放在子类的定义中,支持多继承。
- 异常处理:java中错误(Error)和异常(Exception)被分类管理,golang中只有error,一旦发生错误逐层返回,直到被处理。
- 并发:通常借助于共享内存(全局变量)作为线程间通信的媒介,通常会有线程不安全问题,使用了加锁(同步化)、使用原子类、使用volatile提升可见性等解决;但在Golang中使用的是通道(channel)作为协程间通信的媒介,多个goroutine之间通过Channel来通信,chan的读取和写入操作为原子操作,所以是安全的。
- 垃圾回收和内存管理 (opens new window)
