GO基础语法
LDK Lv4

本篇主要用于记录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
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
package main

import (
"fmt"
"strconv"
"time"
)

// 变量
func testVar() {
// 变量声明: 不给初始值, int 类型 默认初始值为0
var num0 int
fmt.Println(num0) // 默认为0

// 声明的同时给初始值, 可以省略类型:
var numString = "123456" // 省略了类型 string
// 等同于: var num_string string = "123456"
fmt.Println("num_1: ", numString)
// 上面一行等同于 fmt.Println("num_1: " + num_string)

var num1 int = 12
fmt.Printf("type of num_1: %T\n", num1) // 通过Printf和占位符 %T 判断变量类型

// 最常用的变量定义方法:
num2 := 3.14 // 只能在函数体内使用这种方式, 在全局位置(所有函数体的外面)使用这种声明会报错
fmt.Printf("num_2 = %f, type is num_2: %T\n", num2, num2) // %T是类型占位符

// 声明多个同类型的变量
var xx, yy = 1, 2
fmt.Printf("xx = %d, yy = %d\n", xx, yy)
// 声明多个不同类型的变量
var kk, ll = 100, "str" // 此处的 var 不能省略
fmt.Printf("kk = %d, ll = %s\n", kk, ll)

// 多行多变量声明
var (
var1 int = 100 // 此处类型也可省略!
var2 bool = true
var3 float32 = 3.14
)
fmt.Println("var1 =", var1, "var2 =", var2, "var3 =", var3)
}

// const 关键字
func testConst() {
// 将 var 替换为 const, 即可定义常量.
const length int = 10 // 类型依旧可以省略
fmt.Println(length)

const (
BEIJING = iota // 定义 BEIJING 的值为0, 后面每一行累加1
SHANGHAI
SHENZHEN
GUANGZHOU
)
// iota 只能出现在 const 块中

const (
NUM_1 = iota * 10 // iota 从0开始地层
NUM_2 // 10 因为 iota 是每次+1, 所以变量每次 + 10
NUM_3 // 20
NUM_4 // 30
)

const (
a, b = iota + 1, iota + 2 // iota=0, a=iota+1, b=iota+2
c, d // iota=1, c=iota+1, b=iota+2
e, f
g, h = iota * 2, iota * 3 // iota=3, g=iota*2, h=iota*3
i, k // iota=4, i=iota*2, k=iota*3
)
}

// 多返回值
func testMultipleReturn(a int, b int) (int, int) {
return a + b, a * b
}

// 多返回值, 返回值带变量名. 此时对应的变量实际上属于局部变量
func testReturnWithName(a int, b int) (sum int, mul int) {
// sum 和 mul 本质上是局部变量
fmt.Println("before assign, sum= ", sum, " mul= ", mul) // 没有赋值前, 默认为0
sum = a + b
mul = a * b
return
// 也可以不用变量名, 直接用return返回结果, 此时 对变量的赋值将会失效, 以return返回的值为准:
// return 100, 200 // 将会使 sum 和 mul 的值失效
}

// 多返回值, 带变量名, 返回值存在同类型
func testReturnWithNameAndSameType(a int, b int) (sum, mul int, sumStr string) {
sum = a + b
mul = a * b
sumStr = strconv.Itoa(sum)
return
}

func main() { // 函数的左括号一定和函数名在同一行, 否则编译错误
fmt.Println("test go") // 每一句结尾可以不加 ';'
fmt.Println("wait for 1 second")
time.Sleep(time.Second * 1)
fmt.Println("1 second wait finished")

testVar()
testConst()

var a = 10
var b = 20
var sum1, mul1 int = testMultipleReturn(a, b)
fmt.Println("sum1 = ", sum1, " mul1 = ", mul1)
sum1, mul1 = testReturnWithName(a, b)
fmt.Println("sum1 = ", sum1, "mul1 = ", mul1)
sum1, mul1, sumStr := testReturnWithNameAndSameType(a, b)
fmt.Println("sum1 = ", sum1, "mul1 = ", mul1, "sum_str = ", sumStr)
}

import导包顺序和init函数

go语言中,package(包)实际上就是一个文件夹,这个文件夹内可以有很多个.go文件,每个.go文件都可以写init()函数,如果不写,go自己会提供一个默认的init()函数。import "test"时,会依次执行文件夹test中的所有.go文件中的init()函数(此处的依次执行,具体先后顺序暂无定论),当然,如果test文件夹中的.go文件还引入了其他package,则会优先递归执行其他package中的初始化操作。具体顺序见下图:

