Eli's Blog

1. 结构体

将多个不同类型命名字段(field)序列打包成一个复合类型。

结构体特点:

  • 值类型
  • 做参数,值传递
  • 相同类型,可使用==或!=比较

Go语言中实现封装、继承和多态:

  • 封装:通过方法实现
  • 继承:通过匿名字段实现
  • 多态:通过接口实现

1.1 定义结构体

1
2
3
4
5
6
7
8
9
10
11
12
type person struct {
Name string
Age int
}

func main() {
p := person{
Name: "lucy",
Age: 22,
}
fmt.Println(p)
}

1.2 匿名结构体

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
type person struct {
Name string
Age int
Contact struct {
Phone, City string
}
}

func main() {
p1 := person{
Name: "lucy",
Age: 22,
Contact: struct{ Phone, City string } {
Phone: "123456789",
City: "LA",
},
}
fmt.Println(p1)

p2 := person{
Name: "jack",
Age: 19,
}
p2.Contact.Phone = "987654321"
p2.Contact.City = "NY"

fmt.Println(p2)
}

1.3 匿名字段

1
2
3
4
5
6
7
8
9
10
11
func main() {
s := struct {
int
string
} {
10,
"jack",
}

fmt.Println(s)
}

1.4 嵌入结构(模拟继承)

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 person struct {
Name string
Age int
}

type teacher struct {
person
Salary float32
}

type student struct {
person
Score float32
}

func main() {
t := teacher {
person: person{
Name: "Jack",
Age: 45,
},
Salary: 12901.20,
}
t.Age += 1

s := student{
person: person{
Name: "Tom",
Age: 13,
},
Score: 91.50,
}
s.Score -= 2.5

fmt.Println(t, s)
}

1.5 结构体序列化

注意使用struct标签,否则序列化后的名称会保持大写开头不变

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
type Student struct {
Name string `json:"name"`
Age byte `json:"age"`
}

func main() {
stu := Student{"Jack", 21}

js, err := json.Marshal(stu)
if err != nil {
fmt.Println("json化失败", err)
return
}

fmt.Println(string(js))
}

1.6 工厂模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type student struct {
Name string
Age byte
}

func NewStudent(name string, age byte) *student {
return &student{
Name: name,
Age: age,
}
}

func (stu *student) String() string {
return fmt.Sprintf("Name=%v, Age=%v", stu.Name, stu.Age)
}

1.7 结构体链表

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
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
type Node struct {
Name string
Value int
next *Node
}

func main() {
node := &Node{
Name: "head",
Value: 0,
}

appendNodes(node)
//trans(node)

// 只所以要有二级指针,是为了改变顶级node的地址,方便遍历函数遍历
insertNodes(&node)
//trans(node)

delNode(&node, "head")
//trans(node)

addNode(&node, "node_A_1", &Node{Name: "newNode", Value: 12})
trans(node)
}

func appendNodes(p *Node) {
for i := 0; i < 2; i++ {
node := Node{
Name: fmt.Sprintf("node_A_%d", i),
Value: rand.Intn(100),
}

p.next = &node
p = &node
}
}

func insertNodes(p **Node) {
for i := 0; i < 2; i++ {
node := Node{
Name: fmt.Sprintf("node_I_%d", i),
Value: rand.Intn(100),
}

node.next = *p
*p = &node
}
}

func delNode(p **Node, name string) {
head := *p

// 第一个即为要删除的对象
if head.Name == name {
*p = head.next
return
}

prev := head
head = head.next

for head != nil {
if head.Name == name {
prev.next = head.next
break
}

prev = head
head = head.next
}
}

func addNode(p **Node, name string, newNode *Node) {
head := *p

// 第一个元素前插入
if head.Name == name {
newNode.next = head
*p = newNode
return
}

prev := head
head = head.next
for head != nil {
if head.Name == name {
prev.next = newNode
newNode.next = head
break
}

prev = head
head = head.next
}
}

func trans(p *Node) {
for p != nil {
fmt.Println(*p)
p = p.next
}
}func main() {
node := &Node{
Name: "head",
Value: 0,
}

appendNodes(node)
insertNodes(&node)
delNode(&node, "node_I_4")
appendNode(node, "head", "newAppend")
insertNode(&node, "node_A_4", "newInsert")

trans(node)
}

type Node struct {
Name string
Value int
next *Node
}

func trans(p *Node) {
for p != nil {
fmt.Println(*p)
p = p.next
}
}

func appendNodes(p *Node) {
for i := 0; i < 5; i++ {
node := &Node{
Name: fmt.Sprintf("node_A_%d", i),
Value: rand.Intn(100),
}
p.next = node
p = node
}
}

// 除了要在head前增加元素,还需要去改变链表head的位置,需要用到多重指针
func insertNodes(p **Node) {
for i := 0; i < 5; i++ {
node := &Node{
Name: fmt.Sprintf("node_I_%d", i),
Value: rand.Intn(100),
}
node.next = *p
*p = node
}
}

