Changkun's Blog

Science and art, life in between.


  • Home

  • Ideas

  • Archives

  • Tags

  • Bio

Go in 1 Hour

Published at: 2018-03-21   |   Reading: 8929 words ~18min   |   PV/UV: /

几个初步的 Go 语言特点:

  • 类型安全
  • 简洁的面向对象
  • 语言级并发
  • 垃圾回收

一共 25 个关键字:

 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
30
31
32
33
package         包引入
import          包引入

const           常量

var             变量

type            类型

goto            流程控制
if              流程控制
else            流程控制
break           流程控制
continue        流程控制
default         流程控制
switch          流程控制
case            流程控制
fallthrough     流程控制
for    	        流程控制

func            函数相关
return          函数相关
defer           函数相关

struct          面向对象
interface       面向对象

map             数据结构
range           数据结构

go              并发相关
chan            并发相关
select          并发相关

包引入

1
2
3
4
import(
	"fmt"
	"os"
)
  • 支持相对路径和绝对路径
  • 绝对路径 import "xxx/yyy" 会引入 $GPATH/src/xxx/yyy
  • 点导入:import . "fmt" 类似于 using namespace fmt;
  • 别名导入: import f "fmt"
  • 下划线导入:import _ "xxx/xxx/xxx" 只调用包中 init 函数

变量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 用法1
var varname1, varname2 type = value1, value2
// 用法2, 只能用在函数内部,在函数外部使用则会无法编译通过,一般用var方式来定义全局变量
short1, short2 := value1, value2
// 用法3, _ 的赋值会被丢弃
_, under2 := 34, 35
// 错误: 已声明但未使用的变量会在编译阶段报错
// var i int

// 多变量
var(
	i int
	pi float32
	prefix string
)

常量

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// 常量可定义为数值、布尔值或字符串等类型
const constantName = value
// 必要时可以明确指定常量的类型
const Pi float32 = 3.1415926
// 更多参考: https://golang.org/ref/spec#Constants

// 多常量
const(
	i = 100
	pi = 3.1415
	prefix = "Go_"
)

基础内建类型

 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
30
31
32
33
34
// 布尔类型
var available bool  // 一般声明
valid := false      // 简短声明
available = true    // 赋值操作

// 数值类型
// 1. 有无符号和带符号两种, 无符号在类型前加 `u`,例如 int -> uint
// 2. 类型的长度相同取决于不同编译器的实现
// 3. 主要数值类型包括 rune, int8, int16, int32, int64
//    byte, uint8, uint16, uint32, uint64
//    其中 rune 是 int32 的别称,byte 是 uint8 的别称,
//    浮点数的类型有 float32 和 float64,没有float类型,默认 float64
// 4. 不同变量之间不允许互相赋值或操作
// 5. 支持复数。它的默认类型是 complex128(64位实数+64位虚数)

// 字符串
// 1. UTF-8 编码
// 2. 一对双引号("")或反引号(``)括起来定义, 类型是string
var emptyString string = ""  // 声明了一个字符串变量,初始化为空字符串
// 3. 字符串是不可变的
var s string = "hello"
// s[0] = 'c'   // fail
c := []byte(s)  // 将 s 转换为 []byte 类型
c[0] = 'c'      // ok
// 4. 支持 + 操作符
// 5. 可进行切片操作 s[1:]
// 6. 通过`来声明多行 raw 字符串

// 错误类型
import "errors"
err := errors.New("emit macho dwarf: elf header corrupted")
if err != nil {
	fmt.Print(err)
}
  • Go 的底层数据结构:https://research.swtch.com/godata

iota 枚举

 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
30
31
// iota 用来声明枚举时采用,初始值为 0,const 中每增加一行加 1
package main

import (
	"fmt"
)

const (
	x = iota // x == 0
	y = iota // y == 1
	z = iota // z == 2
	w        // 常量声明省略值时,默认和之前一个值的字面相同。即 w = iota,即 w == 3
)

const v = iota // 每遇到一个 const 关键字,iota 就会重置,此时v == 0