image

  1. 如果一个包导入了其他包,则首先初始化导入的包。
  2. 然后初始化当前包的常量。
  3. 接下来初始化当前包的变量。
  4. 最后,调用当前包的 init() 函数。

如果两个.go文件属于同一个文件夹,那也意味着他们属于同一个包。
此时不需要import操作,他们也可以相互访问公共接口函数(以大写字母开头的函数)。

测试代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// lib1/lib1.go
package lib1

import "fmt"
import "GoTest/lib2"

func init() {
fmt.Println("lib1 init() called")
}

// 如果要在其他包中使用该函数, 那么函数名必须以大写字母开头
func Test() {
fmt.Println("lib1 Test() called")
lib2.Test()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// lib2/lib2.go
package lib2

import (
"fmt"
)

func init() {
fmt.Println("lib2 init() called")
}

func Test() {
fmt.Println("lib2 Test() called")
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// main.go

package main

// 默认从 $GOROOT 或者 $GOPATH 找package, 因此此处必须是相对于 $GOROOT 或者 $GOPATH 的路径
// 如果配置了 go module 则另当别论
import "GoTest/lib1"
import "GoTest/lib2"

func main() {
lib1.Test()
lib2.Test()
}

func init() {
fmt.Println("main.go init() called")
}

import匿名导包和别名导包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
_ "GoTest/lib1" // 匿名别名导包. 不能使用包内的函数, 但是会自动调用 init 函数
mylib2 "GoTest/lib2" // 别名导包. 起了别名之后原名就不能使用了
// . "GoTest/lib1" // 缺省导包, 可以直接使用lib1中的接口函数而不需要在前面指明lib1
// 例如: Test() 而不是 lib1.Test()
// 如果存在多个包使用缺省方式导入, 那么他们之间不能存在同名的接口函数, 否则会报错: 重定义
// 缺省导包后, 原包名同样不能使用.
// 缺省导包依旧会调用包中的init()函数
"fmt"
)

func main() {
// lib1.Test()
mylib2.Test()
}

func init() {
fmt.Println("main.go init() called")
}

指针与引用

C/C++差不多,只不过是反着写的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package main

import "fmt"

func changeValue(p *int) {
*p = 100
fmt.Println("address of a = ", p)
}

func main() {
var a int = 10
changeValue(&a)
fmt.Println("a = ", a)

var p *int = &a
fmt.Println("A point point to a = ", p)
var pp **int = &p // 二级指针
fmt.Println("A point point to p = ", pp)
}

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
package main

import "fmt"

func deferCall() {
fmt.Println("deferCall() called")
}

func returnCall() int {
fmt.Println("returnCall() called")
return 1
}

func test() int {
defer fmt.Println("first defer") // 先入栈, 后执行
defer fmt.Println("second defer") // 后入栈, 先执行
defer deferCall() // defer 后面也可以跟函数
return returnCall() // return 语句比 defer 语句先执行
}

func main() {

test()

fmt.Println("main() called")
}

上述代码的输出:

1
2
3
4
5
returnCall() called
deferCall() called
second defer
first defer
main() called

数组

定长数组

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
package main

import "fmt"

// 用定长数组作为函数参数, 必须指明数组长度, 并且是深拷贝(拷贝了一整个数组)
// 因此一般不这么用, 如果要传递数组, 应该考虑动态数组
func printArrayLen_10(arr [10]int) {
for i := 0; i < len(arr); i++ {
fmt.Print(arr[i], " ")
}
fmt.Println()
}

func main() {
// 固定长度的数组
var array1 [10]int
array2 := [10]int{10, 20, 30, 40} // 前4个元素给初始化值, 后面的元素默认值都为0

printArrayLen_10(array1)

// 另一种 for 循环遍历方法, index表示当前元素的下标, value表示当前元素的值
for index, value := range array2 {
fmt.Println("index = ", index, "value = ", value)
}

// 查看数组的数据类型:
fmt.Printf("array1 types is %T\n", array1)
fmt.Printf("array2 types is %T\n", array2)
}

动态数组(切片 slice)

基本使用
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
package main

import "fmt"

// 动态数组传参是浅拷贝, 内部更改外部也会生效
// 浅拷贝的根本原因:
// - GO语言中, 切片slice是引用类型, 管理底层数组, 多个切片可以共享同一个底层数组.
// - 可以理解为在GO语言中, 默认一个切片赋值给其他切片时, 类似于C++中, 复制一个对象内部管理的指针给另一个对象
// - 而在GO语言中, 如果要实现切片的复制操作(深拷贝), 需要调用make和copy两个函数
//
// 复制切片案例:
// src := []int{1, 2, 3}
// dst := make([]int, len(src))
// copy(dst, src)
//
// 单纯执行 dst := src 只不过是让dst也指向src管理的内部数组而已, 实际上两者还是同一个数组
func changeValue(arr []int) {
if len(arr) > 0 {
arr[0] = 100
}
}

func printArray(arr []int) {
// _ 表示匿名, 即不关心下标
for _, v := range arr {
fmt.Print(v, " ")
}
fmt.Println()
}

func main() {
array1 := []int{10, 20, 30, 40} // 动态数组, 初始化为4个元素. 也叫 切片 slice
changeValue(array1)
printArray(array1)
}
切片的4种定义方式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package main

import "fmt"

func main() {
// 1. 声明并初始化
arr1 := []int{10, 20, 30, 40}
fmt.Println(arr1)

// 2. 声明, 但不初始化. 为空, 同样也就无法使用
var arr2 []int
fmt.Println(arr2) // 为空
arr2 = make([]int, 10) // 分配空间
fmt.Println("arr2: ", arr2)

// 3. 声明, 同时分配空间
var arr3 []int = make([]int, 10)
fmt.Println("arr3: ", arr3)

// 4. 直接make, 通过 := 推导出类型
arr4 := make([]int, 10)
fmt.Println("arr4: ", arr4)
}
判断slice是否为空

在go中,空值用nil表示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import "fmt"

func main() {
arr1 := []int{} // 此种方式不为空, 长度为0但是有分配空间
arr2 := make([]int, 0) // 不为空
var arr3 []int // 为空, 根本没有分配空间
if arr1 == nil {
fmt.Println("arr1 is nil")
}
if arr2 == nil {
fmt.Println("arr2 is nil")
}
if arr3 == nil {
fmt.Println("arr3 is nil")
}
fmt.Println(arr1)
fmt.Println(arr2)
fmt.Println(arr3)
}

切片的追加
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import "fmt"

func main() {
// 定义一个切片, 长度为3, 容量为5
arr := make([]int, 3, 5)
// 打印长度和容量
fmt.Printf("arr = %v, len = %d, cap = %d\n", arr, len(arr), cap(arr))
// 此时直接访问长度外的元素会出错
// arr[4] = 100

// 尾插. 必须接受该函数的返回值
arr = append(arr, 10)
arr = append(arr, 10)
arr = append(arr, 10)
// 超过了容量5, 进行了扩容(与C++ vector扩容机制类似,都需要进行数据复制)
fmt.Printf("arr = %v, len = %d, cap = %d\n", arr, len(arr), cap(arr))
}

切片的截取
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package main

import "fmt"

func main() {
arr := []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}
arrSlip := arr[:5] // 等同于 arrSlip := arr[0:3], 左闭右开区间
fmt.Println(arrSlip)
arrSlip[0] = 100 // 此种方式获得的新切片依旧指向与原切片相同的底层数组
fmt.Println(arr) // arr 中的元素也被更改

copy(arrSlip, arr[1:2]) // 如果要拷贝, 可以使用这种方法. 但是目标数组(第一个参数)必须已经分配空间
// copy在将大数组拷贝到小数组时, 会发生截断情况
// copy在将小数组拷贝到大数组时, 会只更改大数组中与小数组长度相同的那部分, 后面的部分会保留原始值不变
fmt.Println(arrSlip)
}

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
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
package main