func delNode(p **Node, name string) {
node := *p

// 第一个就是要删除的元素
if node.Name == name {
*p = node.next
return
}

prev := node
node = node.next

for node != nil {
if node.Name == name {
prev.next = node.next
break
}

prev = node
node = node.next
}
}

func appendNode(p *Node, name, newName string) {
for p != nil {
if p.Name == name {
newNode := &Node{
Name: newName,
Value: rand.Intn(100),
next: p.next,
}
p.next = newNode
break
}

p = p.next
}

}

func insertNode(p **Node, name, newName string) {
node := *p

// 在第一个元素前增加
if node.Name == name {
newNode := &Node{
Name: newName,
Value: rand.Intn(100),
next: node,
}

// 改变链表头
*p = newNode
return
}

prev := node
node = node.next

for node != nil {
if node.Name == name {
newNode := &Node{
Name: newName,
Value: rand.Intn(100),
next: node,
}

// 与前一个链接起来
prev.next = newNode
break
}

prev = node
node = node.next
}
}

1.8 结构体内存结构

不管结构体包含多少字段,其内存总是一次性分配的,各字段在相邻的地址空间按定义顺序排列。当然,对于引用类型、字符串和指针,结构内存中只包含其基本(头部)数据。还有,所有匿名字段成员也被包含在内。

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
type point struct {
x, y int
}

type node struct {
id int
name string
data []byte
next *node
point
}

func main() {
v := node{
id: 1,
name: "yes",
data: []byte{1, 2, 3, 4},
point: point{x: 100, y: 200},
}

format := `
v: %p ~ %x, size %d, align: %d

field address offset size
-------+--------------------+--------+-----
id %p %d %d
name %p %d %d
data %p %d %d
next %p %d %d
x %p %d %d
y %p %d %d
`

fmt.Printf(format,
&v, uintptr(unsafe.Pointer(&v))+unsafe.Sizeof(v), unsafe.Sizeof(v), unsafe.Alignof(v),
&v.id, unsafe.Offsetof(v.id), unsafe.Sizeof(v.id),
&v.name, unsafe.Offsetof(v.name), unsafe.Sizeof(v.name),
&v.data, unsafe.Offsetof(v.data), unsafe.Sizeof(v.data),
&v.next, unsafe.Offsetof(v.next), unsafe.Sizeof(v.next),
&v.x, unsafe.Offsetof(v.x), unsafe.Sizeof(v.x),
&v.y, unsafe.Offsetof(v.y), unsafe.Sizeof(v.y))
}

/*
v: 0xc000084000 ~ c000084048, size 72, align: 8

field address offset size
-------+--------------------+--------+-----
id 0xc000084000 0 8
name 0xc000084008 8 16
data 0xc000084018 24 24
next 0xc000084030 48 8
x 0xc000084038 56 8
y 0xc000084040 64 8
*/

unsafe.Sizeof(x)特点总结:

  • 字符串:始终返回16。字符串类型对应一个结构体,该结构体有两个域,第一个域是指向该字符串的指针,第二个域是字符串的长度,每个域占8个字节,但是并不包含指针指向的字符串的内容。
  • 切片: 始终返回24。if x is a slice, Sizeof returns the size of the slice descriptor, not the size of the memory referenced by the slice.
  • 数组: Sizeof(x[0]) * len(x)

1.9 结构体字段对齐

在分配内存时,字段须做对齐处理,通常以所有字段中最长的基础类型宽度为标准

unsafe.Alignof(x): 获取对齐宽度,以最长的基础类型宽度作为对齐标准。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
func main() {
v1 := struct {
a byte
b byte
c int32 // 对齐宽度4
}{}

v2 := struct {
a byte
b byte // 对齐宽度1
}{}

v3 := struct {
a byte
b []int // 基础类型int,对齐宽度8
c int32
}{}

fmt.Printf("v1: %d, %d\n", unsafe.Alignof(v1), unsafe.Sizeof(v1)) // 4, 8
fmt.Printf("v2: %d, %d\n", unsafe.Alignof(v2), unsafe.Sizeof(v2)) // 1, 2
fmt.Printf("v3: %d, %d\n", unsafe.Alignof(v3), unsafe.Sizeof(v3)) // 8, 40
}

1.10 类型对齐长度

类型 对齐长度
bool 1
int8/byte 1
int32 4
int64 8
string 8
map 8
slice 8

2. 方法

方法是与对象实例绑定的特殊函数

2.1 绑定方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
type A struct {
Name string
}

// (a A) receiver
func (a A) Print() {
fmt.Println(a.Name)
}

func main() {
a := A{
Name: "tom",
}
a.Print()
}

2.2 为int扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
type TZ int

func (a *TZ) Print() {
fmt.Println("TZ")
}

func main() {
var a TZ

a.Print() // method value

(*TZ).Print(&a) // method expression
}