const (
	h, i, j = iota, iota, iota //h=0,i=0,j=0 iota 在同一行值相同
)

const (
	a       = iota             // a=0
	b       = "B"
	c       = iota             //c=2
	d, e, f = iota, iota, iota //d=3, e=3, f=3
	g                          //g = 4
)

func main() {
	fmt.Println(a, b, c, d, e, f, g, h, i, j, x, y, z, w, v)
}
  • 大写字母开头的变量是可导出的,也就是其它包可以读取的,是公有变量;
  • 小写字母开头的就是不可导出的,是私有变量;
  • 大写字母开头的函数相当于class中的带public关键词的公有函数;
  • 小写字母开头的函数相当于class中的带private关键词的私有函数。

array

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 在[n]type中,n表示数组的长度,type表示存储元素的类型, 即 C 中的静态数组
var arr [n]type

var arr [10]int  // 声明了一个int类型的数组
arr[0] = 42      // 数组下标是从0开始的
arr[1] = 13      // 赋值操作
fmt.Printf("The first element is %d\n", arr[0])  // 获取数据,返回42
fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0

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会自动根据元素个数来计算长度

// 声明了一个二维数组,该数组以两个数组作为元素,其中每个数组中又有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}}
  • 当把一个数组作为参数传入函数的时候,传入的其实是该数组的拷贝,而不是它的指针。如果要使用指针,需要 slice 类型。

slice

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
// 和声明 array 一样,只是少了长度,即「动态数组」,为引用类型
var fslice []int
slice := []byte {'a', 'b', 'c', 'd'}

// 切片操作与 python 一致

// 声明一个含有10个元素元素类型为byte的数组
var ar = [10]byte {'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j'}
// 声明两个含有byte的slice
var a, b []byte
// a指向数组的第3个元素开始,并到第五个元素结束,
a = ar[2:5]
//现在 a 含有的元素: ar[2]、ar[3] 和 ar[4]
// b 是数组ar的另一个slice
b = ar[3:5]
// b 的元素是:ar[3]和ar[4]

map

 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
// 即 python 中的字典, 为引用类型
// 声明一个key是字符串,值为int的字典,这种方式的声明需要在使用之前使用make初始化
var numbers map[string]int
// 另一种map的声明方式
numbers = make(map[string]int)
numbers["one"] = 1  //赋值
numbers["ten"] = 10 //赋值
numbers["three"] = 3

// 字典无序
// 长度不定
// 支持 len()
// 非线程安全,需要使用 mutex lock

// 删除某个 key-value
// 初始化一个字典
rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 }
// map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true
csharpRating, ok := rating["C#"]
if ok {
	fmt.Println("C# is in the map and its rating is ", csharpRating)
} else {
	fmt.Println("We have no rating associated with C# in the map")
}

delete(rating, "C")  // 删除key为C的元素

make, new 操作

  • make用于内建类型(map、slice 和channel)的内存分配,返回一个有初始值 (非零) 的 T 类型,而不是*T。导致这三个类型有所不同的原因是指向数据结构的引用在使用前必须被初始化。make返回初始化后的(非零)值。
  • new用于各种类型的内存分配,new(T) 返回了一个指针,指向新分配的类型 T 的零值

零值

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
//零值是一种“变量未填充前”的默认值
int     0
int8    0
int32   0
int64   0
uint    0x0
rune    0 //rune的实际类型是 int32
byte    0x0 // byte的实际类型是 uint8
float32 0 //长度为 4 byte
float64 0 //长度为 8 byte
bool    false
string  ""

流程控制

if

  • 条件不需要括号
  • 条件允许声明变量,并用;做判断
1
2
3
4
5
if x := computedValue(); x > 10 {
	fmt.Println("x is greater than 10")
} else {
	fmt.Println("x is less than 10")
}

godo

  • 不要使用

for

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
sum := 0;
for index:=0; index < 10 ; index++ {
    sum += index
}
fmt.Println("sum is equal to ", sum)