import "fmt"

// 将 map 作为参数传递, 默认传递的也是引用
func changeValue(myMap map[string]int) {
// 增加
myMap["ddd"] = 4
// 删除
delete(myMap, "aaa")
// 修改
myMap["ccc"] = 5
}

func main() {
// 1. 声明一个map, key为int, value为string. 但是不分配空间
var myMap map[string]string
if myMap == nil {
fmt.Println("myMap is nil")
myMap = make(map[string]string) // 此处省略第二个参数: 大小, 默认会分配一个较小的初始大小
}
myMap["one"] = "zhangsan"
myMap["two"] = "lisi"
myMap["three"] = "wangwu"
fmt.Println(myMap)

// 2. 使用make
map2 := make(map[string]int, 10) // 指定容量, 虽然没什么用(因为底层是哈希表)
fmt.Println(map2)

// 3. 声明同时初始化
map3 := map[string]int{
"aaa": 1,
"bbb": 2,
"ccc": 3,
}

changeValue(map3)

// 遍历
for key, value := range map3 {
value = 100 // 此处的 value 是值传递, 不会更改map内的值
fmt.Println(key, map3[key], value)
}

// 如果不需要key, 只需要value, 可以这样
for _, value := range map3 {
fmt.Println("value: ", value)
}

// 如果只需要key, 可以这样
for key := range map3{
fmt.Println("key: ", key, "value: ", map3[key])
}

// map类型的切片数组
sliceMap := make([]map[string]string, 5)
// 注意此处获取的是下标i, 方便对sliceMap[i]进行操作
for i := range sliceMap {
sliceMap[i] = make(map[string]string, 1)
sliceMap[i]["key1"] = "value1"
}
fmt.Printf("sliceMap: %v\n", sliceMap)

// 错误的写法:
// NOT GOOD!
items2 := make([]map[int]int, 5)
for _, item := range items2 {
item = make(map[int]int, 1) // item is only a copy of the slice element.
item[1] = 2 // This 'item' will be lost on the next iteration.
}
fmt.Printf("Value of items: %v\n", items2)
}

