Skip to content

Commit b307314

Browse files
committed
Add syntax highlighting
1 parent e476a6e commit b307314

File tree

5 files changed

+71
-34
lines changed

5 files changed

+71
-34
lines changed

zh/02.1.md

+1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
准备好了吗?Let's Go!
1010
```Go
11+
1112
package main
1213

1314
import "fmt"

zh/02.5.md

+14-7
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
## method
55
现在假设有这么一个场景,你定义了一个struct叫做长方形,你现在想要计算他的面积,那么按照我们一般的思路应该会用下面的方式来实现
6+
```Go
67

78
package main
89
import "fmt"
@@ -21,7 +22,7 @@
2122
fmt.Println("Area of r1 is: ", area(r1))
2223
fmt.Println("Area of r2 is: ", area(r2))
2324
}
24-
25+
```
2526
这段代码可以计算出来长方形的面积,但是area()不是作为Rectangle的方法实现的(类似面向对象里面的方法),而是将Rectangle的对象(如r1,r2)作为参数传入函数计算面积的。
2627

2728
这样实现当然没有问题咯,但是当需要增加圆形、正方形、五边形甚至其它多边形的时候,你想计算他们的面积的时候怎么办啊?那就只能增加新的函数咯,但是函数名你就必须要跟着换了,变成`area_rectangle, area_circle, area_triangle...`
@@ -49,6 +50,7 @@ method的语法如下:
4950
func (r ReceiverType) funcName(parameters) (results)
5051

5152
下面我们用最开始的例子用method来实现:
53+
```Go
5254

5355
package main
5456
import (
@@ -85,7 +87,7 @@ method的语法如下:
8587
fmt.Println("Area of c2 is: ", c2.area())
8688
}
8789

88-
90+
```
8991

9092
在使用method的时候重要注意几点
9193

@@ -104,10 +106,12 @@ method的语法如下:
104106
>值得说明的一点是,图示中method用虚线标出,意思是此处方法的Receiver是以值传递,而非引用传递,是的,Receiver还可以是指针, 两者的差别在于, 指针作为Receiver会对实例对象的内容发生操作,而普通类型作为Receiver仅仅是以副本作为操作对象,并不对原实例对象发生操作。后文对此会有详细论述。
105107
106108
那是不是method只能作用在struct上面呢?当然不是咯,他可以定义在任何你自定义的类型、内置类型、struct等各种类型上面。这里你是不是有点迷糊了,什么叫自定义类型,自定义类型不就是struct嘛,不是这样的哦,struct只是自定义类型里面一种比较特殊的类型而已,还有其他自定义类型申明,可以通过如下这样的申明来实现。
109+
```Go
107110

108111
type typeName typeLiteral
109-
112+
```
110113
请看下面这个申明自定义类型的代码
114+
```Go
111115

112116
type ages int
113117

@@ -121,12 +125,13 @@ method的语法如下:
121125
...
122126
"December":31,
123127
}
124-
128+
```
125129
看到了吗?简单的很吧,这样你就可以在自己的代码里面定义有意义的类型了,实际上只是一个定义了一个别名,有点类似于c中的typedef,例如上面ages替代了int
126130

127131
好了,让我们回到`method`
128132

129133
你可以在任何的自定义类型中定义任意多的`method`,接下来让我们看一个复杂一点的例子
134+
```Go
130135

131136
package main
132137
import "fmt"
@@ -200,7 +205,7 @@ method的语法如下:
200205

201206
fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String())
202207
}
203-
208+
```
204209
上面的代码通过const定义了一些常量,然后定义了一些自定义类型
205210

206211
- Color作为byte的别名
@@ -242,6 +247,7 @@ method的语法如下:
242247

243248
### method继承
244249
前面一章我们学习了字段的继承,那么你也会发现Go的一个神奇之处,method也是可以继承的。如果匿名字段实现了一个method,那么包含这个匿名字段的struct也能调用该method。让我们来看下面这个例子
250+
```Go
245251

246252
package main
247253
import "fmt"
@@ -274,9 +280,10 @@ method的语法如下:
274280
mark.SayHi()
275281
sam.SayHi()
276282
}
277-
283+
```
278284
### method重写
279285
上面的例子中,如果Employee想要实现自己的SayHi,怎么办?简单,和匿名字段冲突一样的道理,我们可以在Employee上面定义一个method,重写了匿名字段的方法。请看下面的例子
286+
```Go
280287