// 当条件 1 和 3 省略时为 while 语句
sum := 1
for sum < 1000 { // 即 for ; sum < 100; {
	sum += sum
}
  • 支持 break 或 continue
  • 配合 range 可以用于读取 slice 和 map 的数据
1
2
3
4
for k, v := range a_map { // 可以将 k 替换为 _ 消除无用赋值和编译报错
	fmt.Println("map's key:",k)
	fmt.Println("map's val:",v)
}

switch

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// expr1, expr2, expr3 类型一致 (类型安全)
// 如果 switch 没有表达式,它会匹配 true
// 每个 case 默认 break
// fallthrough 可用于贯穿执行下一个 case
switch sExpr {
case expr1:
	some instructions
case expr2:
	some other instructions
case expr3:
	some other instructions
default:
	other code
}

函数

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
func funcName(input1 type1, input2 type2) (output1 type1, output2 type2) {

	// 多返回值
	return value1, value2
}

// 比较下列用法
func max(a, b int) int {
	if a > b {
		return a
	}
	return 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
}

// 变长参数
// 变量 arg 是一个int的slice
func myfunc(arg ...int) {
    for _, n := range arg {
        fmt.Printf("And the number is: %d\n", n)
    }
}

// 传值,传指针
// 比较下列用法, 与 C 类似
func add1(a int) int {
	a = a+1 // 我们改变了a的值
	return a //返回一个新值
}
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)
	fmt.Println("x+1 = ", x1) // 输出"x+1 = 4"
	fmt.Println("x = ", x)    // 输出"x = 3"
    
   	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"
}
  • channel,slice,map这三种类型的实现机制类似指针,所以可以直接传递,而不用取地址后传递指针。(若函数需改变slice的长度,则仍需要取地址传递指针

defer 延时语句

  • 当函数执行到最后时,defer 语句会按照逆序执行,最后该函数返回。
  • 类似于析构函数
 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
// 比较下列代码
func ReadWrite1() bool {
	file.Open("file")
	if failureX {
		file.Close()
		return false
	}

	if failureY {
		file.Close()
		return false
	}

	file.Close()
	return true
}

func ReadWrite2() bool {
	file.Open("file")
	defer file.Close()
	if failureX {
		return false
	}
	if failureY {
		return false
	}
	return true
}
  • 注意逆序输出:
1
2
3
4
// 4 3 2 1 0
for i := 0; i < 5; i++ {
	defer fmt.Printf("%d ", i)
}

函数类型与传值

  • 类似于函数指针
 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
30
31
32
33
34
35
36
type typeName func(input1 inputType1 , input2 inputType2 [, ...]) (result1 resultType1 [, ...])

// 例
type testInt func(int) bool // 声明了一个函数类型
func isOdd(integer int) bool {
	if integer%2 == 0 {
		return false
	}
	return true
}

func isEven(integer int) bool {
	if integer%2 == 0 {
		return true
	}
	return false
}

// 函数类型
func filter(slice []int, f testInt) []int {
	var result []int
	for _, value := range slice {
		if f(value) {
			result = append(result, value)
		}
	}
	return result
}
func main(){
	slice := []int {1, 2, 3, 4, 5, 7}
	fmt.Println("slice = ", slice)
	odd := filter(slice, isOdd)    // 函数传值
	fmt.Println("Odd elements of slice are: ", odd)
	even := filter(slice, isEven)  // 函数传值
	fmt.Println("Even elements of slice are: ", even)
}

panic 与 recover

  • panic 与 recover 机制替代了异常
  • panic() 为内建函数,可以中断原有的控制流程,进入一个令人恐慌的流程中。当函数F调用panic,函数F的执行被中断,但是F中的延迟函数会正常执行,然后F返回到调用它的地方。在调用的地方,F的行为就像调用了panic。这一过程继续向上,直到发生panic的goroutine中所有调用的函数返回,此时程序退出。恐慌可以直接调用panic产生。也可以由运行时错误产生,例如访问越界的数组。
  • recover() 为内建的函数,可以让进入令人恐慌的流程中的goroutine恢复过来。recover仅在延迟函数中有效。在正常的执行过程中,调用recover会返回nil,并且没有其它任何效果。如果当前的goroutine陷入恐慌,调用recover可以捕获到panic的输入值,并且恢复正常的执行。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
// 比较下列代码
var user = os.Getenv("USER")
func init() {
	if user == "" {
		panic("no value for $USER")
	}
}
func throwsPanic(f func()) (b bool) {
	defer func() {
		if x := recover(); x != nil {
			b = true
		}
	}()
	f() //执行函数 f,如果 f 中出现了 panic,那么就可以恢复回来
	return
}
throwPanic(init)

main 与 init 函数

  • main (只能应用于package main)与 init 函数(能够应用于所有的package)均为保留函数
  • 定义时不能有任何的参数和返回值
  • 程序会自动调用init()和main()
  • 每个package中的init函数都是可选的,但package main就必须包含一个main函数。
  • 执行机制:

mage-20180321150937

struct 类型

 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
type person struct {
	name string
	age int
}
var P person  // P现在就是person类型的变量了
P.name = "Astaxie"  // 赋值"Astaxie"给P的name属性
P.age = 25  // 赋值"25"给变量P的age属性
fmt.Printf("The person's name is %s", P.name)  // 访问P的name属性


// 按照顺序提供初始化值
P := person{"Tom", 25}
// 通过field:value的方式初始化,这样可以任意顺序
P := person{age:24, name:"Tom"}
// 当然也可以通过new函数分配一个指针,此处P的类型为*person
P := new(person)


// 匿名字段(嵌入字段)
// 当匿名字段是一个struct的时候,那么这个struct所拥有的全部字段都被隐式地引入了当前定义的这个struct
// 所有的内置类型和自定义类型都是可以作为匿名字段的
// 考虑下面代码
package main

import "fmt"

type Skills []string

type Human struct {
	name string
	age int
	weight int
}

type Student struct {
	Human  // 匿名字段,struct
	Skills // 匿名字段,自定义的类型string slice
	int    // 内置类型作为匿名字段
	speciality string
}

func main() {
	// 初始化学生Jane
	jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"}
	// 现在我们来访问相应的字段
	fmt.Println("Her name is ", jane.name)
	fmt.Println("Her age is ", jane.age)
	fmt.Println("Her weight is ", jane.weight)
	fmt.Println("Her speciality is ", jane.speciality)
	// 我们来修改他的skill技能字段
	jane.Skills = []string{"anatomy"}
	fmt.Println("Her skills are ", jane.Skills)
	fmt.Println("She acquired two new ones ")
	jane.Skills = append(jane.Skills, "physics", "golang")
	fmt.Println("Her skills now are ", jane.Skills)
	// 修改匿名内置类型字段
	jane.int = 3
	fmt.Println("Her preferred number is", jane.int)
}
// 如果 Human 和 Student 军包含相同字段,则 student 对象会优先访问最外层,即访问 Student 的字段
// 如果需要访问内部字段,则通过匿名字段名来访问:
package main

import "fmt"

type Human struct {
	name string
	age int
	phone string  // Human类型拥有的字段
}

type Employee struct {
	Human  // 匿名字段Human
	speciality string
	phone string  // 雇员的phone字段
}

func main() {
	Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"}
	fmt.Println("Bob's work phone is:", Bob.phone)
	// 如果我们要访问Human的phone字段
	fmt.Println("Bob's personal phone is:", Bob.Human.phone)
}

面向对象

方法

  • 对象方法与函数声明语法几乎一致,只在 func 后增加一个 receiver

    1
    2
    3
    4
    
    // function
    func funcName(input1 type1, input2 type2) (output1 type1, output2 type2)
    // method
    func (r ReceiverType) funcName(parameters) (results)
    
  • 方法里可以访问接收者的字段

  • 调用方法通过.访问,就像struct里面访问字段一样

  • 方法可以定义在任何自定义类型、内置类型、struct 等上

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 考虑下面声明
// type typeName typeLiteral

type ages int

type money float32

type months map[string]int

m := months {
	"January":31,
	"February":28,
	...
	"December":31,
}

再看一个例子:

  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
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
package main

import "fmt"

const(
	WHITE = iota
	BLACK
	BLUE
	RED
	YELLOW
)

type Color byte

type Box struct {
	width, height, depth float64
	color Color
}

type BoxList []Box //a slice of boxes

func (b Box) Volume() float64 {
	return b.width * b.height * b.depth
}

// 注意,这里的 receiver 是一个 Box 指针
// 原因在于我们希望修改 Box 的颜色,如果不传递指针
// 则此方法接受的是一个 Box 的拷贝,并非实际修改 Box
// 同时,无论是 *b.Color=c 还是 b.Color=c 都是可以的
// 
// 也就是说,如果一个方法的 receiver 是 *T
// 则可以在一个 T 类型的实例变量 V 上面调用这个方法
// 而不需要 &V 去调用这个 method
//
// 同理,如果一个方法的receiver是T
// 则可以在一个*T类型的变量P上面调用这个方法
// 而不需要 *P去调用这个method
func (b *Box) SetColor(c Color) {
	b.color = c
}

func (bl BoxList) BiggestColor() Color {
	v := 0.00
	k := Color(WHITE)
	for _, b := range bl {
		if bv := b.Volume(); bv > v {
			v = bv
			k = b.color
		}
	}
	return k
}

func (bl BoxList) PaintItBlack() {
	for i := range bl {
		bl[i].SetColor(BLACK)
	}
}

func (c Color) String() string {
	strings := []string {"WHITE", "BLACK", "BLUE", "RED", "YELLOW"}
	return strings[c]
}

func main() {
	boxes := BoxList {
		Box{4, 4, 4, RED},
		Box{10, 10, 1, YELLOW},
		Box{1, 1, 20, BLACK},
		Box{10, 10, 1, BLUE},
		Box{10, 30, 1, WHITE},
		Box{20, 20, 20, YELLOW},
	}

	fmt.Printf("We have %d boxes in our set\n", len(boxes))
	fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³")
	fmt.Println("The color of the last one is",boxes[len(boxes)-1].color.String())
	fmt.Println("The biggest one is", boxes.BiggestColor().String())

	fmt.Println("Let's paint them all black")
	boxes.PaintItBlack()
	fmt.Println("The color of the second one is", boxes[1].color.String())

	fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
}


// 方法继承
package main

import "fmt"

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
	Human //匿名字段
	school string
}

