Go 基础
# Go 基础
# Go 的安装
# 配置 Go 代理
使用 PowerShell 执行添加
$env:GO111MODULE = "on"
$env:GOPROXY = "https://goproxy.cn"
2
# 第一个 Go 程序
package main
import (
"fmt"
)
func main() {
fmt.Println("hello world")
}
2
3
4
5
6
7
8
9
package 必须为 main 否则无法执行主程序
go run 执行
go run main.go
go build 构建后执行
go build main.go
./main
2
# 变量
package main
import (
"fmt"
"math"
)
func main() {
var a = "initial" //var为自动推导类型
var b, c int = 1, 2 //也可以手动指定类型
var d = true
var e float64
f := float32(e) //自动推导类型
g := a + "foo" //拼接变量
fmt.Println(a, b, c, d, e, f) // initial 1 2 true 0 0
fmt.Println(g) // initialfoo
const s string = "constant" //常量
const h = 500000000 //如果不指定类型,则通过上下文进行确定
const i = 3e20 / h
fmt.Println(s, h, i, math.Sin(h), math.Sin(i))
}
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
# 常量
常量声明可以使用 iota 常量生成器初始化,它用于生成一组以相似规则初始化的常量
const (
x = iota // x == 0
y = iota // y == 1
z = iota // z == 2
w // 这里隐式地说 w = iota,因此 w == 3。其实上面 y 和 z 可同样不用"= iota"
)
2
3
4
5
6
# 数据类型
| 类型 | 名称 | 长度 | 说明 |
|---|---|---|---|
| bool | 布尔类型 | 1 | |
| byte | 字节型 | 1 | uint8 别名 |
| rune | 字符类型 | 4 | 专用于存储 unicode 编码,等价于 uint32 |
| int, uint | 整型 | 4 或 8 | |
| int8, uint8 | 整型 | 1 | |
| int16, uint16 | 整型 | 2 | |
| int32, uint32 | 整型 | 4 | |
| int64, uint64 | 整型 | 8 | |
| float32 | 浮点型 | 4 | 小数位精确到 7 位 |
| float64 | 浮点型 | 8 | 小数位精确到 15 位 |
| complex64 | 复数类型 | 8 | |
| complex128 | 复数类型 | 16 | |
| uintptr | 整型 | 4 或 8 | 足以存储指针的 uint32 或 uint64 整数 |
| string | 字符串 |
# 类型转换
Go 语言中不允许隐式转换,所有类型转换必须显式声明,而且转换只能发生在两种相互兼容的类型之间。
var ch byte = 97
//var a int = ch //err, cannot use ch (type byte) as type int inassignment
var a int = int(ch)
2
3
# 类型别名
type bigint int64 //int64 类型改名为 bigint
var x bigint = 100
type (
myint int //int 改名为 myint
mystr string //string 改名为 mystr
)
2
3
4
5
6
7
# fmt 输入输出
| 格式 | 含义 |
|---|---|
| %% | 一个 % 字面量 |
| %b | 一个二进制整数值 (基数为 2),或者是一个 (高级的) 用科学计数法表示的指数为 2 的浮点数 |
| %c | 字符型。可以把输入的数字按照 ASCII 码相应转换为对应的字符 |
| %d | 一个十进制数值 (基数为 10) |
| %e | 以科学记数法 e 表示的浮点数或者复数值 |
| %E | 以科学记数法 E 表示的浮点数或者复数值 |
| %f | 以标准记数法表示的浮点数或者复数值 |
| %g | 以 % e 或者 % f 表示的浮点数或者复数,任何一个都以最为紧凑的方式输出 |
| %G | 以 % E 或者 % f 表示的浮点数或者复数,任何一个都以最为紧凑的方式输出 |
| %o | 一个以八进制表示的数字 (基数为 8) |
| %p | 以十六进制 (基数为 16) 表示的一个值的地址,前缀为 0x, 字母使用小写的 a-f 表示 |
| %q | 使用 Go 语法以及必须时使用转义,以双引号括起来的字符串或者字节切片 [] byte,或者是以单引号括起来的数字 |
| %s | 字符串。输出字符串中的字符直至字符串中的空字符(字符串以 '\0‘结尾,这个 '\0' 即空字符) |
| %t | 以 true 或者 false 输出的布尔值 |
| %T | 使用 Go 语法输出的值的类型 |
| %U | 一个用 Unicode 表示法表示的整型码点,默认值为 4 个数字字符 |
| %v | 使用默认格式输出的内置或者自定义类型的值,或者是使用其类型的 String () 方式输出的自定义值,如果该方法存在的话 |
| %x | 以十六进制表示的整型值 (基数为十六),数字 a-f 使用小写表示 |
| %X | 以十六进制表示的整型值 (基数为十六),数字 A-F 使用小写表示 |
// 输出
f := 3.14
fmt.Printf("f = %f, %g\n", f, f) //f = 3.140000, 3.14
fmt.Printf("f type = %T\n", f) //f type = float64
//输入
var v int
fmt.Println("请输入一个整型:")
fmt.Scanf("%d", &v)
//fmt.Scan(&v)
fmt.Println("v = ", v)
2
3
4
5
6
7
8
9
10
11
# 流程控制
# 条件语句 if
可省略条件表达式括号
package main
import "fmt"
func main() {
if 7%2 == 0 {
fmt.Println("7 is even")
} else {
fmt.Println("7 is odd")
}
if 8%4 == 0 {
fmt.Println("8 is divisible by 4")
}
//支持一个初始化表达式, 初始化字句和条件表达式直接需要用分号分隔
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 条件语句 switch
switch 语句用于基于不同条件执行不同动作,每一个 case 分支都是唯一的,从上直下逐一测试,直到匹配为止。 Golang switch 分支表达式可以是任意类型,不限于常量。可省略 break,默认自动终止。
package main
import (
"fmt"
"time"
)
func main() {
a := 2
switch a {
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
case 4, 5: //val1 和 val2 则可以是同类型的任意值,使用逗号进行分割
fmt.Println("four or five")
default:
fmt.Println("other")
}
t := time.Now()
switch { //省略条件表达式
case t.Hour() < 12: //可以使用条件语句,优化多条if else
fmt.Println("It's before noon")
default:
fmt.Println("It's after noon")
}
}
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
switch 语句还可以被用于 type-switch 来判断某个 interface 变量中实际存储的变量类型。
package main
import "fmt"
func main() {
var x interface{}
//写法一:
switch i := x.(type) { // 带初始化语句
case nil:
fmt.Printf(" x 的类型 :%T\r\n", i)
case int:
fmt.Printf("x 是 int 型")
case float64:
fmt.Printf("x 是 float64 型")
case func(int) float64:
fmt.Printf("x 是 func(int) 型")
case bool, string:
fmt.Printf("x 是 bool 或 string 型")
default:
fmt.Printf("未知型")
}
//写法二
var j = 0
switch j {
case 0:
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法三
var k = 0
switch k {
case 0:
println("fallthrough")
fallthrough
/*
Go的switch非常灵活,表达式不必是常量或整数,执行的过程从上至下,直到找到匹配项;
而如果switch没有表达式,它会匹配true。
Go里面switch默认相当于每个case最后带有break,
匹配成功后不会自动向下执行其他case,而是跳出整个switch,
但是可以使用fallthrough强制执行后面的case代码。
*/
case 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法三
var m = 0
switch m {
case 0, 1:
fmt.Println("1")
case 2:
fmt.Println("2")
default:
fmt.Println("def")
}
//写法四
var n = 0
switch { //省略条件表达式,可当 if...else if...else
case n > 0 && n < 10:
fmt.Println("i > 0 and i < 10")
case n > 10 && n < 20:
fmt.Println("i > 10 and i < 20")
default:
fmt.Println("def")
}
}
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
# 循环语句 for
package main
import "fmt"
func main() {
i := 1
for { //相当于for (;;),替代 while (true)
fmt.Println("loop")
break
}
for j := 7; j < 9; j++ {
fmt.Println(j)
}
for n := 0; n < 5; n++ {
if n%2 == 0 {
continue //同样支持break和continue
}
fmt.Println(n)
}
for i <= 3 { //相当于for (; i <= 3;),替代 while (i <= 3)
fmt.Println(i)
i = 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
# goto
用 goto 跳转到必须在当前函数内定义的标签
func main() {
for i := 0; i < 5; i++ {
for {
fmt.Println(i)
goto LABEL //跳转到标签 LABEL,从标签处,执行代码
}
}
fmt.Println("this is test")
LABEL:
fmt.Println("it is over")
}
2
3
4
5
6
7
8
9
10
11
# 数组
- 访问越界,如果下标在数组合法范围之外,则触发访问越界,会 panic
- 数组是值类型,赋值和传参会复制整个数组,而不是指针。因此改变副本的值,不会改变本身的值。
- 指针数组 [n]*T,数组指针 *[n] T。
package main
import "fmt"
func main() {
var a [5]int
a[4] = 100
fmt.Println("get:", a[2])
fmt.Println("len:", len(a))
b := [5]int{1, 2, 3, 4, 5}
c := [...]int{1, 2, 3, 4} // 通过初始化值确定数组长度。
d := [5]int{2: 100, 4: 200} // 使用引号初始化指定下标的元素。
e := [...]struct {
name string
age uint8
}{
{"user1", 10}, // 可省略元素类型。
{"user2", 20}, // 别忘了最后一行的逗号。
} //结构体数组
fmt.Println(b)
fmt.Println(c)
var twoD [2][3]int
for i := 0; i < 2; i++ {
for j := 0; j < 3; j++ {
twoD[i][j] = i + j
}
}
fmt.Println("2d: ", twoD)
}
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
# 切片 Slice
需要说明,slice 并不是数组或数组指针。它通过内部指针和相关属性引用数组片段,以实现变长方案。
- 切片:切片是数组的一个引用,因此切片是引用类型。但自身是结构体,值拷贝传递。
- 切片的长度可以改变,因此,切片是一个可变的数组。
- 切片遍历方式和数组一样,可以用 len () 求长度。表示可用元素数量,读写操作不能超过该限制。
- cap 可以求出 slice 最大扩张容量,不能超出数组限制。0 <= len (slice) <= len (array),其中 array 是 slice 引用的数组。
- 切片的定义:var 变量名 [] 类型,比如 var str [] string var arr [] int。
- 如果 slice == nil,那么 len、cap 结果都等于 0。
package main
import "fmt"
func main() {
s := make([]string, 3)
s[0] = "a"
s[1] = "b"
s[2] = "c"
fmt.Println("get:", s[2]) // c
fmt.Println("len:", len(s)) // 3
s = append(s, "d") //追加后需要赋值回原切片才能修改原切片,因为如果容量不够时会进行自动扩容变成一个新的切片
s = append(s, "e", "f")
fmt.Println(s) // [a b c d e f]
c := make([]string, len(s)) //指定长度
d := make([]string, len(s), 10) //指定长度和10的容量
copy(c, s) //可以使用copy进行两个切片元素复制
fmt.Println(c) // [a b c d e f]
fmt.Println(s[2:5]) // [c d e] 前闭后开,不支持Python中的负下标
fmt.Println(s[:5]) // [a b c d e]
fmt.Println(s[2:]) // [c d e f]
good := []string{"g", "o", "o", "d"}
fmt.Println(good) // [g o o d]
}
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
# Map
map 类型的变量默认初始值为 nil,需要使用 make () 函数来分配内存。
package main
import "fmt"
func main() {
m := make(map[string]int) //其中[string]为key的类型 int为value的类型
m2 := make(map[string]int, 3) //指定map的cap容量 3
m["one"] = 1
m["two"] = 2
fmt.Println(m) // map[one:1 two:2]
fmt.Println(len(m)) // 2
fmt.Println(m["one"]) // 1
fmt.Println(m["unknow"]) // 0
r, ok := m["unknow"] //获取map中的元素 ok则为map是否存在指定的key
fmt.Println(r, ok) // 0 false
delete(m, "one") //删除指定key
m2 := map[string]int{"one": 1, "two": 2}
var m3 = map[string]int{"one": 1, "two": 2}
fmt.Println(m2, m3)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Range
使用 Range 快速遍历数组或切片
package main
import "fmt"
func main() {
nums := []int{2, 3, 4}
sum := 0
for i, num := range nums { //返回索引和元素值
sum += num
if num == 2 {
fmt.Println("index:", i, "num:", num) // index: 0 num: 2
}
}
fmt.Println(sum) // 9
m := map[string]string{"a": "A", "b": "B"}
for k, v := range m { //返回 key和value
fmt.Println(k, v) // b 8; a A
}
for k := range m { //只遍历key
fmt.Println("key", k) // key a; key b
}
for i := range s { //支持 string/array/slice/map。
fmt.Printf("%c\n", s[i])
}
for _, c := range s { // 忽略 index
fmt.Printf("%c\n", c)
}
}
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
# 函数
语法
func FuncName(/*参数列表*/) (o1 type1, o2 type2/*返回类型*/) {
//函数体
return v1, v2 //返回多个值
}
2
3
4
5
- func:函数由关键字 func 开始声明
- FuncName:函数名称,根据约定,函数名首字母小写即为 private,大写即为 public
- 参数列表:函数可以有 0 个或多个参数,参数格式为:变量名 类型,如果有多个参数通过逗号分隔,不支持默认参数
- 返回类型:
- 上面返回值声明了两个变量名 o1 和 o2 (命名返回参数),这个不是必须,可以只有类型没有变量名
- 如果只有一个返回值且不声明返回值变量,那么你可以省略,包括返回值的括号
- 如果没有返回值,那么就直接省略最后的返回信息
- 如果有返回值, 那么必须在函数的内部添加 return 语句
package main
import "fmt"
func add(a int, b int) int {
return a + b
}
func add2(a, b int) int {
return a + b
}
//可以返回多个类型
func exists(m map[string]string, k string) (v string, ok bool) {
v, ok = m[k]
return v, ok
}
func Test01() (int, string) { //方式 2
return 250, "sb"
}
func Test02() (a int, str string) { //方式 3, 给返回值命名
a = 250
str = "sb"
return
}
func main() {
res := add(1, 2)
fmt.Println(res) // 3
v, ok := exists(map[string]string{"a": "A"}, "a")
fmt.Println(v, ok) // A True
v1, v2 := Test01() //函数调用
_, v3 := Test02() //函数调用, 第一个返回值丢弃
v4, _ := Test02() //函数调用, 第二个返回值丢弃
fmt.Printf("v1 = %d, v2 = %s, v3 = %s, v4 = %d\n", v1, v2, v3, v4)
}
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
# 不定参数列表
不定参数是指函数传入的参数个数为不定数量。为了做到这点,首先需要将函数定义为接受
//形如...type 格式的类型只能作为函数的参数类型存在,并且必须是最后一个参数
func Test(args ...int) {
for _, n := range args { //遍历参数列表
fmt.Println(n)
}
}
func main() {
//函数调用,可传 0 到多个参数
Test()
Test(1)
Test(1, 2, 3, 4)
}
2
3
4
5
6
7
8
9
10
11
12
13
# 函数类型
函数也是一种数据类型,我们可以通过 type 来定义它,它的类型就是所有拥有相同的参数,相同的返回值的一种
类型。
type FuncType func(int, int) int //声明一个函数类型, func 后面没有函数名
//函数中有一个参数类型为函数类型:f FuncType
func Calc(a, b int, f FuncType) (result int) {
result = f(a, b) //通过调用 f()实现任务
return
}
func Add(a, b int) int {
return a + b
}
func Minus(a, b int) int {
return a - b
}
func main() {
//函数调用,第三个参数为函数名字,此函数的参数,返回值必须和 FuncType 类型一致
result := Calc(1, 1, Add)
fmt.Println(result) //2
var f FuncType = Minus
fmt.Println("result = ", f(10, 2)) //result = 8
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 匿名函数与闭包
所谓闭包就是一个函数 “捕获” 了和它在同一作用域的其它常量和变量。这就意味着当闭包被调用的时候,不管在程序什么地方调用,闭包能够使用这些常量或者变量。它不关心这些捕获了的变量和常量是否已经超出了作用域,所以只有闭包还在使用它,这些变量就还会存在。
在 Go 语言里,所有的匿名函数 (Go 语言规范中称之为函数字面量) 都是闭包。
func main() {
i := 0
str := "mike"
//方式 1
f1 := func() { //匿名函数,无参无返回值
//引用到函数外的变量
fmt.Printf("方式 1:i = %d, str = %s\n", i, str)
}
f1() //函数调用
//方式 1 的另一种方式
type FuncType func() //声明函数类型, 无参无返回值
var f2 FuncType = f1
f2() //函数调用
//方式 2
var f3 FuncType = func() {
fmt.Printf("方式 2:i = %d, str = %s\n", i, str)
}
f3() //函数调用
//方式 3
func() { //匿名函数,无参无返回值
fmt.Printf("方式 3:i = %d, str = %s\n", i, str)
}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
//方式 4, 匿名函数,有参有返回值
v := func(a, b int) (result int) {
result = a + b
return
}(1, 1) //别忘了后面的(1, 1), (1, 1)的作用是,此处直接调用此匿名函数, 并传参
fmt.Println("v = ", v)
}
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
闭包
func main() {
i := 10
str := "mike"
func() {
i = 100
str = "go"
//内部:i = 100, str = go
fmt.Printf("内部:i = %d, str = %s\n", i, str)
}() //别忘了后面的(), ()的作用是,此处直接调用此匿名函数
//外部:i = 100, str = go
fmt.Printf("外部:i = %d, str = %s\n", i, str)
}
2
3
4
5
6
7
8
9
10
11
12
# 指针
Go 语言虽然保留了指针,但与其它编程语言不同的是:
- 默认值 nil,没有 NULL 常量
- 不支持指针运算,不支持 "->" 运算符,直接⽤ "." 访问目标成员
package main
import "fmt"
func add2(n int) {
n += 2
}
//传递指针地址
func add2ptr(n *int) {
//解析指针地址并修改
*n += 2
}
func main() {
n := 5
add2(n)
fmt.Println(n) // 5
add2ptr(&n) //取地址
fmt.Println(n) // 7
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# new 函数
表达式 new (T) 将创建一个 T 类型的匿名变量,所做的是为 T 类型的新值分配并清零一块内存空间,然后将这块内存空间的地址作为结果返回,而这个结果就是指向这个新的 T 类型值的指针值,返回的指针类型为 * T。
func main() {
var p1 *int
p1 = new(int) //p1 为*int 类型, 指向匿名的 int 变量
fmt.Println("*p1 = ", *p1) //*p1 = 0
p2 := new(int) //p2 为*int 类型, 指向匿名的 int 变量
*p2 = 111
fmt.Println("*p2 = ", *p2) //*p1 = 111
}
2
3
4
5
6
7
8
# 指针做参数
func swap01(a, b int) {
a, b = b, a
fmt.Printf("swap01 a = %d, b = %d\n", a, b)
}
func swap02(x, y *int) {
*x, *y = *y, *x
}
func main() {
a := 10
b := 20
//swap01(a, b) //值传递
swap02(&a, &b) //变量地址传递
fmt.Printf("a = %d, b = %d\n", a, b)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 结构体
package main
import "fmt"
type user struct {
name string
password string
}
func main() {
a := user{name: "wang", password: "1024"}
b := user{"wang", "1024"} //按顺序赋值
c := user{name: "wang"} // 只指定其中一部分
c.password = "1024"
var d user
d.name = "wang" //读取结构体的内容
d.password = "1024"
fmt.Println(a, b, c, d) // {wang 1024} {wang 1024} {wang 1024} {wang 1024}
fmt.Println(checkPassword(a, "haha")) // false
fmt.Println(checkPassword2(&a, "haha")) // false
}
//结构体作为参数
func checkPassword(u user, password string) bool {
return u.password == password
}
//结构体指针作为参数
func checkPassword2(u *user, password string) bool {
return u.password == password
}
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
# 结构体方法
package main
import "fmt"
type user struct {
name string
password string
}
//声明一个属于user结构体的checkPassword方法
func (u user) checkPassword(password string) bool {
return u.password == password
}
//带指针可以方便对结构体进行修改
func (u *user) resetPassword(password string) {
u.password = password
}
func main() {
a := user{name: "wang", password: "1024"}
a.resetPassword("2048")
fmt.Println(a.checkPassword("2048")) // true
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 异常处理
Go 语言引入了一个关于错误处理的标准模式,即 error 接口,它是 Go 语言内建的接口类型,该接口的定义如下:
type error interface {
Error() string
}
2
3
案例
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
//返回值带 error 类型,表明函数可能会有错误
func findUser(users []user, name string) (v *user, err error) {
for _, u := range users {
if u.name == name {
//没有错误返回结果和nil
return &u, nil
}
}
//出现错误返回错误结果
return nil, errors.New("not found")
}
func main() {
//如果函数声明了会携带error,则必须接受error结果
u, err := findUser([]user{{"wang", "1024"}}, "wang")
//先判断错误是否存在,再进行操作
if err != nil {
fmt.Println(err)
return
}
//如不判断错误是否存在,则u可能为空指针错误
fmt.Println(u.name) // wang
if u, err := findUser([]user{{"wang", "1024"}}, "li"); err != nil {
fmt.Println(err) // not found
return
} else {
fmt.Println(u.name)
}
}
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
还有另一个可以生成 error 类型值的方法是调用 fmt 包中的 Errorf 函数:
import (
"errors"
"fmt"
)
func main() {
var err1 error = errors.New("a normal err1") //新建一个异常,可用于返回或者直接输出
fmt.Println(err1) //a normal err1
var err2 error = fmt.Errorf("%s", "a normal err2")
fmt.Println(err2) //a normal err2
}
2
3
4
5
6
7
8
9
10
11
返回异常案例
func Divide(a, b float64) (result float64, err error) {
if b == 0 {
result = 0.0
err = errors.New("runtime error: divide by zero")
return
}
result = a / b
err = nil
return
}
func main() {
r, err := Divide(10.0, 0)
if err != nil {
fmt.Println(err) //错误处理 runtime error: divide by zero
} else {
fmt.Println(r) // 使用返回值
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# panic
当遇到不可恢复的错误状态的时候,如数组访问越界、空指针引用等,这些运行时错误会引起 painc 异常。使用 error 类型值处理显然就不适合了。我们不应通过调用 panic 函数来报告普通的错误,而应该只把它作为报告致错误的一种方式。
一般而言,当 panic 异常发生时,程序会中断运行,并立即执行在该 goroutine。随后,程序崩溃并输出日志信息。日志信息包括 panic value 和函数调用的堆栈跟踪信息。
func TestA() {
fmt.Println("func TestA()")
}
func TestB() {
panic("func TestB(): panic")
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
TestB() //TestB()发生异常,中断程序
TestC()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

# recover
运行时 panic 异常一旦被引发就会导致程序崩溃。这当然不是我们愿意看到的,因为谁也不能保证程序不会发生任何运行时错误。 不过,Go 语言为我们提供了专用于 “拦截” 运行时 panic 的内建函数 ——recover。它可以是当前的程序从运行时 panic 的状态中恢复并重新获得流程控制权。
注意:recover 只有在 defer 调用的函数中有效。
func TestA() {
fmt.Println("func TestA()")
}
func TestB() (err error) {
defer func() { //在发生异常时,设置恢复
if x := recover(); x != nil {
//panic value 被附加到错误信息中;
//并用 err 变量接收错误信息,返回给调用者。
err = fmt.Errorf("internal error: %v", x)
}
}()
panic("func TestB(): panic")
}
func TestC() {
fmt.Println("func TestC()")
}
func main() {
TestA()
err := TestB()
fmt.Println(err)
TestC()
/*
运行结果:
func TestA()
internal error: func TestB(): panic
func TestC()
*/
}
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
# 字符串
# 字符串操作
package main
import (
"fmt"
"strings"
)
func main() {
a := "hello"
fmt.Println(strings.Contains(a, "ll")) // true 判断字符串是否包含指定字符串
fmt.Println(strings.Count(a, "l")) // 2 统计指定字符串出现次数
fmt.Println(strings.HasPrefix(a, "he")) // true 判断字符串是否是指定字符串开头
fmt.Println(strings.HasSuffix(a, "llo")) // true 判断字符串是否是指定字符串结尾
fmt.Println(strings.Index(a, "ll")) // 2 查找指定字符串下标
fmt.Println(strings.Join([]string{"he", "llo"}, "-")) // he-llo 连接多个字符串
fmt.Println(strings.Repeat(a, 2)) // hellohello 重复指定字符串
fmt.Println(strings.Replace(a, "e", "E", -1)) // hEllo 替换
fmt.Println(strings.Split("a-b-c", "-")) // [a b c] 拆分
fmt.Println(strings.ToLower(a)) // hello 转小写
fmt.Println(strings.ToUpper(a)) // HELLO 转大写
fmt.Println(len(a)) // 5 字符串长度
b := "你好"
fmt.Println(len(b)) // 6
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 字符串格式化
package main
import "fmt"
type point struct {
x, y int
}
func main() {
s := "hello"
n := 123
p := point{1, 2}
fmt.Println(s, n) // hello 123
fmt.Println(p) // {1 2}
fmt.Printf("s=%v\n", s) // s=hello %v格式化输出支持任意类型
fmt.Printf("n=%v\n", n) // n=123
fmt.Printf("p=%v\n", p) // p={1 2}
fmt.Printf("p=%+v\n", p) // p={x:1 y:2} %+v带结构体属性名格式化
fmt.Printf("p=%#v\n", p) // p=main.point{x:1, y:2} %#v文件名+结构体属性
f := 3.141592653
fmt.Println(f) // 3.141592653
fmt.Printf("%.2f\n", f) // 3.14 浮点型格式化
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# JSON
package main
import (
"encoding/json"
"fmt"
)
//需要保证字段是以大写字母开头
type userInfo struct {
Name string
Age int `json:"age"` //默认以字段作为key,如需要小写则需要在类型后面使用json别名
Hobby []string
}
func main() {
a := userInfo{Name: "wang", Age: 18, Hobby: []string{"Golang", "TypeScript"}}
buf, err := json.Marshal(a) //序列化
if err != nil {
panic(err)
}
fmt.Println(buf) // [123 34 78 97...]
fmt.Println(string(buf)) //需要转为字符串输出 {"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
buf, err = json.MarshalIndent(a, "", "\t")
if err != nil {
panic(err)
}
fmt.Println(string(buf))
var b userInfo
err = json.Unmarshal(buf, &b) //反序列化
if err != nil {
panic(err)
}
fmt.Printf("%#v\n", b) // main.userInfo{Name:"wang", Age:18, Hobby:[]string{"Golang", "TypeScript"}}
}
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
# 文件操作
写文件
import (
"fmt"
"os"
)
func main() {
fout, err := os.Create("./xxx.txt") //新建文件
//fout, err := os.OpenFile("./xxx.txt", os.O_CREATE, 0666)
if err != nil {
fmt.Println(err)
return
}
defer fout.Close() //main 函数结束前, 关闭文件
for i := 0; i < 5; i++ {
outstr := fmt.Sprintf("%s:%d\n", "Hello go", i)
fout.WriteString(outstr) //写入 string 信息到文件
fout.Write([]byte("abcd\n")) //写入 byte 类型的信息到文件
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
读文件
func main() {
fin, err := os.Open("./xxx.txt") //打开文件
if err != nil {
fmt.Println(err)
}
defer fin.Close()
buf := make([]byte, 1024) //开辟 1024 个字节的 slice 作为缓冲
for {
n, _ := fin.Read(buf) //读文件
if n == 0 { //0 表示已经到文件结束
break
}
fmt.Println(string(buf)) //输出读取的内容
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
拷贝文件
import (
"fmt"
"io"
"os"
)
func main() {
args := os.Args //获取用户输入的所有参数
//如果用户没有输入,或参数个数不够,则调用该函数提示用户
if args == nil || len(args) != 3 {
fmt.Println("useage : xxx srcFile dstFile")
return
}
srcPath := args[1] //获取输入的第一个参数
dstPath := args[2] //获取输入的第二个参数
fmt.Printf("srcPath = %s, dstPath = %s\n", srcPath, dstPath)
if srcPath == dstPath {
fmt.Println("源文件和目的文件名字不能相同")
return
}
srcFile, err1 := os.Open(srcPath) //打开源文件
if err1 != nil {
fmt.Println(err1)
return
}
dstFile, err2 := os.Create(dstPath) //创建目的文件
if err2 != nil {
fmt.Println(err2)
return
}
buf := make([]byte, 1024) //切片缓冲区
for {
//从源文件读取内容,n 为读取文件内容的长度
n, err := srcFile.Read(buf)
if err != nil && err != io.EOF {
fmt.Println(err)
break
}
if n == 0 {
fmt.Println("文件处理完毕")
break
}
//切片截取
tmp := buf[:n]
//把读取的内容写入到目的文件
dstFile.Write(tmp)
}
//关闭文件
srcFile.Close()
dstFile.Close()
}
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
# Time
时间类型有一个自带的方法 Format 进行格式化,需要注意的是 Go 语言中格式化时间模板不是常见的 Y-m-d H:M:S 而是使用 Go 的诞生时间 2006 年 1 月 2 号 15 点 04 分(记忆口诀为 2006 1 2 3 4 5)
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now() //当前时间
fmt.Println(now) // 2022-03-27 18:04:59.433297 +0800 CST m=+0.000087933
t := time.Date(2022, 3, 27, 1, 25, 36, 0, time.UTC) //构造Date
t2 := time.Date(2022, 3, 27, 2, 30, 36, 0, time.UTC)
fmt.Println(t) // 2022-03-27 01:25:36 +0000 UTC
fmt.Println(t.Year(), t.Month(), t.Day(), t.Hour(), t.Minute()) // 2022 March 27 1 25
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2022-03-27 01:25:36
diff := t2.Sub(t)
fmt.Println(diff) // 1h5m0s
fmt.Println(diff.Minutes(), diff.Seconds()) // 65 3900
t3, err := time.Parse("2006-01-02 15:04:05", "2022-03-27 01:25:36")
if err != nil {
panic(err)
}
fmt.Println(t3 == t) // true
fmt.Println(now.Unix()) //时间戳 1648738080
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# Strconv
strconv 包实现了基本数据类型与其字符串表示的转换,主要有以下常用函数: Atoi ()、Itia ()、parse 系列、format 系列、append 系列。
package main
import (
"fmt"
"strconv"
)
func main() {
f, _ := strconv.ParseFloat("1.234", 64)
fmt.Println(f) // 1.234
n, _ := strconv.ParseInt("111", 10, 64) //参数1为需要解析的字符串 参数2为进制,如果传递0则自动推导 参数3为64位int类型
fmt.Println(n) // 111
n, _ = strconv.ParseInt("0x1000", 0, 64)
fmt.Println(n) // 4096
n2, _ := strconv.Atoi("123")
fmt.Println(n2) // 123
n2, err := strconv.Atoi("AAA")
fmt.Println(n2, err) // 0 strconv.Atoi: parsing "AAA": invalid syntax
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# 进程信息
Command 函数 os/exec 将允许我们执行此操作。它接受至少一个字符串 - 您要运行的命令 / 二进制文件的名称 - 后接任意数量的字符串作为其参数。
package main
import (
"fmt"
"os"
"os/exec"
)
func main() {
// go run example/20-env/main.go a b c d
fmt.Println(os.Args) // 获取执行参数 第一个为二进制程序执行的临时路径[/var/folders/8p/n34xxfnx38dg8bv_x8l62t_m0000gn/T/go-build3406981276/b001/exe/main a b c d]
fmt.Println(os.Getenv("PATH")) //获取环境变量 /usr/local/go/bin...
fmt.Println(os.Setenv("AA", "BB")) //写入环境变量
//执行命令/程序
buf, err := exec.Command("grep", "127.0.0.1", "/etc/hosts").CombinedOutput()
if err != nil {
panic(err)
}
fmt.Println(string(buf)) // 127.0.0.1 localhost
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 延迟调用(defer)
defer 特性:
- 关键字 defer 用于注册延迟调用。
- 这些调用直到 return 前才被执。因此,可以用来做资源清理。
- 多个 defer 语句,按先进后出的方式执行。
- defer 语句中的变量,在 defer 声明时就决定了。
defer 用途:
- 关闭文件句柄
- 锁资源释放
- 数据库连接释放
defer 是先进后出,后面的语句会依赖前面的资源,因此如果先前面的资源先释放了,后面的语句就没法执行了。
package main
import "fmt"
func main() {
var whatever [5]struct{}
for i := range whatever {
defer fmt.Println(i)
}
}
2
3
4
5
6
7
8
9
10
11
输出结果
4
3
2
1
0
2
3
4
5
# 面向对象
Go 语言中没有封装、继承、多态这些概念,比如继承 (不支持继承,尽管匿名字段的内存布局和行为类似 继承,但它并不是继承)、虚函数、构造函数和析构函数、隐藏的 this 指针等。但同样通过别的方式实现这些特性:
- 封装:通过方法实现
- 继承:通过匿名字段实现
- 多态:通过接口实现
# 匿名字段
Go 支持只提供类型,而不写字段名的方式,也就是匿名字段,也称为嵌入字段
//人
type Person struct {
name string
sex byte
age int
}
//学生
type Student struct {
Person // 匿名字段,那么默认 Student 就包含了 Person 的所有字段
id int
addr string
}
func main() {
//顺序初始化
s1 := Student{Person{"mike", 'm', 18}, 1, "sz"}
//s1 = {Person:{name:mike sex:109 age:18} id:1 addr:sz}
fmt.Printf("s1 = %+v\n", s1)
//s2 := Student{"mike", 'm', 18, 1, "sz"} //err
//部分成员初始化 1
s3 := Student{Person: Person{"lily", 'f', 19}, id: 2}
//s3 = {Person:{name:lily sex:102 age:19} id:2 addr:}
fmt.Printf("s3 = %+v\n", s3)
//部分成员初始化 2
s4 := Student{Person: Person{name: "tom"}, id: 3}
//s4 = {Person:{name:tom sex:0 age:0} id:3 addr:}
fmt.Printf("s4 = %+v\n", s4)
}
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
所有的内置类型和自定义类型都是可以作为匿名字段的
type mystr string //自定义类型
type Person struct {
name string
sex byte
age int
}
type Student struct {
Person // 匿名字段,结构体类型
int // 匿名字段,内置类型
mystr // 匿名字段,自定义类型
}
func main() {
//初始化
s1 := Student{Person{"mike", 'm', 18}, 1, "bj"}
//{Person:{name:mike sex:109 age:18} int:1 mystr:bj}
fmt.Printf("%+v\n", s1)
//成员的操作,打印结果:mike, m, 18, 1, bj
fmt.Printf("%s, %c, %d, %d, %s\n", s1.name, s1.sex, s1.age, s1.int,
s1.mystr)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
结构体指针类型
type Person struct { //人
name string
sex byte
age int
}
type Student struct { //学生
*Person // 匿名字段,结构体指针类型
id int
addr string
}
func main() {
//初始化
s1 := Student{&Person{"mike", 'm', 18}, 1, "bj"}
//{Person:0xc0420023e0 id:1 addr:bj}
fmt.Printf("%+v\n", s1)
//mike, m, 18
fmt.Printf("%s, %c, %d\n", s1.name, s1.sex, s1.age)
//声明变量
var s2 Student
s2.Person = new(Person) //分配空间
s2.name = "yoyo"
s2.sex = 'f'
s2.age = 20
s2.id = 2
s2.addr = "sz"
//yoyo 102 20 2 20
fmt.Println(s2.name, s2.sex, s2.age, s2.id, s2.age)
}
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
# 成员的操作
var s1 Student //变量声明
//给成员赋值
s1.name = "mike" //等价于 s1.Person.name = "mike"
s1.sex = 'm'
s1.age = 18
s1.id = 1
s1.addr = "sz"
fmt.Println(s1) //{{mike 109 18} 1 sz}
var s2 Student //变量声明
s2.Person = Person{"lily", 'f', 19}
s2.id = 2
s2.addr = "bj"
fmt.Println(s2) //{{lily 102 19} 2 bj}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 同名字段
//人
type Person struct {
name string
sex byte
age int
}
//学生
type Student struct {
Person // 匿名字段,那么默认 Student 就包含了 Person 的所有字段
id int
addr string
name string //和 Person 中的 name 同名
}
func main() {
var s Student //变量声明
//给 Student 的 name,还是给 Person 赋值?
s.name = "mike"
//{Person:{name: sex:0 age:0} id:0 addr: name:mike}
fmt.Printf("%+v\n", s)
//默认只会给最外层的成员赋值
//给匿名同名成员赋值,需要显示调用
s.Person.name = "yoyo"
//Person:{name:yoyo sex:0 age:0} id:0 addr: name:mike}
fmt.Printf("%+v\n", s)
}
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
# 方法
⽅法总是绑定对象实例,并隐式将实例作为第⼀实参 (receiver),方法的语法如下:
func (receiver ReceiverType) funcName(parameters) (results)
- 参数 receiver 可任意命名。如⽅法中未曾使⽤,可省略参数名。
- 参数 receiver 类型可以是 T 或 *T。基类型 T 不能是接口或指针。
- 不支持重载方法,也就是说,不能定义名字相同但是不同参数的方法。
# 基础类型作为接收者
type MyInt int //自定义类型,给 int 改名为 MyInt
//在函数定义时,在其名字之前放上一个变量,即是一个方法
func (a MyInt) Add(b MyInt) MyInt { //面向对象
return a + b
}
//传统方式的定义
func Add(a, b MyInt) MyInt { //面向过程
return a + b
}
func main() {
var a MyInt = 1
var b MyInt = 1
//调用 func (a MyInt) Add(b MyInt)
fmt.Println("a.Add(b) = ", a.Add(b)) //a.Add(b) = 2
//调用 func Add(a, b MyInt)
fmt.Println("Add(a, b) = ", Add(a, b)) //Add(a, b) = 2
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 结构体作为接收者
方法里面可以访问接收者的字段,调用方法通过点 (.) 访问,就像 struct 里面访问字段一样:
type Person struct {
name string
sex byte
age int
}
func (p Person) PrintInfo() { //给 Person 添加方法
fmt.Println(p.name, p.sex, p.age)
}
func main() {
p := Person{"mike", 'm', 18} //初始化
p.PrintInfo() //调用 func (p Person) PrintInfo()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
# 结构体指针作为接受者
type Person struct {
name string
sex byte
age int
}
//指针作为接收者,引用语义
func (p *Person) SetInfoPointer() {
//给成员赋值
(*p).name = "yoyo"
p.sex = 'f'
p.age = 22
}
//值作为接收者,值语义
func (p Person) SetInfoValue() {
//给成员赋值
p.name = "yoyo"
p.sex = 'f'
p.age = 22
}
func main() {
//指针作为接收者,引用语义
p1 := Person{"mike", 'm', 18} //初始化
fmt.Println("函数调用前 = ", p1) //函数调用前 = {mike 109 18}
(&p1).SetInfoPointer()
fmt.Println("函数调用后 = ", p1) //函数调用后 = {yoyo 102 22}
fmt.Println("==========================")
p2 := Person{"mike", 'm', 18} //初始化
//值作为接收者,值语义
fmt.Println("函数调用前 = ", p2) //函数调用前 = {mike 109 18}
p2.SetInfoValue()
fmt.Println("函数调用后 = ", p2) //函数调用后 = {mike 109 18}
}
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
# 方法集
方法集是指可以被该类型的值调用的所有方法的集合。
用实例实例 value 和 pointer 调用方法(含匿名字段)不受方法集约束,编译器编总是查找全部方法,并自动转换 receiver 实参。
一个指向自定义类型的值的指针,它的方法集由该类型定义的所有方法组成,无论这些方法接受的是一个值还是一个指针。
type Person struct {
name string
sex byte
age int
}
//指针作为接收者,引用语义
func (p *Person) SetInfoPointer() {
(*p).name = "yoyo"
p.sex = 'f'
p.age = 22
}
//值作为接收者,值语义
func (p Person) SetInfoValue() {
p.name = "xxx"
p.sex = 'm'
p.age = 33
}
func main() {
//p 为指针类型
var p *Person = &Person{"mike", 'm', 18}
p.SetInfoPointer() //func (p) SetInfoPointer()
p.SetInfoValue() //func (*p) SetInfoValue()
(*p).SetInfoValue() //func (*p) SetInfoValue()
//p 为普通值类型
var p Person = Person{"mike", 'm', 18}
(&p).SetInfoPointer() //func (&p) SetInfoPointer()
p.SetInfoPointer() //func (&p) SetInfoPointer()
p.SetInfoValue() //func (p) SetInfoValue()
(&p).SetInfoValue() //func (*&p) SetInfoValue()
}
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
# 匿名字段中方法的重写
如果匿名字段实现了一个方法,那么包含这个匿名字段的 struct 也能调用该方法,并可以重写该匿名字段的方法。
type Person struct {
name string
sex byte
age int
}
//Person 定义了方法
func (p *Person) PrintInfo() {
fmt.Printf("Person: %s,%c,%d\n", p.name, p.sex, p.age)
}
type Student struct {
Person // 匿名字段,那么 Student 包含了 Person 的所有字段
id int
addr string
}
//Student 定义了方法
func (s *Student) PrintInfo() {
fmt.Printf("Student:%s,%c,%d\n", s.name, s.sex, s.age)
}
func main() {
p := Person{"mike", 'm', 18}
p.PrintInfo() //Person: mike,m,18
s := Student{Person{"yoyo", 'f', 20}, 2, "sz"}
s.PrintInfo() //Student:yoyo,f,20
s.Person.PrintInfo() //Person: yoyo,f,20
}
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
# 表达式
类似于我们可以对函数进行赋值和传递一样,方法也可以进行赋值和传递。
type Person struct {
name string
sex byte
age int
}
func (p *Person) PrintInfoPointer() {
fmt.Printf("%p, %v\n", p, p)
}
func (p Person) PrintInfoValue() {
fmt.Printf("%p, %v\n", &p, p)
}
func main() {
p := Person{"mike", 'm', 18}
p.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}
pFunc1 := p.PrintInfoPointer //方法值,隐式传递 receiver
pFunc1() //0xc0420023e0, &{mike 109 18}
pFunc2 := p.PrintInfoValue
pFunc2() //0xc042048420, {mike 109 18}
p2 := Person{"mike", 'm', 18}
p2.PrintInfoPointer() //0xc0420023e0, &{mike 109 18}
//方法表达式, 须显式传参
//func pFunc1(p *Person))
pFunc3 := (*Person).PrintInfoPointer
pFunc3(&p) //0xc0420023e0, &{mike 109 18}
pFunc4 := Person.PrintInfoValue
pFunc4(p) //0xc042002460, {mike 109 18}
}
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
# 接口
接口类型是一种抽象的类型,它不会暴露出它所代表的对象的内部值的结构和这个对象支持的基础操作的集合,它们只会展示出它们自己的方法。因此接口类型不能将其实例化。
type Humaner interface {
SayHi()
}
2
3
- 接口命名习惯以 er 结尾
- 接口只有方法声明,没有实现,没有数据字段
- 接口可以匿名嵌入其它接口,或嵌入到结构中
# 接口实现
接口是用来定义行为的类型。这些被定义的行为不由接口直接实现,而是通过方法由用户定义的类型实现,一个实现了这些方法的具体类型是这个接口类型的实例。
type Humaner interface {
SayHi()
}
type Student struct { //学生
name string
score float64
}
//Student 实现 SayHi()方法
func (s *Student) SayHi() {
fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}
type Teacher struct { //老师
name string
group string
}
//Teacher 实现 SayHi()方法
func (t *Teacher) SayHi() {
fmt.Printf("Teacher[%s, %s] say hi!!\n", t.name, t.group)
}
type MyStr string
//MyStr 实现 SayHi()方法
func (str MyStr) SayHi() {
fmt.Printf("MyStr[%s] say hi!!\n", str)
}
//普通函数,参数为 Humaner 类型的变量 i
func WhoSayHi(i Humaner) {
i.SayHi()
}
func main() {
s := &Student{"mike", 88.88}
t := &Teacher{"yoyo", "Go 语言"}
var tmp MyStr = "测试"
s.SayHi() //Student[mike, 88.880000] say hi!!
t.SayHi() //Teacher[yoyo, Go 语言] say hi!!
tmp.SayHi() //MyStr[测试] say hi!!
//多态,调用同一接口,不同表现
WhoSayHi(s) //Student[mike, 88.880000] say hi!!
WhoSayHi(t) //Teacher[yoyo, Go 语言] say hi!!
WhoSayHi(tmp) //MyStr[测试] say hi!!
x := make([]Humaner, 3)
//这三个都是不同类型的元素,但是他们实现了 interface 同一个接口
x[0], x[1], x[2] = s, t, tmp
for _, value := range x {
value.SayHi()
}
/*
Student[mike, 88.880000] say hi!!
Teacher[yoyo, Go 语言] say hi!!
MyStr[测试] say hi!!
*/
}
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
# 接口组合
# 嵌入
如果一个 interface1 作为 interface2 的一个嵌入字段,那么 interface2 隐式的包含了 interface1 里面的方法。
type Humaner interface {
SayHi()
}
type Personer interface {
Humaner //这里像写了 SayHi()一样
Sing(lyrics string)
}
type Student struct { //学生
name string
score float64
}
//Student 实现 SayHi()方法
func (s *Student) SayHi() {
fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}
//Student 实现 Sing()方法
func (s *Student) Sing(lyrics string) {
fmt.Printf("Student sing[%s]!!\n", lyrics)
}
func main() {
s := &Student{"mike", 88.88}
var i2 Personer
i2 = s
i2.SayHi() //Student[mike, 88.880000] say hi!!
i2.Sing("学生哥") //Student sing[学生哥]!!
}
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
# 转换
超集接口对象可转换为⼦集接口,反之出错:
type Humaner interface {
SayHi()
}
type Personer interface {
Humaner //这里像写了 SayHi()一样
Sing(lyrics string)
}
type Student struct { //学生
name string
score float64
}
//Student 实现 SayHi()方法
func (s *Student) SayHi() {
fmt.Printf("Student[%s, %f] say hi!!\n", s.name, s.score)
}
//Student 实现 Sing()方法
func (s *Student) Sing(lyrics string) {
fmt.Printf("Student sing[%s]!!\n", lyrics)
}
func main() {
//var i1 Humaner = &Student{"mike", 88.88}
//var i2 Personer = i1 //err
//Personer 为超集,Humaner 为子集
var i1 Personer = &Student{"mike", 88.88}
var i2 Humaner = i1
i2.SayHi() //Student[mike, 88.880000] say hi!!
}
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
# 空接口
空接口 (interface {}) 不包含任何的方法,正因为如此,所有的类型都实现了空接口,因此空接口可以存储任意类型
的数值。它有点类似于 C 语言的 void * 类型。
var v1 interface{} = 1 // 将 int 类型赋值给 interface{}
var v2 interface{} = "abc" // 将 string 类型赋值给 interface{}
var v3 interface{} = &v2 // 将*interface{}类型赋值给 interface{}
var v4 interface{} = struct{ X int }{1}
var v5 interface{} = &struct{ X int }{1}
2
3
4
5
# 类型查询
interface 的变量里面可以存储任意类型的数值 (该类型实现了 interface)。那么我们怎么反向知道这个变量里面实际保存了的是哪个类型的对象呢?
- comma-ok 断言
- switch 测试
Go 语言里面有一个语法,可以直接判断是否是该类型的变量: value, ok = element.(T),这里 value 就是变量的值,ok 是一个 bool 类型,element 是 interface 变量,T 是断言的类型。
如果 element 里面确实存储了 T 类型的数值,那么 ok 返回 true,否则返回 false。
type Element interface{}
type Person struct {
name string
age int
}
func main() {
list := make([]Element, 3)
list[0] = 1 // an int
list[1] = "Hello" // a string
list[2] = Person{"mike", 18}
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, %d]\n",
index, value.name, value.age)
} else {
fmt.Printf("list[%d] is of a different type\n", index)
}
}
/* 打印结果:
list[0] is an int and its value is 1
list[1] is a string and its value is Hello
list[2] is a Person and its value is [mike, 18]
*/
}
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
Switch 测试
type Element interface{}
type Person struct {
name string
age int
}
func main() {
list := make([]Element, 3)
list[0] = 1 //an int
list[1] = "Hello" //a string
list[2] = Person{"mike", 18}
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, %d]\n",
index, value.name, value.age)
default:
fmt.Println("list[%d] is of a different type", index)
}
}
}
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