281288
package main
282289
import "fmt"
@@ -315,7 +322,7 @@ method的语法如下:
315322
mark.SayHi()
316323
sam.SayHi()
317324
}
318-
325+
```
319326
上面的代码设计的是如此的美妙,让人不自觉的为Go的设计惊叹!
320327

321328
通过这些内容,我们可以设计出基本的面向对象的程序了,但是Go里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。

zh/02.6.md

+32-14
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ Go语言里面设计最精妙的应该算interface,它让面向对象,内容
1414
上面这些方法的组合称为interface(被对象Student和Employee实现)。例如Student和Employee都实现了interface:SayHi和Sing,也就是这两个对象是该interface类型。而Employee没有实现这个interface:SayHi、Sing和BorrowMoney,因为Employee没有实现BorrowMoney这个方法。
1515
### interface类型
1616
interface类型定义了一组方法,如果某个对象实现了某个接口的所有方法,则此对象就实现了此接口。详细的语法参考下面这个例子
17+
```Go
1718

1819
type Human struct {
1920
name string
@@ -82,7 +83,7 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
8283
Sing(song string)
8384
SpendSalary(amount float32)
8485
}
85-
86+
```
8687
通过上面的代码我们可以知道,interface可以被任意的对象实现。我们看到上面的Men interface被Human、Student和Employee实现。同理,一个对象可以实现任意多个interface,例如上面的Student实现了Men和YoungChap两个interface。
8788

8889
最后,任意的类型都实现了空interface(我们这样定义:interface{}),也就是包含0个method的interface。
@@ -93,6 +94,7 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
9394
因为m能够持有这三种类型的对象,所以我们可以定义一个包含Men类型元素的slice,这个slice可以被赋予实现了Men接口的任意结构的对象,这个和我们传统意义上面的slice有所不同。
9495

9596
让我们来看一下下面这个例子:
97+
```Go
9698

9799
package main
98100
import "fmt"
@@ -169,11 +171,12 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
169171
value.SayHi()
170172
}
171173
}
172-
174+
```
173175
通过上面的代码,你会发现interface就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, Go通过interface实现了duck-typing:即"当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子"。
174176

175177
### 空interface
176178
空interface(interface{})不包含任何的method,正因为如此,所有的类型都实现了空interface。空interface对于描述起不到任何的作用(因为它不包含任何的method),但是空interface在我们需要存储任意类型的数值的时候相当有用,因为它可以存储任意类型的数值。它有点类似于C语言的void*类型。
179+
```Go
177180

178181
// 定义a为空接口
179182
var a interface{}
@@ -182,17 +185,20 @@ interface类型定义了一组方法,如果某个对象实现了某个接口
182185
// a可以存储任意类型的数值
183186
a = i
184187
a = s
185-
188+
```
186189
一个函数把interface{}作为参数,那么他可以接受任意类型的值作为参数,如果一个函数返回interface{},那么也就可以返回任意类型的值。是不是很有用啊!
187190
### interface函数参数
188191
interface的变量可以持有任意实现该interface类型的对象,这给我们编写函数(包括method)提供了一些额外的思考,我们是不是可以通过定义interface参数,让函数接受各种类型的参数。
189192

190193
举个例子:fmt.Println是我们常用的一个函数,但是你是否注意到它可以接受任意类型的数据。打开fmt的源码文件,你会看到这样一个定义:
194+
```Go
191195

192196
type Stringer interface {
193197
String() string
194198
}
199+
```
195200
也就是说,任何实现了String方法的类型都能作为参数被fmt.Println调用,让我们来试一试
201+
```Go
196202

197203
package main
198204
import (
@@ -215,12 +221,14 @@ interface的变量可以持有任意实现该interface类型的对象,这给
215221
Bob := Human{"Bob", 39, "000-7777-XXX"}
216222
fmt.Println("This Human is : ", Bob)
217223
}
224+
```
218225
现在我们再回顾一下前面的Box示例,你会发现Color结构也定义了一个method:String。其实这也是实现了fmt.Stringer这个interface,即如果需要某个类型能被fmt包以特殊的格式输出,你就必须实现Stringer这个接口。如果没有实现这个接口,fmt将以默认的方式输出。
226+
```Go
219227

220228
//实现同样的功能
221229
fmt.Println("The biggest one is", boxes.BiggestsColor().String())
222230
fmt.Println("The biggest one is", boxes.BiggestsColor())
223-
231+
```
224232
注:实现了error接口的对象(即实现了Error() string的对象),使用fmt输出时,会调用Error()方法,因此不必再定义String()方法了。
225233
### interface变量存储的类型
226234

@@ -233,6 +241,7 @@ interface的变量可以持有任意实现该interface类型的对象,这给
233241
如果element里面确实存储了T类型的数值,那么ok返回true,否则返回false。
234242

235243
让我们通过一个例子来更加深入的理解。
244+
```Go
236245