type Employee struct {
	Human //匿名字段
	company string
}

//在human上面定义了一个method
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"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

	mark.SayHi()
	sam.SayHi()
}



// 方法重写
package main

import "fmt"

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
	Human //匿名字段
	school string
}

type Employee struct {
	Human //匿名字段
	company string
}

//Human定义method
func (h *Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Employee的method重写Human的method
func (e *Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
		e.company, e.phone)
}

func main() {
	mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"}
	sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"}

	mark.SayHi()
	sam.SayHi()
}
  • Go 中的面向对象没有私有共有关键字,通过名称的大小写来实现

接口

  • 接口定义了一组方法,如果对象实现了某个接口的所有方法,则对象就实现了接口。
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// 考虑下面代码
type Human struct {
    name string
    age int
    phone string
}
type Student struct {
    Human
    school string
    loan float32
}
type Employee struct {
    Human
    company string
    money float32
}

// Human 对象实现 SayHi, Sing, Guzzle 方法
func (human *Human) SayHi() {
    fmt.Printf("Hi, I am %s you can call me on %s\n", human.name, human.phone)
}
func (human *Human) Sing(lyrics string) {
    fmt.Println("La la, la la la, la la la la la...", lyrics)
}
func (human *Human) Guzzle(beerStein string) {
	fmt.Println("Guzzle Guzzle Guzzle...", beerStein)
}