面向对象

结构体

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
package main

import "fmt"

type myInt int // 给 int 起别名, 类似于C++中的 typedef,
// 但是GO中的别名和原名是不兼容的, 例如 myInt 类型的变量不能直接赋值给 int 类型的变量
// var num int = 100
// var myNum myInt = num // 报错
// var myNum myInt = myInt(num) // 显示转换, 可行
// var myNum myInt = 100 // 可行

// 定义一个结构体
type Student struct {
name string
age int
}

func changeStudentByCopy(stu Student) {
stu.age = 100 // 更改失败, 结构体传值是值传递
}

// 指针传递
func changeStudentByPoint(stu *Student) {
stu.age = 100 // 指针也可以通过 . 访问内部成员
}

func main() {
// 声明方式1
var zhangsan = Student{"zhangsan", 20}
fmt.Println(zhangsan) // 可以用 Println 直接打印结构体
fmt.Printf("%v\n", zhangsan) // 也可以用%v占位符, %v可以格式化任何类型

changeStudentByCopy(zhangsan) // 更改失败
fmt.Println(zhangsan)

// 声明方式2
lisi := Student{
"lisi",
20,
}
changeStudentByPoint(&lisi) // 更改成功
fmt.Println(lisi)
}

匿名结构体

1
2
3
4
5
6
7
8
9
10
11
func main(){
// 定义一个匿名结构体
d := struct {
// 第一个大括号内写匿名结构体的定义(有哪些字段)
fn func() string // 函数作为结构体的字段
}{
// 第二个大括号用于初始化匿名结构体(给字段赋值)
fn: func() string { return "Hello, World!" },
}
println(d.fn()) // 调用函数
}

封装
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
package main

import "fmt"

// Student 大写字母开头让其他 package 也能访问到
type Student struct {
Name string // 属性名大写字母开头, 表示该属性其他package可以直接访问
Age int
id int // 小写开头表示其他package无法访问
}

// 为 Student 绑定 getName 方法
// 方法名大写字母开头, 表示其他 package 可以访问该方法
func (stu *Student) GetName() string {
// stu 是调用该方法的当前对象的指针
return stu.Name
}

func (stu *Student) GetAge() int {
return stu.Age
}

func (stu *Student) SetName(name string) {
stu.Name = name
}

func (stu *Student) SetAge(age int) {
stu.Age = age
}

func (stu *Student) SetId(id int) {
stu.id = id
}

func (stu Student) Print() {
fmt.Printf("%s is %d years old\n", stu.GetName(), stu.Age)
}

// 下面写法需要注意
func (stu Student) GetNameCopy() string {
// stu 是调用该方法的当前对象的拷贝(将原对象复制了一份)
return stu.Name
}

func main() {
stu := Student{"James", 20, 1}
stu.Print()

stu.SetAge(100)
stu.SetName("Jack")
stu.Print()

fmt.Println(stu.GetAge())
}
继承

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
package main

import "fmt"

type Human struct {
name string
age int
sex string
}

func (human Human) Eat() {
fmt.Printf("Human %s eats!\n", human.name)
}