237246
package main
238247

@@ -272,13 +281,14 @@ interface的变量可以持有任意实现该interface类型的对象,这给
272281
}
273282
}
274283
}
275-
284+
```
276285
是不是很简单啊,同时你是否注意到了多个if里面,还记得我前面介绍流程时讲过,if里面允许初始化变量。
277286

278287
也许你注意到了,我们断言的类型越多,那么if else也就越多,所以才引出了下面要介绍的switch。
279288
- switch测试
280289

281290
最好的讲解就是代码例子,现在让我们重写上面的这个实现
291+
```Go
282292

283293
package main
284294

@@ -319,21 +329,23 @@ interface的变量可以持有任意实现该interface类型的对象,这给
319329
}
320330
}
321331
}
322-
332+
```
323333
这里有一点需要强调的是:`element.(type)`语法不能在switch外的任何逻辑里面使用,如果你要在switch外面判断一个类型就使用`comma-ok`。
324334

325335
### 嵌入interface
326336
Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Struct时学习的匿名字段,多么的优雅啊,那么相同的逻辑引入到interface里面,那不是更加完美了。如果一个interface1作为interface2的一个嵌入字段,那么interface2隐式的包含了interface1里面的method。
327337

328338
我们可以看到源码包container/heap里面有这样的一个定义
339+
```Go
329340

330341
type Interface interface {
331342
sort.Interface //嵌入字段sort.Interface
332343
Push(x interface{}) //a Push method to push elements into the heap
333344
Pop() interface{} //a Pop elements that pops elements from the heap
334345
}
335-
346+
```
336347
我们看到sort.Interface其实就是嵌入字段,把sort.Interface的所有method给隐式的包含进来了。也就是下面三个方法:
348+
```Go
337349

338350
type Interface interface {
339351
// Len is the number of elements in the collection.
@@ -344,49 +356,55 @@ Go里面真正吸引人的是它内置的逻辑语法,就像我们在学习Str
344356
// Swap swaps the elements with indexes i and j.
345357
Swap(i, j int)
346358
}
347-
359+
```
348360
另一个例子就是io包下面的 io.ReadWriter ,它包含了io包下面的Reader和Writer两个interface:
361+
```Go
349362

350363
// io.ReadWriter
351364
type ReadWriter interface {
352365
Reader
353366
Writer
354367
}
355-
368+
```
356369
### 反射
357370
Go语言实现了反射,所谓反射就是能检查程序在运行时的状态。我们一般用到的包是reflect包。如何运用reflect包,官方的这篇文章详细的讲解了reflect包的实现原理,[laws of reflection](http://golang.org/doc/articles/laws_of_reflection.html)
358371

359372
使用reflect一般分成三步,下面简要的讲解一下:要去反射是一个类型的值(这些值都实现了空interface),首先需要把它转化成reflect对象(reflect.Type或者reflect.Value,根据不同的情况调用不同的函数)。这两种获取方式如下:
373+
```Go
360374

361375
t := reflect.TypeOf(i) //得到类型的元数据,通过t我们能获取类型定义里面的所有元素
362376
v := reflect.ValueOf(i) //得到实际的值,通过v我们获取存储在里面的值,还可以去改变值
363-
377+
```
364378
转化为reflect对象之后我们就可以进行一些操作了,也就是将reflect对象转化成相应的值,例如
379+
```Go
365380

366381
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签
367382
name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
368-
383+
```
369384
获取反射值能返回相应的类型和数值
385+
```Go
370386

371387
var x float64 = 3.4
372388
v := reflect.ValueOf(x)
373389
fmt.Println("type:", v.Type())
374390
fmt.Println("kind is float64:", v.Kind() == reflect.Float64)
375391
fmt.Println("value:", v.Float())
376-
392+
```
377393
最后,反射的话,那么反射的字段必须是可修改的,我们前面学习过传值和传引用,这个里面也是一样的道理。反射的字段必须是可读写的意思是,如果下面这样写,那么会发生错误
394+
```Go
378395

379396
var x float64 = 3.4
380397
v := reflect.ValueOf(x)
381398
v.SetFloat(7.1)
382-
399+
```
383400
如果要修改相应的值,必须这样写
401+
```Go
384402

385403
var x float64 = 3.4
386404
p := reflect.ValueOf(&x)
387405
v := p.Elem()
388406
v.SetFloat(7.1)
389-
407+
```
390408
上面只是对反射的简单介绍,更深入的理解还需要自己在编程中不断的实践。
391409

392410
## links

0 commit comments

Comments
 (0)