//Student实现BorrowMoney方法
func (s *Student) BorrowMoney(amount float32) {
	s.loan += amount // (again and again and...)
}

// Employee 重载 Human Sayhi
func (e *Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
		e.company, e.phone) //此句可以分成多行
}
// Employee 实现 SpendSalary
func (e *Employee) SpendSalary(amount float32) {
	e.money -= amount // More vodka please!!! Get me through the day!
}

// 定义接口
type Men interface {
    SayHi()
    Sing(lyrics string)
    Guzzle(beerStein string)
}
type YoungChap interface {
	SayHi()
	Sing(song string)
	BorrowMoney(amount float32)
}

type ElderlyGent interface {
	SayHi()
	Sing(song string)
	SpendSalary(amount float32)
}
  • 接口可以被任意对象实现,上面的 Men 接口被 Human, Student 和 Employee 实现
  • 同理,一个对象可以实现多个 interface,上面的 Student 实现了 Men 和 YoungChap 两个接口
  • 任意类型均实现了空接口 interface{}

接口值

  • 定义了的接口变量,可以保存实现接口的任意类型对象。
  • 上面的例子中定义的 Men 接口类型的比变量 m 可以存储 Human, Student 或 Employee 值
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
package main

import "fmt"

type Human struct {
	name string
	age int
	phone string
}