2.3 结构体重写String()方法

重新String()方法:(fmt.Println()会自动调用String()方法)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
type Student struct {
Name string `json:"name"`
Age byte `json:"age"`
}

func main() {
stu := Student{"Jack", 21}

fmt.Println(&stu) // 自动调用String()方法
}

func (stu *Student) String() string {
return fmt.Sprintf("Name=%v, Age=%v", stu.Name, stu.Age)
}

2.4 方法集

类型有一个与之相关的方法集(method set),这决定了它是否实现某个接口。

  • 类型T方法集包含所有receiver T方法。
  • 类型T方法集包含所有receiver T+T方法。
  • 匿名嵌入S,T方法集包含所有receiver S方法。
  • 匿名嵌入S,T方法集包含所有receiver S+S方法。
  • 匿名嵌入S或S,T方法集包含所有receiver S+*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
27
28
29
type S struct{}

type T struct {
S
}

func (S) Hello() {}
func (S) sVal() {}
func (*S) sPtr() {}
func (T) tVal() {}
func (*T) tPtr() {}

func methodSet(a interface{}) {
t := reflect.TypeOf(a)
fmt.Println(t.NumMethod()) // methods need to export, 只有Hello一个方法可导出

for i, n := 0, t.NumMethod(); i < n; i++ {
m := t.Method(i)
fmt.Println(m.Name, m.Type)
}
}

func main() {
var t = T{}

methodSet(t)
println("------------")
methodSet(&t)
}

3. 接口

接口代表一种调用契约,是多个方法声明的集合。

采用duck type方式

它把所有具有共性的方法定义在一起,任何其他类型只要实现了这些方法,就实现了该接口。注意,要全部实现!

接口特性:

  • 一个或多个方法签名的集合
  • 只要某类型拥有改接口的所有方法签名,即算实现该接口,无需显示声明实现了那些接口,此称为Structural Typing
  • 接口中只有方法声明,没有实现
  • 接口可匿名嵌入其他接口,或嵌入到结构中
  • 将对象赋值给接口,会发生拷贝,而接口内部存储指向这个复制品的指针,即无法修改复制品的状态,也无法获取指针
  • 只有当接口存储的类型和对象均为nil时,接口才能有nil
  • 接口调用不会做receiver的自动转换
  • 接口同样支持匿名字段方法
  • 接口也可实现类似OOP中的多态
  • 空接口可以作为任何类型数据的容器

3.1 示例1

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
type Animal interface {
walk()
eat()
}

type Cat struct {}

type Bird struct {}

func (cat Cat) walk() {
fmt.Println("Cat walking with four limbs.")
}

func (cat Cat) eat() {
fmt.Println("Cats like to eat fish.")
}

func (bird Bird) walk() {
fmt.Println("Bird walkin with two legs.")
}

func (bird Bird) eat() {
fmt.Println("Birds like to eat insects.")
}

func main() {
var animal Animal

animal = new(Cat)
animal.walk()

animal = new(Bird)
animal.eat()
}

3.2 示例2

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
type USB interface {
Name() string
Connector
}

type Connector interface {
Connect()
}

type PhoneConnector struct {
name string
}

func (pc PhoneConnector) Name() string {
return pc.name
}

func (pc PhoneConnector) Connect() {
fmt.Println("Connected:", pc.name)
}

/*func Disconnect(usb USB) {
// 类型断言
if pc, ok := usb.(PhoneConnector); ok {
fmt.Println("Disconnected:", pc.name)
return
}
fmt.Println("Unknown device")
}*/

// 万能空接口
func Disconnect(usb interface{}) {
switch v := usb.(type) {
case PhoneConnector:
fmt.Println("Disconnected:", v.name)
default:
fmt.Println("Unknown device")
}
}

func main() {
var a USB = PhoneConnector{"PhoneConnector"}
a.Connect()
Disconnect(a)
}

3.3 类型断言

空接口可接收任意类型,如果要转换为具体类型,需要使用类型断言

1
2
3
4
5
6
7
8
9
10
11
func main() {
var point Point = Point{1, 2}

var a interface{}
a = point

var b Point
b = a.(Point) // 类型断言

fmt.Println(b)
}

3.3.1 类型断言检查

为避免转换错误直接panic,可先检查转换是否成功

1
2
3
4
5
6
7
8
9
10
11
12
13
func main() {
var a interface{}

var x float32 = 1.23
a = x

y, ok := a.(float32) // 类型断言
if ok {
fmt.Println(y)
} else {
fmt.Println("转换失败")
}
}

3.3.2 类型断言switch

1
2
3
4
5
6
7
8
9
10
11
12
func TypeJudge(items ...interface{}) {
for index, x := range items {
switch x.(type) {
case bool:
fmt.Printf("%v: %v is bool\n", index, x)
case string:
fmt.Printf("%v: %v is string\n", index, x)
default:
fmt.Printf("%v: %v is unknown\n", index, x)
}
}
}