func (human Human) Work() {
fmt.Printf("Human %s works!\n", human.name)
}

type SuperMan struct {
Human // 只写类名, 不写对象名. 表示继承对应类的方法和属性
level int
}

// 重写父类方法
func (super SuperMan) Eat() {
fmt.Printf("Super %s eats!!!\n", super.name)
}

// 重写父类方法
func (super SuperMan) Work() {
fmt.Printf("Super %s works!!!\n", super.name)
}

// 子类新增方法
func (superMan SuperMan) Fly() {
fmt.Printf("super %s Flying!!!\n", superMan.name)
}

func main() {
// 1. 直接声明并初始化子类
superHuman := SuperMan{Human{"李四", 20, "男"}, 1000}
superHuman.name = "王五" // 子类可以直接访问父类资源并更改
superHuman.Eat()
superHuman.Work()
superHuman.Fly()

// 2. 先声明对象, 再逐个赋值
var superMan SuperMan
superMan.name = "赵六"
superMan.age = 30
superMan.sex = "男"
superMan.level = 100
superMan.Work()
superMan.Fly()
}
多态

GO语言中,通过interface实现多态

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
package main

import "fmt"

// Animal interface 本质是指针
// 子类必须全部实现interface内的所有函数才能实现多态
type Animal interface {
Sleep()
GetColor() string
GetType() string
}

type Bird struct {
color string
}

func (bird *Bird) Sleep() {
fmt.Println("bird sleep")
}

func (bird *Bird) GetColor() string {
return bird.color
}

func (bird *Bird) GetType() string {
return "Bird"
}

type Cat struct {
color string
}

func (cat *Cat) Sleep() {
fmt.Println("cat sleep")
}

func (cat *Cat) GetColor() string {
return cat.color
}

func (cat *Cat) GetType() string {
return "Cat"
}

func animalSleep(animal Animal) {
fmt.Printf("%s color %s Sleep\n", animal.GetColor(), animal.GetType())
animal.Sleep()
}

func main() {
bird := Bird{color: "black"}
animalSleep(&bird)

cat := Cat{color: "yellow"}
animalSleep(&cat)
}
万能类型和类型断言

GO语言中,int,string,float32,float64,struct等这些基本类型都实现了interface{}的接口,因此可以用interface{}类型去指向(或者说引用)任意类型的对象。因此interface{}被称为万能类型。

既然interface是万能类型,可以引用任何其他类型,那该如何判断其引用的具体是哪种类型呢?此时可以通过类型断言,而类型断言只有interface{}类型有,其他类型没有

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
package main

import "fmt"

type Book struct {
title string
}

func test(arg interface{}) {
fmt.Println(arg)

// 类型断言
// 判断 arg 是否为 string 类型,
// 如果是, 赋值给value, 且ok为true
// 如果不是, value为空串, 且ok为false
value, ok := arg.(string)
if !ok {
fmt.Println("arg is not a string") // 此时 value 为空串
} else {
fmt.Println(value)
}
fmt.Println("===========================")
}

func main() {
test(100)
test("hello world")
test(Book{"Golang"})
test(3.14)
test(map[string]string{
"001": "C++",
"002": "Go",
"003": "Python",
})
}

从上述代码中也可以看出,类型断言也可以用来进行特殊的类型强转,因为如果断言成功将会返回断言的目标类型的值

反射

变量的内置pair结构

image

GO语言中,每一个变量其实都包括(type, value)两部分,value就是变量的值,而type则是变量的类型,type又可以分为两种:一种是声明变量时指定的类型(如intstring,结构体等),另一种是接口interface内部实际存储的类型(主要涉及多态)。可以通过reflect.TypeOf(变量名)来获取某个变量的type

上面提到的万能类型的interface{}的类型断言,就是通过判断type是否是指定值来实现的。

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
package main

import (
"fmt"
"reflect"
)

type Person interface {
Eat()
}

type Student struct {
Name string
Age int
}

func (s Student) Eat() {
fmt.Println(s.Name, " eat")
}

func main() {
var num = 100
fmt.Println(reflect.TypeOf(num))
stu := Student{"张三", 20}
fmt.Println(reflect.TypeOf(stu))

var person Person
fmt.Println(reflect.TypeOf(person)) // 对于一个interface, 如果未指向任何对象, 那么 type 就为空
}

再看下一段代码:

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
package main

import "fmt"