type Student struct {
	Human //匿名字段
	school string
	loan float32
}

type Employee struct {
	Human //匿名字段
	company string
	money float32
}

//Human实现SayHi方法
func (h Human) SayHi() {
	fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone)
}

//Human实现Sing方法
func (h Human) Sing(lyrics string) {
	fmt.Println("La la la la...", lyrics)
}

//Employee重载Human的SayHi方法
func (e Employee) SayHi() {
	fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name,
		e.company, e.phone)
	}

// Interface Men被Human,Student和Employee实现
// 因为这三个类型都实现了这两个方法
type Men interface {
	SayHi()
	Sing(lyrics string)
}

func main() {
	mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00}
	paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100}
	sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000}
	tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000}

	//定义Men类型的变量i
	var i Men

	//i能存储Student
	i = mike
	fmt.Println("This is Mike, a Student:")
	i.SayHi()
	i.Sing("November rain")

	//i也能存储Employee
	i = tom
	fmt.Println("This is tom, an Employee:")
	i.SayHi()
	i.Sing("Born to be wild")

	//定义了slice Men
	fmt.Println("Let's use a slice of Men and see what happens")
	x := make([]Men, 3)
	//这三个都是不同类型的元素,但是他们实现了interface同一个接口
	x[0], x[1], x[2] = paul, sam, mike

	for _, value := range x{
		value.SayHi()
	}
}
  • 接口知识一组抽象方法的集合,必须由其他非接口类型实现,不能自我实现
  • 由于任意类型都实现了空接口,因此空接口可以存储任何类型数值(类似于void *)
1
2
3
4
5
6
7
// 定义a为空接口
var a interface{}
var i int = 5
s := "Hello world"
// a可以存储任意类型的数值
a = i
a = s

接口函数参数

  • 接口的变量可以持有任意实现该接口的对象,因此我们可以通过定义接口参数使函数接受各种类型参数
 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
type Stringer interface {
	 String() string
}
// 则任何实现了 String 方法的类型都能被 fmt.Println 调用

package main
import (
	"fmt"
	"strconv"
)

type Human struct {
	name string
	age int
	phone string
}

// 通过这个方法 Human 实现了 fmt.Stringer
func (h Human) String() string {
	return "❰"+h.name+" - "+strconv.Itoa(h.age)+" years -  ✆ " +h.phone+"❱"
}

func main() {
	Bob := Human{"Bob", 39, "000-7777-XXX"}
	fmt.Println("This Human is : ", Bob)
}
  • 实现了 error 接口的对象(即 Error() string 对象),fmt 输出时会调用 Error() 方法,无需定义 String() 方法

接口变量的存储类型

  • 既然可以存储任意类型,通常有两种方法来反向检查变量的类型

  • Comma-ok 断言

    • 判断类型时通过 value, ok = element.(T),value 为变量值,ok 是 bool 类型,element 是接口变量,T 是断言类型
    • 如果存储了 T 类型,ok 为 true
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    
    list := make(List, 3)
    list[0] = 1 // an int
    list[1] = "Hello" // a string
    list[2] = Person{"Dennis", 70}
    
    for index, element := range list {
        if value, ok := element.(int); ok {
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
        } else if value, ok := element.(string); ok {
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
        } else if value, ok := element.(Person); ok {
            fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
        } else {
            fmt.Printf("list[%d] is of a different type\n", index)
        }
    }
    

    ​

  • Switch 测试

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    
    list := make(List, 3)
    list[0] = 1 //an int
    list[1] = "Hello" //a string
    list[2] = Person{"Dennis", 70}
    
    for index, element := range list{
        switch value := element.(type) {
            case int:
            fmt.Printf("list[%d] is an int and its value is %d\n", index, value)
            case string:
            fmt.Printf("list[%d] is a string and its value is %s\n", index, value)
            case Person:
            fmt.Printf("list[%d] is a Person and its value is %s\n", index, value)
            default:
            fmt.Println("list[%d] is of a different type", index)
        }
    }
    

嵌入接口

  • 接口也有匿名字段,也叫做嵌入字段
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
type Interface interface {
	sort.Interface 		//嵌入字段sort.Interface
	Push(x interface{}) //可以 push 任何元素
	Pop() interface{}   //可以 pop 任何元素
}
// io.ReadWriter
type ReadWriter interface {
	Reader
	Writer
}

反射

  • 运行时状态检查使用 reflect 包

  • 使用 reflect 分三步:

    • 将要反射的值(这些值都实现了空接口)转化为 reflect 对象(reflect.Type 或者 reflect.Value)
    1
    2
    
    t := reflect.TypeOf(i)    //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
    v := reflect.ValueOf(i)   //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
    
    • 将 reflect 对象转化成相应的值
    1
    2
    
    tag := t.Elem().Field(0).Tag  //获取定义在struct里面的标签
    name := v.Elem().Field(0).String()  //获取存储在第一个字段里面的值
    
    • 获取反射值返回对应的类型和数值
    1
    2
    3
    4
    5
    
    var x float64 = 3.4
    v := reflect.ValueOf(x)
    fmt.Println("type:", v.Type())
    fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
    fmt.Println("value:", v.Float())
    
  • 如果需要反射的值可修改,则

1
2
3
4
var x float64 = 3.4
p := reflect.ValueOf(&x)
v := p.Elem()
v.SetFloat(7.1) 
  • 深入参考: https://blog.golang.org/laws-of-reflection

并发

runtime 包中包含几个处理 goroutine 的函数:

  • Goexit: 退出当前执行的 goroutine,但继续调用 defer
  • Gosched: 让出当前 goroutine 执行权限,调度器安排其他等待的任务运行,在下次某个时候从该位置恢复执行
  • NumCPU: CPU 核数
  • GOMAXPROCS: 设置并行计算的 CPU 核最大值,并返回之前的值

goroutine

  • Go 在语言层面支持并发,只需要极少栈内存,支持伸缩
  • goroutine 的本质为协程,并且在语言层实现了 goroutine 之间的内存共享
  • 通过 runtime 管理的一个线程管理器,使用 go 关键字
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
package main

import (
	"fmt"
	"runtime"
)

func say(s string) {
	for i := 0; i < 5; i++ {
		fmt.Println(s)
	}
}

func main() {
	go say("world")  // 开一个新的 goroutines 执行
    runtime.Gosched()
	say("hello")     // 当前 goroutines 执行
}

channels

  • goroutine 运行在相同的地址空间,因此内存共享会出现一致性问题。
  • channel 提供了类似于双向管道的机制,可以通过它发送或接受值,并且只能为 channel 类型
  • 定义 channel 也需要定义发送到 channel 的值得类型,且必须使用 make 创建
 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
30
31
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})

// 使用 <- 来接受和发送数据
ch <- v    // 发送 v 到 channel ch.
v := <-ch  // 从ch中接收数据,并赋值给v

// 考虑下面代码
package main

import "fmt"

func sum(a []int, c chan int) {
	total := 0
	for _, v := range a {
		total += v
	}
	c <- total  // 将 total 发送给 c
}