type Reader interface {
ReadBook()
}

type Writer interface {
WriteBook()
}

type Book struct {
Name string
}

func (b Book) ReadBook() {
fmt.Println("ReadBook")
}

func (b Book) WriteBook() {
fmt.Println("WriteBook")
}

func main() {
var reader Reader = &Book{"Bob"}
reader.ReadBook()
var writer Writer = reader.(Writer) // 这一行断言能执行成功就是因为 reader 的 type 中有 Writer
writer.WriteBook()
}

反射使用

此处仅为反射的简单使用,更详细的使用在: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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package main

import (
"fmt"
"reflect"
)

type User struct {
// 反引号内部是字段的tag, 以key:value的形式存在,
// key一般不加双引号, value一般为字符串(加双引号),
// 一个字段可以有多对key-value, 中间用空格隔开。
// tag相当于运行时的注释。使用场景如下:
Id int `info:"id" docs:"学号"`
Name string `json:"username"` // 例如用于json的序列化和反序列化, 这样序列化后的JSON字段名为username而不是Name
Age int `gorm:"column:userage"` // 例如用于数据库映射,Gorm内部会根据设置好的tag映射到数据库中的列名
}

// 注意! 此方法并不会被NumMethod()方法算作类内的方法,因为传递对象时使用的是指针
func (user *User) CallByPoint() {
fmt.Println("User(*) is called ..")
fmt.Printf("%v\n", user)
}

// 因为 方法小写字母开头, 所以也不被 NumMethod算作类内方法
func (user User) call() {
fmt.Println("User is called ..")
fmt.Printf("%v\n", user)
}

func (user User) Call() int {
fmt.Println("User is called ..")
fmt.Printf("%v\n", user)
return 0
}

func DoFiledAndMethod(input interface{}) {
// 获取 input 的 type
inputType := reflect.TypeOf(input)
fmt.Println("inputType: ", inputType.Name()) // 获取类型名
// fmt.Println(inputType) // 直接打印inputType将会输出 包名.类型名

// 获取 input 的 value
inputValue := reflect.ValueOf(input)
fmt.Println("inputValue: ", inputValue)

// 通过 type 获取里面的字段名,字段类型和字段值
// NumField() 函数返回类型内部包含的字段的个数
// Field(index) 函数根据传入的index函数返回第index个字段的值或者类型(具体看调用着)

for i := 0; i < inputType.NumField(); i++ {
field := inputType.Field(i) // 获取第 i 个字段
fmt.Println("fieldName: ", field.Name) // 获取字段名(就是变量名)
fmt.Println("fieldType: ", field.Type) // 获取字段类型
// tag本质也是string
if field.Tag != "" {
fmt.Println("tag: ", field.Tag) // 获取 tag
}

fieldValue := inputValue.Field(i) // 获取第 i 个字段的值
fmt.Println("fieldValue:", fieldValue.Interface()) // 此处Interface是否调用不影响???
fmt.Println("====================================")
}

// 通过 type 获取里面的方法
// NumMechod()方法返回类型中包含的方法的数量. 不包括指针传值的函数和函数名小写字母开头
for i := 0; i < inputType.NumMethod(); i++ {
method := inputType.Method(i)
methodValue := inputValue.Method(i)
fmt.Println("methodName: ", method.Name) // 方法名称
fmt.Println("methodType: ", method.Type) // 方法类型信息(参数和返回值类型)
fmt.Println("methodValue: ", methodValue) // 类似于C++中方法的地址
methodValue.Call([]reflect.Value{}) // 调用方法
}
}

func main() {
user := User{1, "Jack", 20}
DoFiledAndMethod(user)
}

struct内的tag在json中的使用

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
package main

import (
"encoding/json"
"fmt"
)

type Movie struct {
Title string `json:"title"`
Year int `json:"year"`
Price int `json:"price"`
Actors []string `json:"actors"`
}

func main() {
movie := Movie{"喜剧之王", 2000, 10, []string{"xingyue", "zhangbozhi"}}

// 编码
jsonEncodeStr, err := json.Marshal(movie)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("jsonStr: ", string(jsonEncodeStr))

// 解码
movie = Movie{}
err = json.Unmarshal(jsonEncodeStr, &movie)
if err != nil {
fmt.Println(err)
return
}
fmt.Println("movie: ", movie)
}
由 Hexo 驱动 & 主题 Keep
本站由 提供部署服务
总字数 74.8k 访客数 访问量