func main() {
	a := []int{7, 2, 8, -9, 4, 0}

	c := make(chan int)
	go sum(a[:len(a)/2], c)
	go sum(a[len(a)/2:], c)
	x, y := <-c, <-c  // 从 c 中接受

	fmt.Println(x, y, x + y)
}
  • channel 接受和发送数据均阻塞,除非另一端已经准备好,因此不需要显式的 lock,即 value := <-ch 会阻塞到有数据为止
  • 任何发送也会被阻塞,直到数据被取出

buffered channels

  • 可以指定 channel 缓冲大小,即可以存储的元素
  • ch:= make(chan bool, 4) 创建了可以存储四个元素的 bool 类型 channel
  • 这时前四个元素无阻塞写入,后续元素写入需要等到有元素从 channel 中取出后才能写入,类似于池化技术
  • ch := make(chan type, value) 当 value 为 0 时, channel 无缓冲阻塞读写,当 value > 0 时候, channel 有缓冲、非阻塞,直到 value 个元素写满。

range 和 close

 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
30
31
32
33
34
35
36
37
// 考虑下面代码
package main

import "fmt"

func main() {
	c := make(chan int, 2) //修改2为1就报错,修改2为3可以正常运行
	c <- 1
	c <- 2
	fmt.Println(<-c)
	fmt.Println(<-c)
}
// 我们需要读取两次 c, 可以通过 range 来操作缓存类型 channel
package main

import (
	"fmt"
)

func fibonacci(n int, c chan int) {
	x, y := 1, 1
	for i := 0; i < n; i++ {
		c <- x
		x, y = y, x + y
	}
    // 应该在生产者的位置关闭 channel,否则会引起 panic
	close(c)
}

func main() {
	c := make(chan int, 10)
	go fibonacci(cap(c), c)
    // 不断读取 channel 里面的数据,直到 channel 被显式关闭
	for i := range c {
		fmt.Println(i)
	}
}

select

  • 当存在多个 channel 时,可以使用 select 监听 channel 上的数据流动
  • select 默认阻塞,只有当监听的 channel 中由发送或接受可以进行时才会运行
  • 当多个 channel 准备好时,select 随机选择一个执行
  • 类似于 switch
 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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
// 考虑如下代码
package main

import "fmt"

func fibonacci(c, quit chan int) {
	x, y := 1, 1
	for {
		select {
		case c <- x:
			x, y = y, x + y
		case <-quit:
			fmt.Println("quit")
			return
        default:
        	// 当所监听 channel 均未准备好时执行    
		}
	}
}

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)
}

// 当出现 goroutine 阻塞时,可以使用 select 设置超时
func main() {
	c := make(chan int)
	o := make(chan bool)
	go func() {
		for {
			select {
				case v := <- c:
					println(v)
				case <- time.After(5 * time.Second):
					println("timeout")
					o <- true
					break
			}
		}
	}()
	<- o
}
#Go# #教程#
  • Author: Changkun Ou
  • Link: https://changkun.de/blog/posts/go-in-1-hour/
  • License: All articles in this blog are licensed under CC BY-NC-ND 4.0 unless stating additionally.
Go Web in 1 Hour
UMSLT04: The Past and Present of SGD
  • TOC
  • Overview
Changkun Ou

Changkun Ou

Stop Talking. Just Coding.

276 Blogs
165 Tags
Homepage GitHub Email YouTube Twitter Zhihu
Friends
    Frimin ZZZero march1993 qcrao maiyang Xargin Muniao
  • 包引入
  • 变量
  • 常量
  • 基础内建类型
  • iota 枚举
  • array
  • slice
  • map
  • make, new 操作
  • 零值
  • 流程控制
    • if
    • godo
    • for
    • switch
  • 函数
    • defer 延时语句
  • 函数类型与传值
  • panic 与 recover
  • main 与 init 函数
  • struct 类型
  • 面向对象
    • 方法
    • 接口
  • 反射
  • 并发
    • goroutine
    • channels
© 2008 - 2024 Changkun Ou. All rights reserved. | PV/UV: /
0%