noblog 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/README.md +4 -2
  2. package/dist/cmds/generate.js +9 -7
  3. package/dist/cmds/generate.js.map +1 -1
  4. package/dist/index.js +24 -10
  5. package/dist/index.js.map +1 -1
  6. package/dist/lib/nomd/nomd.d.ts.map +1 -1
  7. package/dist/lib/nomd/nomd.js +16 -1
  8. package/dist/lib/nomd/nomd.js.map +1 -1
  9. package/dist/lib/nosm/default.js +0 -1
  10. package/dist/lib/nosm/default.js.map +1 -1
  11. package/dist/lib/nosm/nosm.js +1 -0
  12. package/dist/lib/nosm/nosm.js.map +1 -1
  13. package/dist/lib/notion/client.d.ts +1 -0
  14. package/dist/lib/notion/client.d.ts.map +1 -1
  15. package/dist/lib/notion/client.js +16 -0
  16. package/dist/lib/notion/client.js.map +1 -1
  17. package/package.json +44 -28
  18. package/output/00097769-2aac-4b6f-8f37-d9b658d46b2e +0 -435
  19. package/output/1707063b-7c0a-4572-9526-f7db2f7bbc49 +0 -162
  20. package/output/1ed588e1-2604-4e33-9685-722b03f66374 +0 -89
  21. package/output/2b1c0d09-40f7-4151-a053-11391f775ece +0 -33
  22. package/output/336b6a5a-f383-4d6c-8fe5-54c9a918c4c0 +0 -264
  23. package/output/37b77a0a-e233-405a-93f5-a60a808eee35 +0 -72
  24. package/output/3ac660cb-9503-4e88-8939-5aee30c98351 +0 -24
  25. package/output/5bc1b19a-8350-48b3-aa19-8073561ad45b +0 -109
  26. package/output/765976b5-7790-497b-8634-4be1d4770e16 +0 -111
  27. package/output/90f2cb44-90ab-40b7-a736-02bfb83a314c +0 -1167
  28. package/output/99a7b0c5-f9de-403c-95ec-eba3d700a195 +0 -49
  29. package/output/c844b66d-0bf3-4f89-bbe1-5974f2de454b +0 -193
  30. package/output/cd6299f7-54bb-4160-8735-16e629422a57 +0 -193
  31. package/output/d462cfca-0a39-4b65-8430-4eae35edbb76 +0 -27
  32. package/output/daafaad2-2570-43da-8e9e-f95a1b29ca17 +0 -12
  33. package/output/e45ef6bf-6c30-4fb2-9efc-a1984e5dc530 +0 -84
  34. package/output/sub/05009ca9-cb39-4356-8f24-c0db39cb4661 +0 -64
  35. package/output/sub/0b903359-2337-4ad9-adef-e77b69142581 +0 -328
  36. package/output/sub/18f0fae1-b7ca-40ea-b725-4e69404429dc +0 -109
  37. package/output/sub/232ae8ce-5ec3-4737-a18e-077774b84b1e +0 -940
  38. package/output/sub/34f4d4af-812e-4e56-a6dd-deb96e7f605c +0 -3
  39. package/output/sub/44331753-a264-40a4-b86f-eafd676b3da0 +0 -509
  40. package/output/sub/4bac355b-24f0-49e7-a317-e4f1cad63c79 +0 -65
  41. package/output/sub/4d6663a2-e93a-402e-89f8-a47cccc0c8cf +0 -76
  42. package/output/sub/5771c956-25bf-4714-bc2e-65959c9156d9 +0 -52
  43. package/output/sub/6ff91a18-494e-4ffc-a33b-b9fd2c36f43f +0 -42
  44. package/output/sub/81e4a2b0-05e2-4013-bd41-14b144979d91 +0 -0
  45. package/output/sub/99ef1497-0b94-490f-b9a0-f54b0e64194d +0 -6
  46. package/output/sub/9a33b8cc-101a-4f6a-9852-115595cd166b +0 -172
  47. package/output/sub/9c060d29-39a2-432b-b06b-b0bd91989a52 +0 -1159
  48. package/output/sub/a45b383c-d894-4480-8322-9c5d028722e3 +0 -9
  49. package/output/sub/b875bfe4-3c1e-4a8f-a56c-27a48ab874ca +0 -438
  50. package/output/sub/b8cbb8f9-f702-4a43-943c-02a6e39683d5 +0 -222
  51. package/output/sub/e339a686-bbb1-41d9-b6b5-18111e72a68c +0 -1392
  52. package/output/sub/e7931a8e-45ca-444a-8d6f-e6fed9415c26 +0 -110
  53. package/output/sub/e859552b-c597-49ed-9ede-fab320fafd87 +0 -150
  54. package/output/sub/f4237e72-fdce-44a4-b62c-659dce24b1ee +0 -107
  55. package/output/sub/ff7ea97c-55ac-471c-902f-b7a7bd101ee6 +0 -0
  56. package/src/cmds/generate.ts +0 -54
  57. package/src/index.ts +0 -22
  58. package/src/lib/nomd/cvter.ts +0 -143
  59. package/src/lib/nomd/nomd.ts +0 -88
  60. package/src/lib/nosm/default.ts +0 -0
  61. package/src/lib/nosm/nosm.ts +0 -3
  62. package/src/lib/notion/client.ts +0 -59
  63. package/src/lib/notion/object.ts +0 -176
  64. package/src/lib/notion/request.ts +0 -42
  65. package/tsconfig.json +0 -35
@@ -1,1392 +0,0 @@
1
-
2
- > If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck.
3
-
4
-
5
-
6
-
7
-
8
- # Implicit Implementation of Receiver
9
-
10
-
11
- | Methods Receivers | Values |
12
- | ----------------- | -------- |
13
- | (t T) | T and *T |
14
- | (t *T) | *T |
15
-
16
-
17
-
18
-
19
-
20
-
21
-
22
-
23
-
24
-
25
-
26
- 上面的表格可以解读为:如果是值Receiver,实体类型的值和指针都可以实现对应的接口;如果是指针Receiver,那么只有类型的指针能够实现对应的接口。
27
-
28
-
29
- 其次我们我们以实体类型是值还是指针的角度看。
30
-
31
-
32
- | Values | Methods Receivers |
33
- | ------ | ----------------- |
34
- | T | (t T) |
35
- | *T | (t T) and (t *T) |
36
-
37
-
38
-
39
-
40
-
41
-
42
-
43
-
44
-
45
-
46
-
47
- 上面的表格可以解读为:类型的值只能实现值Receiver的接口;指向类型的指针,既可以实现值Receiver的接口,也可以实现指针Receiver的接口。
48
-
49
-
50
- **总结一下:想要创建接口类型的对象(如下方的coder接口),可以用****`*T`****的值来初始化接口对象,因为不管****`*T`****的指针对象实现的接口方法中Receiver是****`T`****还是****`*T`****。都可以认为是实现了接口。但是,****`T`****的对象实现接口方法时,只有Receiver都是值Receiver才可以认为是实现了接口方法。****或者更宏观地说,指针可以通过解引用获得值,但是值不应该取地址去调用指针的方法。**
51
-
52
-
53
- **这样的设计有一个简单的解释:****接收者是指针类型的方法,很可能在方法中会对接收者的属性进行更改操作,从而影响接收者;而对于接收者是值类型的方法,在方法中不会对接收者本身产生影响****。**
54
-
55
-
56
- 下面是一个简单的 修改对象/副本 的案例:
57
-
58
-
59
- ```go
60
- package main
61
-
62
- type coder interface {
63
- code()
64
- debug()
65
- }
66
-
67
- type Gopher struct {
68
- language string
69
- }
70
-
71
- func (p *Gopher) code() {
72
- p.language = "Go"
73
- }
74
-
75
- func (p Gopher) debug() {
76
- p.language = "Rust"
77
- }
78
- func main() {
79
- var c coder = &Gopher{"C"}
80
- c.debug()
81
- println(c.(*Gopher).language)
82
- c.code()
83
- println(c.(*Gopher).language)
84
- }
85
- ```
86
-
87
-
88
- 只要是值Receiver,不管调用者是什么,只会修改对象的副本,只有指针Receiver会修改对象。
89
-
90
-
91
- 以上内容为接口的特性,勿和函数调用混淆。
92
-
93
-
94
- # iface & eface
95
-
96
-
97
- ## iface (interface)
98
-
99
-
100
- 从源码层面看一下:
101
-
102
-
103
- ```go
104
- type iface struct {
105
- tab  *itab
106
- data unsafe.Pointer
107
- }
108
-
109
- // 旧版本itab
110
- ~~type itab struct {
111
- inter  *interfacetype
112
- _type  *_type
113
- link   *itab
114
- hash   uint32 // copy of _type.hash. Used for type switches.
115
- bad    bool   // type does not implement interface
116
- inhash bool   // has this itab been added to hash?
117
- unused [2]byte
118
- fun   [1]uintptr // variable sized
119
- }~~
120
-
121
- // 新版本itab
122
- type itab struct {
123
- inter *interfacetype
124
- _type *_type
125
- hash uint32 // copy of _type.hash. Used for type switches.
126
- _ [4]byte
127
- fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
128
- }
129
- ```
130
-
131
-
132
- `iface` 内部维护两个指针,`tab` 指向一个 `itab` 实体, 它表示接口的类型以及赋给这个接口的实体类型。`data` 则指向接口具体的值,一般而言是一个指向**堆内存**的指针。
133
-
134
-
135
- 再来仔细看一下 `itab` 结构体:`_type` 字段描述了实体的类型,包括内存对齐方式,大小等;`inter` 字段则描述了接口的类型。`fun` 字段放置和接口方法对应的具体数据类型的方法地址,实现接口调用方法的动态分派,一般在每次给接口赋值发生转换时会更新此表,或者直接拿缓存的 itab。
136
-
137
-
138
- 为什么 `fun` 数组的大小为 1,要是接口定义了多个方法可怎么办?实际上,这里存储的是第一个方法的函数指针,如果有更多的方法,在它之后的内存空间里继续存储。从汇编角度来看,通过增加地址就能获取到这些函数指针,没什么影响。顺便提一句,这些方法是按照函数名称的字典序进行排列的。
139
-
140
-
141
- 再看一下 `interfacetype` 类型,它描述的是接口的类型:
142
-
143
-
144
- ```go
145
- type interfacetype struct {
146
- typ     _type
147
- pkgpath name
148
- mhdr   []imethod
149
- }
150
- ```
151
-
152
-
153
- 可以看到,它包装了 `_type` 类型,`_type` 实际上是描述 Go 语言中各种数据类型的结构体。我们注意到,这里还包含一个 `mhdr` 字段,表示接口所定义的函数列表, `pkgpath` 记录定义了接口的包名。
154
-
155
-
156
- 这里通过一张图来看下 `iface` 结构体的全貌:
157
-
158
-
159
- ![Untitled.png](https://prod-files-secure.s3.us-west-2.amazonaws.com/816ab0e2-0bbf-4fbc-a791-de833db1ae15/0eef2fbc-5ce4-4ae0-8c56-5d2526900ddb/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240727%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240727T092456Z&X-Amz-Expires=3600&X-Amz-Signature=2cfdba0dd22a145c369afd40ed54c66e5d414a0e65326d47a8d56f95dc3b510c&X-Amz-SignedHeaders=host&x-id=GetObject)
160
-
161
-
162
- ## eface (empty interface)
163
-
164
-
165
- 接着来看一下 `eface` 的源码:
166
-
167
-
168
- ```go
169
- type eface struct {
170
-    _type *_type
171
-    data  unsafe.Pointer
172
- }
173
- ```
174
-
175
-
176
- 相比 `iface`,`eface` 就比较简单了。只维护了一个 `_type` 字段,表示空接口所承载的具体的实体类型。`data` 描述了具体的值。
177
-
178
-
179
- ![Untitled.png](https://prod-files-secure.s3.us-west-2.amazonaws.com/816ab0e2-0bbf-4fbc-a791-de833db1ae15/6bcc7f5f-efc4-46c3-8bb1-fcc08b89f682/Untitled.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIAT73L2G45HZZMZUHI%2F20240727%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Date=20240727T092456Z&X-Amz-Expires=3600&X-Amz-Signature=d316cacd1f90e0ad9f18f34888ba07277349c544d52abba0a9eb89bc35626ead&X-Amz-SignedHeaders=host&x-id=GetObject)
180
-
181
-
182
- 我们来看个例子:
183
-
184
-
185
- ```go
186
- package main
187
-
188
- import "fmt"
189
-
190
- func main() {
191
- x := 200
192
- var any interface{} = x
193
- fmt.Println(any)
194
-
195
- g := Gopher{"Go"}
196
- var c coder = g
197
- fmt.Println(c)
198
- }
199
-
200
- type coder interface {
201
- code()
202
- debug()
203
- }
204
-
205
- type Gopher struct {
206
- language string
207
- }
208
-
209
- func (p Gopher) code() {
210
- fmt.Printf("I am coding %s language\n", p.language)
211
- }
212
-
213
- func (p Gopher) debug() {
214
- fmt.Printf("I am debuging %s language\n", p.language)
215
- }
216
- ```
217
-
218
-
219
- 执行命令,打印出汇编语言:
220
-
221
-
222
- ```text
223
- go tool compile -S ./src/main.go
224
- ```
225
-
226
-
227
- 可以看到,main 函数里调用了两个函数:
228
-
229
-
230
- ```text
231
- func convT2E64(t *_type, elem unsafe.Pointer) (e eface)
232
- func convT2I(tab *itab, elem unsafe.Pointer) (i iface)
233
- ```
234
-
235
-
236
- 上面两个函数的参数和 `iface` 及 `eface` 结构体的字段是可以联系起来的:两个函数都是将参数`组装`一下,形成最终的接口。
237
-
238
-
239
- 作为补充,我们最后再来看下 `_type` 结构体:
240
-
241
-
242
- ```go
243
- type _type struct {
244
-    // 类型大小
245
- size       uintptr
246
-    ptrdata    uintptr
247
-    // 类型的 hash 值
248
-    hash       uint32
249
-    // 类型的 flag,和反射相关
250
-    tflag      tflag
251
-    // 内存对齐相关
252
-    align      uint8
253
-    fieldalign uint8
254
-    // 类型的编号,有bool, slice, struct 等等等等
255
- kind       uint8
256
- alg        *typeAlg
257
- // gc 相关
258
- gcdata    *byte
259
- str       nameOff
260
- ptrToThis typeOff
261
- }
262
- ```
263
-
264
-
265
- Go 语言各种数据类型都是在 `_type` 字段的基础上,增加一些额外的字段来进行管理的:
266
-
267
-
268
- ```go
269
- type arraytype struct {
270
- typ   _type
271
- elem  *_type
272
- slice *_type
273
- len   uintptr
274
- }
275
-
276
- type chantype struct {
277
- typ  _type
278
- elem *_type
279
- dir  uintptr
280
- }
281
-
282
- type slicetype struct {
283
- typ  _type
284
- elem *_type
285
- }
286
-
287
- type structtype struct {
288
- typ     _type
289
- pkgPath name
290
- fields []structfield
291
- }
292
- ```
293
-
294
-
295
- 这些数据类型的结构体定义,是反射实现的基础。
296
-
297
-
298
- # 什么是itab?
299
-
300
-
301
- iface中tab字段,是一個itab結構,包含了inter接口類型、_type數據類型、hash哈希的方法、fun函數地址佔位符。這個hash方法拷貝自_type.hash;fun是一個大小爲1的uintptr數組,**當fun[0]爲0時,說明_type並沒有實現該接口,當有實現接口時,fun存放了第一個接口方法的地址,其他方法一次往下存放,這裏就簡單用空間換時間,其實方法都在_type字段中能找到,實際在這記錄下,每次調用的時候就不用動態查找了**。
302
-
303
-
304
- ### 全局的itab table
305
-
306
-
307
- iface.go:
308
-
309
-
310
- ```go
311
- const itabInitSize = 512
312
-
313
- // Note: change the formula in the mallocgc call in itabAdd if you change these fields.
314
- type itabTableType struct {
315
- size uintptr // length of entries array. Always a power of 2.
316
- count uintptr // current number of filled entries.
317
- entries [itabInitSize]*itab // really [size] large
318
- }
319
-
320
- ```
321
-
322
-
323
- 可以看出這個全局的itabTable是用數組在存儲的;`size`記錄數組的大小,總是2的次冪;`count`記錄數組中已使用了多少;`entries`是一個`*itab`數組,初始大小是512。
324
-
325
-
326
- ### 獲取itab的流程
327
-
328
-
329
-
330
-
331
-
332
- golang interface的核心邏輯就在這,**在get的時候,不僅僅會從itabTalbe中查找,還可能會創建插入,itabTable使用容量超過75%還會擴容**。下面我們看下代碼:
333
-
334
-
335
- ```go
336
- func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
337
- if len(inter.mhdr) == 0 {
338
- throw("internal error - misuse of itab")
339
- }
340
-
341
- // easy case
342
- if typ.tflag&tflagUncommon == 0 {
343
- if canfail {
344
- return nil
345
- }
346
- name := inter.typ.nameOff(inter.mhdr[0].name)
347
- panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
348
- }
349
-
350
- var m *itab
351
-
352
- // First, look in the existing table to see if we can find the itab we need.
353
- // This is by far the most common case, so do it without locks.
354
- // Use atomic to ensure we see any previous writes done by the thread
355
- // that updates the itabTable field (with atomic.Storep in itabAdd).
356
- t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
357
- if m = t.find(inter, typ); m != nil {
358
- goto finish
359
- }
360
-
361
- // Not found. Grab the lock and try again.
362
- lock(&itabLock)
363
- if m = itabTable.find(inter, typ); m != nil {
364
- unlock(&itabLock)
365
- goto finish
366
- }
367
-
368
- // Entry doesn't exist yet. Make a new entry & add it.
369
- m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
370
- m.inter = inter
371
- m._type = typ
372
- m.init()
373
- itabAdd(m)
374
- unlock(&itabLock)
375
- finish:
376
- if m.fun[0] != 0 {
377
- return m
378
- }
379
- if canfail {
380
- return nil
381
- }
382
- // this can only happen if the conversion
383
- // was already done once using the , ok form
384
- // and we have a cached negative result.
385
- // The cached result doesn't record which
386
- // interface function was missing, so initialize
387
- // the itab again to get the missing function name.
388
- panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
389
- }
390
-
391
- ```
392
-
393
-
394
- 流程很簡單
395
-
396
- 1. 先用t保存全局itabTable的地址,然後使用t.find去查找,這樣是爲了防止查找過程中,itabTable被替換導致查找錯誤。
397
- 2. 如果沒找到,那麼就會上鎖,然後使用itabTable.find去查找,這樣是因爲在第一步查找的同時,另外一個協程寫入,可能導致實際存在卻查找不到,這時上鎖避免itabTable被替換,然後直接在itaTable中查找。
398
- 3. 再沒找到,說明確實沒有,那麼就根據接口類型、數據類型,去生成一個新的itab,然後插入到itabTable中,這裏可能會導致hash表擴容,如果數據類型並沒有實現接口,那麼根據調用方式,該報錯報錯,該panic panic。
399
-
400
- 這裏我們可以看到申請新的itab空間時,內存空間大小 `unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize`,參照前面接受的結構,`len(inter.mhdr)`就是接口定義的方法數量,因爲字段fun是一個大小爲1的數組,所以`len(inter.mhdr)-1`,在fun字段下面其實隱藏了其他方法接口地址。
401
-
402
-
403
- # 接口和nil
404
-
405
-
406
- 接口值的零值是指`动态类型`和`动态值`都为 `nil`。当仅且当这两部分的值都为 `nil` 的情况下,这个接口值就才会被认为 `接口值 == nil`。
407
-
408
-
409
- 来看个例子:
410
-
411
-
412
- ```go
413
- package main
414
-
415
- import "fmt"
416
-
417
- type Coder interface {
418
- code()
419
- }
420
-
421
- type Gopher struct {
422
- name string
423
- }
424
-
425
- func (g Gopher) code() {
426
- fmt.Printf("%s is coding\n", g.name)
427
- }
428
-
429
- func main() {
430
- var c Coder
431
- fmt.Println(c == nil)
432
- fmt.Printf("c: %T, %v\n", c, c)
433
-
434
- var g *Gopher
435
- fmt.Println(g == nil)
436
-
437
- c = g
438
- fmt.Println(c == nil)
439
- fmt.Printf("c: %T, %v\n", c, c)
440
- }
441
- ```
442
-
443
-
444
- 输出:
445
-
446
-
447
- ```go
448
- true
449
- c: <nil>, <nil>
450
- true
451
- false
452
- c: *main.Gopher, <nil>
453
- ```
454
-
455
-
456
- 一开始,`c` 的 动态类型和动态值都为 `nil`,`g` 也为 `nil`,当把 `g` 赋值给 `c` 后,`c` 的动态类型变成了 `*main.Gopher`,仅管 `c` 的动态值仍为 `nil`,但是当 `c` 和 `nil` 作比较的时候,结果就是 `false` 了。
457
-
458
-
459
- # 返回值为接口时的隐式转换
460
-
461
-
462
- 来看一个例子,看一下它的输出:
463
-
464
-
465
- ```go
466
- package main
467
-
468
- import "fmt"
469
-
470
- type MyError struct {}
471
-
472
- func (i MyError) Error() string {
473
- return "MyError"
474
- }
475
-
476
- func main() {
477
- err := Process()
478
- fmt.Println(err)
479
-
480
- fmt.Println(err == nil)
481
- }
482
-
483
- func Process() error {
484
- var err *MyError = nil
485
- return err
486
- }
487
- ```
488
-
489
-
490
- 函数运行结果:
491
-
492
-
493
- ```text
494
- <nil>
495
- false
496
- ```
497
-
498
-
499
- 这里先定义了一个 `MyError` 结构体,实现了 `Error` 函数,也就实现了 `error` 接口。`Process` 函数返回了一个 `error` 接口,这块隐含了类型转换。所以,虽然它的值是 `nil`,其实它的类型是 `*MyError`,最后和 `nil` 比较的时候,结果为 `false`。
500
-
501
-
502
- 让err成为结构体对象而非指针对象:
503
-
504
-
505
- ```go
506
- func Process() error {
507
- var err MyError = MyError{}
508
- return err
509
- }
510
- 或者
511
- func Process() error {
512
- var err *MyError = &MyError{}
513
- return err
514
- }
515
- ```
516
-
517
-
518
- 函数运行结果:
519
-
520
-
521
- ```text
522
- MyError
523
- false
524
- ```
525
-
526
-
527
- 所以,当不在乎值类型的时候,应该直接对空结构体指针赋nil,可以让代码逻辑更清晰
528
-
529
-
530
- # 打印接口的动态类型和值
531
-
532
-
533
- ```go
534
- package main
535
-
536
- import (
537
- "unsafe"
538
- "fmt"
539
- )
540
-
541
- type iface struct {
542
- itab, data uintptr
543
- }
544
-
545
- func main() {
546
- var a interface{} = nil
547
-
548
- var b interface{} = (*int)(nil)
549
-
550
- x := 5
551
- var c interface{} = (*int)(&x)
552
-
553
- ia := *(*iface)(unsafe.Pointer(&a))
554
- ib := *(*iface)(unsafe.Pointer(&b))
555
- ic := *(*iface)(unsafe.Pointer(&c))
556
-
557
- fmt.Println(ia, ib, ic)
558
-
559
- fmt.Println(*(*int)(unsafe.Pointer(ic.data)))
560
- }
561
- ```
562
-
563
-
564
- 代码里直接定义了一个 `iface` 结构体,用两个指针来描述 `itab` 和 `data`,之后将 a, b, c 在内存中的内容强制解释成我们自定义的 `iface`。最后就可以打印出动态类型和动态值的地址。
565
-
566
-
567
- 运行结果如下:
568
-
569
-
570
- ```text
571
- {0 0} {17426912 0} {17426912 842350714568}
572
- 5
573
- ```
574
-
575
-
576
- a 的动态类型和动态值的地址均为 0,也就是 nil;b 的动态类型和 c 的动态类型一致,都是 `*int`;最后,c 的动态值为 5。
577
-
578
-
579
- # 编译器检测接口是否实现
580
-
581
-
582
- 经常看到一些开源库里会有一些类似下面这种奇怪的用法:
583
-
584
-
585
- ```go
586
- var _ io.Writer = (*myWriter)(nil)
587
- ```
588
-
589
-
590
- 这时候会有点懵,不知道作者想要干什么,实际上这就是此问题的答案。编译器会由此检查 `*myWriter` 类型是否实现了 `io.Writer` 接口。
591
-
592
-
593
- 来看一个例子:
594
-
595
-
596
- ```go
597
- package main
598
-
599
- import "io"
600
-
601
- type myWriter struct {
602
-
603
- }
604
-
605
- /*func (w myWriter) Write(p []byte) (n int, err error) {
606
- return
607
- }*/
608
-
609
- func main() {
610
-    // 检查 *myWriter 类型是否实现了 io.Writer 接口
611
-    var _ io.Writer = (*myWriter)(nil)
612
-
613
-    // 检查 myWriter 类型是否实现了 io.Writer 接口
614
-    var _ io.Writer = myWriter{}
615
- }
616
- ```
617
-
618
-
619
- 注释掉为 myWriter 定义的 Write 函数后,运行程序:
620
-
621
-
622
- ```text
623
- src/main.go:14:6: cannot use (*myWriter)(nil) (type *myWriter) as type io.Writer in assignment:
624
- *myWriter does not implement io.Writer (missing Write method)
625
- src/main.go:15:6: cannot use myWriter literal (type myWriter) as type io.Writer in assignment:
626
- myWriter does not implement io.Writer (missing Write method)
627
- ```
628
-
629
-
630
- 报错信息:*myWriter/myWriter 未实现 io.Writer 接口,也就是未实现 Write 方法。
631
-
632
-
633
- 解除注释后,运行程序不报错。
634
-
635
-
636
- 实际上,上述赋值语句会发生隐式地类型转换,在转换的过程中,编译器会检测等号右边的类型是否实现了等号左边接口所规定的函数。
637
-
638
-
639
- 总结一下,可通过在代码中添加类似如下的代码,用来检测类型是否实现了接口:
640
-
641
-
642
- ```go
643
- var _ io.Writer = (*myWriter)(nil)
644
- var _ io.Writer = myWriter{}
645
- ```
646
-
647
-
648
- # 接口的构造
649
-
650
-
651
- ```go
652
- package main
653
-
654
- import "fmt"
655
-
656
- type Person interface {
657
- growUp()
658
- }
659
-
660
- type Student struct {
661
- age int
662
- }
663
-
664
- func (p Student) growUp() {
665
- p.age += 1
666
- return
667
- }
668
-
669
- func main() {
670
- var qcrao = Person(Student{age: 18})
671
-
672
- fmt.Println(qcrao)
673
- }
674
- ```
675
-
676
-
677
- 通过汇编可以看到调用了以下函数
678
-
679
-
680
- ```go
681
- func convT64(val uint64) (x unsafe.Pointer) {
682
- if val < uint64(len(staticuint64s)) {
683
- x = unsafe.Pointer(&staticuint64s[val])
684
- } else {
685
- x = mallocgc(8, uint64Type, false)
686
- *(*uint64)(x) = val
687
- }
688
- return
689
- }
690
- ```
691
-
692
-
693
- ```go
694
- rel 2+0 t=23 type:main.Student+0
695
- rel 2+0 t=23 type:*os.File+0
696
- rel 35+4 t=7 runtime.convT64+0
697
- rel 48+4 t=14 go:itab.main.Student,main.Person+8
698
- rel 65+4 t=14 os.Stdout+0
699
- rel 72+4 t=14 go:itab.*os.File,io.Writer+0
700
- rel 90+4 t=7 fmt.Fprintln+0
701
- rel 105+4 t=7 runtime.morestack_noctxt+0
702
- ```
703
-
704
-
705
- 需要注意的是,新版本的itab有所修改,size由40Byte变为32Byte
706
-
707
-
708
- ```go
709
- type itab struct {
710
- inter *interfacetype
711
- _type *_type
712
- hash uint32 // copy of _type.hash. Used for type switches.
713
- _ [4]byte
714
- fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
715
- }
716
- ```
717
-
718
-
719
- ## 如何打印出接口类型的 `Hash` 值
720
-
721
-
722
- 具体做法如下:
723
-
724
-
725
- ```go
726
- package main
727
-
728
- import (
729
- "fmt"
730
- "unsafe"
731
- )
732
-
733
- type iface struct {
734
- tab *itab
735
- data unsafe.Pointer
736
- }
737
-
738
- type itab struct {
739
- inter uintptr
740
- _type uintptr
741
- hash uint32 // copy of _type.hash. Used for type switches.
742
- _ [4]byte
743
- fun [1]uintptr // variable sized. fun[0]==0 means _type does not implement inter.
744
- }
745
-
746
- type Person interface {
747
- growUp()
748
- }
749
-
750
- type Student struct {
751
- age int
752
- }
753
-
754
- func (p Student) growUp() {
755
- p.age += 1
756
- return
757
- }
758
- func main() {
759
- var qcrao = Person(Student{age: 18})
760
- var pcrao = Person(Student{age: 177})
761
- ifaceq := (*iface)(unsafe.Pointer(&qcrao))
762
- ifacep := (*iface)(unsafe.Pointer(&pcrao))
763
- fmt.Printf("iface.tab.hash = %#x\n", ifaceq.tab.hash)
764
- fmt.Printf("iface.tab.hash = %#x\n", ifacep.tab.hash)
765
- }
766
- ```
767
-
768
-
769
- 定义了一个山寨版的 `iface` 和 `itab`,说它山寨是因为 `itab` 里的一些关键数据结构都不具体展开了,比如 `_type`,对比一下正宗的定义就可以发现,但是山寨版依然能工作,因为 `_type` 就是一个指针而已嘛。
770
-
771
-
772
- 在 `main` 函数里,先构造出一个接口对象 `qcrao`,然后强制类型转换,最后读取出 `hash` 值。
773
-
774
-
775
- 运行结果:
776
-
777
-
778
- ```text
779
- ➜ go run ./ifacehash.go
780
- iface.tab.hash = 0x7ab929eb
781
- iface.tab.hash = 0x7ab929eb
782
- ```
783
-
784
-
785
- 值得一提的是,构造接口 `qcrao` 的时候,即使我把 `age` 写成其他值,得到的 `hash` 值依然不变的,这应该是可以预料的,`hash` 值只和他的字段、方法相关。
786
-
787
-
788
- # 接口转换
789
-
790
-
791
- 通过前面提到的 `iface` 的源码可以看到,实际上它包含接口的类型 `interfacetype` 和 实体类型的类型 `_type`,这两者都是 `iface` 的字段 `itab` 的成员。也就是说生成一个 `itab` 同时需要接口的类型和实体的类型。
792
-
793
-
794
- > <interface 类型, 实体类型> ->itable
795
-
796
-
797
- **当判定一种类型是否满足某个接口时,Go 使用类型的方法集和接口所需要的方法集进行匹配,如果类型的方法集完全包含接口的方法集,则可认为该类型实现了该接口。**
798
-
799
-
800
- **例如某类型有** **`m`** **个方法,某接口有** **`n`** **个方法,则很容易知道这种判定的时间复杂度为** **`O(mn)`****,Go 会对方法集的函数按照函数名的字典序进行排序,所以实际的时间复杂度为** **`O(m+n)`****。**
801
-
802
-
803
- 直接来看一个例子:
804
-
805
-
806
- ```go
807
- package main
808
-
809
- import "fmt"
810
-
811
- type coder interface {
812
- code()
813
- run()
814
- }
815
-
816
- type runner interface {
817
- run()
818
- }
819
-
820
- type Gopher struct {
821
- language string
822
- }
823
-
824
- func (g Gopher) code() {
825
- return
826
- }
827
-
828
- func (g Gopher) run() {
829
- return
830
- }
831
-
832
- func main() {
833
- var c coder = Gopher{}
834
-
835
- var r runner
836
- r = c
837
- fmt.Println(c, r)
838
- }
839
- ```
840
-
841
-
842
- 简单解释下上述代码:定义了两个 `interface`: `coder` 和 `runner`。定义了一个实体类 `Gopher`,类型 `Gopher` 实现了两个方法,分别是 `run()` 和 `code()`。main 函数里定义了一个接口变量 `c`,绑定了一个 `Gopher` 对象,之后将 `c` 赋值给另外一个接口变量 `r` 。赋值成功的原因是 `c` 中包含 `run()` 方法。这样,两个接口变量完成了转换。
843
-
844
-
845
- 执行命令:
846
-
847
-
848
- ```text
849
- go tool compile -S ./src/main.go
850
- ```
851
-
852
-
853
- 得到 main 函数的汇编命令,可以看到: `r = c` 这一行语句实际上是调用`runtime.convI2I(SB)`,也就是 `convI2I` 函数,从函数名来看,就是将一个 `interface` 转换成另外一个 `interface`,看下它的源代码:
854
-
855
-
856
- ```go
857
- func convI2I(inter *interfacetype, i iface) (r iface) {
858
- tab := i.tab
859
- if tab == nil {
860
- return
861
- }
862
- if tab.inter == inter {
863
- r.tab = tab
864
- r.data = i.data
865
- return
866
- }
867
- r.tab = getitab(inter, tab._type, false)
868
- r.data = i.data
869
- return
870
- }
871
- ```
872
-
873
-
874
- 代码比较简单,函数参数 `inter` 表示接口类型,`i` 表示绑定了实体类型的接口,`r` 则表示接口转换了之后的新的 `iface`。通过前面的分析,我们又知道, `iface` 是由 `tab` 和 `data` 两个字段组成。所以,实际上 `convI2I` 函数真正要做的事,找到新 `interface` 的 `tab` 和 `data`,就大功告成了。
875
-
876
-
877
- 我们还知道,`tab` 是由接口类型 `interfacetype` 和 实体类型 `_type`。所以最关键的语句是 `r.tab = getitab(inter, tab._type, false)`。
878
-
879
-
880
- 因此,重点来看下 `getitab` 函数的源码,只看关键的地方:
881
-
882
-
883
-
884
-
885
-
886
- golang interface的核心邏輯就在這,**在get的時候,不僅僅會從itabTalbe中查找,還可能會創建插入,itabTable使用容量超過75%還會擴容**。下面我們看下代碼:
887
-
888
-
889
- ```go
890
- func getitab(inter *interfacetype, typ *_type, canfail bool) *itab {
891
- if len(inter.mhdr) == 0 {
892
- throw("internal error - misuse of itab")
893
- }
894
-
895
- // easy case
896
- if typ.tflag&tflagUncommon == 0 {
897
- if canfail {
898
- return nil
899
- }
900
- name := inter.typ.nameOff(inter.mhdr[0].name)
901
- panic(&TypeAssertionError{nil, typ, &inter.typ, name.name()})
902
- }
903
-
904
- var m *itab
905
-
906
- // First, look in the existing table to see if we can find the itab we need.
907
- // This is by far the most common case, so do it without locks.
908
- // Use atomic to ensure we see any previous writes done by the thread
909
- // that updates the itabTable field (with atomic.Storep in itabAdd).
910
- t := (*itabTableType)(atomic.Loadp(unsafe.Pointer(&itabTable)))
911
- if m = t.find(inter, typ); m != nil {
912
- goto finish
913
- }
914
-
915
- // Not found. Grab the lock and try again.
916
- lock(&itabLock)
917
- if m = itabTable.find(inter, typ); m != nil {
918
- unlock(&itabLock)
919
- goto finish
920
- }
921
-
922
- // Entry doesn't exist yet. Make a new entry & add it.
923
- m = (*itab)(persistentalloc(unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize, 0, &memstats.other_sys))
924
- m.inter = inter
925
- m._type = typ
926
- m.init()
927
- itabAdd(m)
928
- unlock(&itabLock)
929
- finish:
930
- if m.fun[0] != 0 {
931
- return m
932
- }
933
- if canfail {
934
- return nil
935
- }
936
- // this can only happen if the conversion
937
- // was already done once using the , ok form
938
- // and we have a cached negative result.
939
- // The cached result doesn't record which
940
- // interface function was missing, so initialize
941
- // the itab again to get the missing function name.
942
- panic(&TypeAssertionError{concrete: typ, asserted: &inter.typ, missingMethod: m.init()})
943
- }
944
-
945
- ```
946
-
947
-
948
- 流程很簡單
949
-
950
- 1. 先用t保存全局itabTable的地址,然後使用t.find去查找,這樣是爲了防止查找過程中,itabTable被替換導致查找錯誤。
951
- 2. 如果沒找到,那麼就會上鎖,然後使用itabTable.find去查找,這樣是因爲在第一步查找的同時,另外一個協程寫入,可能導致實際存在卻查找不到,這時上鎖避免itabTable被替換,然後直接在itaTable中查找。
952
- 3. 再沒找到,說明確實沒有,那麼就根據接口類型、數據類型,去生成一個新的itab,然後插入到itabTable中,這裏可能會導致hash表擴容,如果數據類型並沒有實現接口,那麼根據調用方式,該報錯報錯,該panic panic。
953
-
954
- 這裏我們可以看到申請新的itab空間時,內存空間大小 `unsafe.Sizeof(itab{})+uintptr(len(inter.mhdr)-1)*sys.PtrSize`,參照前面接受的結構,`len(inter.mhdr)`就是接口定義的方法數量,因爲字段fun是一個大小爲1的數組,所以`len(inter.mhdr)-1`,在fun字段下面其實隱藏了其他方法接口地址。
955
-
956
-
957
- 再来看一下 `itabAdd` 函数的代码:
958
-
959
-
960
- ```go
961
- func itabAdd(m *itab) {
962
- // Bugs can lead to calling this while mallocing is set,
963
- // typically because this is called while panicking.
964
- // Crash reliably, rather than only when we need to grow
965
- // the hash table.
966
- if getg().m.mallocing != 0 {
967
- throw("malloc deadlock")
968
- }
969
-
970
- t := itabTable
971
- if t.count >= 3*(t.size/4) { // 75% load factor
972
- // Grow hash table.
973
- // t2 = new(itabTableType) + some additional entries
974
- // We lie and tell malloc we want pointer-free memory because
975
- // all the pointed-to values are not in the heap.
976
- t2 := (*itabTableType)(mallocgc((2+2*t.size)*goarch.PtrSize, nil, true))
977
- t2.size = t.size * 2
978
-
979
- // Copy over entries.
980
- // Note: while copying, other threads may look for an itab and
981
- // fail to find it. That's ok, they will then try to get the itab lock
982
- // and as a consequence wait until this copying is complete.
983
- iterate_itabs(t2.add)
984
- if t2.count != t.count {
985
- throw("mismatched count during itab table copy")
986
- }
987
- // Publish new hash table. Use an atomic write: see comment in getitab.
988
- atomicstorep(unsafe.Pointer(&itabTable), unsafe.Pointer(t2))
989
- // Adopt the new table as our own.
990
- t = itabTable
991
- // Note: the old table can be GC'ed here.
992
- }
993
- t.add(m)
994
- }
995
- ```
996
-
997
-
998
- `itabAdd` 会检查 `m.mallocing`标志位,然后检查`itabTable`的负载因子。如果大于阈值`3/4`就扩容`2`倍并复制所有的旧的`itab`到`t2`指针指向的内存,最后进行一个指针的换,丢弃原来的`itabTable` ,最后再添加新的`m *itab` 。
999
-
1000
-
1001
- 新版本中`itabTable`不再是哈希表,而是切片,`itabTable.find(inter, typ)`是怎么做的:
1002
-
1003
-
1004
- ```go
1005
- func (t *itabTableType) find(inter *interfacetype, typ *_type) *itab {
1006
- // Implemented using quadratic probing.
1007
- // Probe sequence is h(i) = h0 + i*(i+1)/2 mod 2^k.
1008
- // We're guaranteed to hit all table entries using this probe sequence.
1009
- mask := t.size - 1
1010
- h := itabHashFunc(inter, typ) & mask
1011
- for i := uintptr(1); ; i++ {
1012
- p := (**itab)(add(unsafe.Pointer(&t.entries), h*goarch.PtrSize))
1013
- // Use atomic read here so if we see m != nil, we also see
1014
- // the initializations of the fields of m.
1015
- // m := *p
1016
- m := (*itab)(atomic.Loadp(unsafe.Pointer(p)))
1017
- if m == nil {
1018
- return nil
1019
- }
1020
- if m.inter == inter && m._type == typ {
1021
- return m
1022
- }
1023
- h += i
1024
- h &= mask
1025
- }
1026
- }
1027
- ```
1028
-
1029
-
1030
- `itabHashFunc`会搞出一个哈希值,具体值是`inter`和`typ`相与。这个结果再和`mask`相与,获得一个范围在`[0, t.size]`之间的值。然后使用 _quadratic probing_ 进行hash插入,如果想refer to有关hash的内容:[All I need to know about basic hash](/762351b4a6494b3e95d0addbd2c0029a)
1031
-
1032
-
1033
- 更一般的,当把实体类型赋值给接口的时候,会调用 `conv` 系列函数,例如空接口调用 `convT2E` 系列、非空接口调用 `convT2I` 系列。这些函数比较相似:
1034
-
1035
-
1036
- > 具体类型转空接口时,_type 字段直接复制源类型的 _type;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。具体类型转非空接口时,入参 tab 是编译器在编译阶段预先生成好的,新接口 tab 字段直接指向入参 tab 指向的 itab;调用 mallocgc 获得一块新内存,把值复制进去,data 再指向这块新内存。~~而对于接口转接口,itab 调用 getitab 函数获取。只用生成一次,之后直接从 hash 表中获取。~~
1037
-
1038
-
1039
- # 类型转换和断言
1040
-
1041
-
1042
- 我们知道,Go 语言中不允许隐式类型转换,也就是说 `=` 两边,不允许出现类型不相同的变量。
1043
-
1044
-
1045
- `类型转换`、`类型断言`本质都是把一个类型转换成另外一个类型。不同之处在于,类型断言是对接口变量进行的操作。
1046
-
1047
-
1048
- ## 类型转换
1049
-
1050
-
1051
- 对于`类型转换`而言,转换前后的两个类型要相互兼容才行。类型转换的语法为:
1052
-
1053
-
1054
- > <结果类型> := <目标类型> ( <表达式> )
1055
-
1056
-
1057
- ```go
1058
- package main
1059
-
1060
- import "fmt"
1061
-
1062
- func main() {
1063
- var i int = 9
1064
-
1065
- var f float64
1066
- f = float64(i)
1067
- fmt.Printf("%T, %v\n", f, f)
1068
-
1069
- f = 10.8
1070
- a := int(f)
1071
- fmt.Printf("%T, %v\n", a, a)
1072
-
1073
- // s := []int(i)
1074
- }
1075
- ```
1076
-
1077
-
1078
- 上面的代码里,我定义了一个 `int` 型和 `float64` 型的变量,尝试在它们之前相互转换,结果是成功的:`int` 型和 `float64` 是相互兼容的。
1079
-
1080
-
1081
- 如果我把最后一行代码的注释去掉,编译器会报告类型不兼容的错误:
1082
-
1083
-
1084
- ```text
1085
- cannot convert i (type int) to type []int
1086
- ```
1087
-
1088
-
1089
- ## 断言
1090
-
1091
-
1092
- 前面说过,因为空接口 `interface{}` 没有定义任何函数,因此 Go 中所有类型都实现了空接口。当一个函数的形参是 `interface{}`,那么在函数中,需要对形参进行断言,从而得到它的真实类型。
1093
-
1094
-
1095
- 断言的语法为:
1096
-
1097
-
1098
- > <目标类型的值>,<布尔参数> := <表达式>.( 目标类型 ) // 安全类型断言
1099
- > <目标类型的值> := <表达式>.( 目标类型 )  //非安全类型断言
1100
-
1101
-
1102
- 类型转换和类型断言有些相似,不同之处,在于类型断言是对接口进行的操作。
1103
-
1104
-
1105
- 还是来看一个简短的例子:
1106
-
1107
-
1108
- ```go
1109
- package main
1110
-
1111
- import "fmt"
1112
-
1113
- type Student struct {
1114
- Name string
1115
- Age int
1116
- }
1117
-
1118
- func main() {
1119
- var i interface{} = new(Student)
1120
- s := i.(Student)
1121
-
1122
- fmt.Println(s)
1123
- }
1124
- ```
1125
-
1126
-
1127
- 运行一下:
1128
-
1129
-
1130
- ```text
1131
- panic: interface conversion: interface {} is *main.Student, not main.Student
1132
- ```
1133
-
1134
-
1135
- 直接 `panic` 了,这是因为 `i` 是 `*Student` 类型,并非 `Student` 类型,断言失败。这里直接发生了 `panic`,线上代码可能并不适合这样做,可以采用“安全断言”的语法:
1136
-
1137
-
1138
- ```go
1139
- func main() {
1140
- var i interface{} = new(Student)
1141
- s, ok := i.(Student)
1142
- if ok {
1143
- fmt.Println(s)
1144
- }
1145
- }
1146
- ```
1147
-
1148
-
1149
- 这样,即使断言失败也不会 `panic`。
1150
-
1151
-
1152
- 断言其实还有另一种形式,就是用在利用 `switch` 语句判断接口的类型。每一个 `case` 会被顺序地考虑。当命中一个 `case` 时,就会执行 `case` 中的语句,因此 `case` 语句的顺序是很重要的,因为很有可能会有多个 `case` 匹配的情况。
1153
-
1154
-
1155
- 代码示例如下:
1156
-
1157
-
1158
- ```go
1159
- func main() {
1160
- //var i interface{} = new(Student)
1161
- //var i interface{} = (*Student)(nil)
1162
- var i interface{}
1163
-
1164
- fmt.Printf("%p %v\n", &i, i)
1165
-
1166
- judge(i)
1167
- }
1168
-
1169
- func judge(v interface{}) {
1170
- fmt.Printf("%p %v\n", &v, v)
1171
-
1172
- switch v := v.(type) {
1173
- case nil:
1174
- fmt.Printf("%p %v\n", &v, v)
1175
- fmt.Printf("nil type[%T] %v\n", v, v)
1176
-
1177
- case Student:
1178
- fmt.Printf("%p %v\n", &v, v)
1179
- fmt.Printf("Student type[%T] %v\n", v, v)
1180
-
1181
- case *Student:
1182
- fmt.Printf("%p %v\n", &v, v)
1183
- fmt.Printf("*Student type[%T] %v\n", v, v)
1184
-
1185
- default:
1186
- fmt.Printf("%p %v\n", &v, v)
1187
- fmt.Printf("unknow\n")
1188
- }
1189
- }
1190
-
1191
- type Student struct {
1192
- Name string
1193
- Age int
1194
- }
1195
-
1196
- ```
1197
-
1198
-
1199
- `main` 函数里有三行不同的声明,每次运行一行,注释另外两行,得到三组运行结果:
1200
-
1201
-
1202
- ```text
1203
- // --- var i interface{} = new(Student)
1204
- 0xc4200701b0 [Name: ], [Age: 0]
1205
- 0xc4200701d0 [Name: ], [Age: 0]
1206
- 0xc420080020 [Name: ], [Age: 0]
1207
- *Student type[*main.Student] [Name: ], [Age: 0]
1208
-
1209
- // --- var i interface{} = (*Student)(nil)
1210
- 0xc42000e1d0 <nil>
1211
- 0xc42000e1f0 <nil>
1212
- 0xc42000c030 <nil>
1213
- *Student type[*main.Student] <nil>
1214
-
1215
- // --- var i interface{}
1216
- 0xc42000e1d0 <nil>
1217
- 0xc42000e1e0 <nil>
1218
- 0xc42000e1f0 <nil>
1219
- nil type[<nil>] <nil>
1220
- ```
1221
-
1222
-
1223
- 对于第一行语句:
1224
-
1225
-
1226
- ```go
1227
- var i interface{} = new(Student)
1228
- ```
1229
-
1230
-
1231
- `i` 是一个 `*Student` 类型,匹配上第三个 case,从打印的三个地址来看,这三处的变量实际上都是不一样的。在 `main` 函数里有一个局部变量 `i`;调用函数时,实际上是复制了一份参数,因此函数里又有一个变量 `v`,它是 `i` 的拷贝;断言之后,又生成了一份新的拷贝。所以最终打印的三个变量的地址都不一样。
1232
-
1233
-
1234
- 对于第二行语句:
1235
-
1236
-
1237
- ```go
1238
- var i interface{} = (*Student)(nil)
1239
- ```
1240
-
1241
-
1242
- 这里想说明的其实是 `i` 在这里动态类型是 `(*Student)`, 数据为 `nil`,它的类型并不是 `nil`,它与 `nil` 作比较的时候,得到的结果也是 `false`。
1243
-
1244
-
1245
- 最后一行语句:
1246
-
1247
-
1248
- ```go
1249
- var i interface{}
1250
- ```
1251
-
1252
-
1253
- 这回 `i` 才是 `nil` 类型。
1254
-
1255
-
1256
- 【引申1】
1257
- `fmt.Println` 函数的参数是 `interface`。对于内置类型,函数内部会用穷举法,得出它的真实类型,然后转换为字符串打印。而对于自定义类型,首先确定该类型是否实现了 `String()` 方法,如果实现了,则直接打印输出 `String()` 方法的结果;否则,会通过反射来遍历对象的成员进行打印。
1258
-
1259
-
1260
- 再来看一个简短的例子,比较简单,不要紧张:
1261
-
1262
-
1263
- ```go
1264
- package main
1265
-
1266
- import "fmt"
1267
-
1268
- type Student struct {
1269
- Name string
1270
- Age int
1271
- }
1272
-
1273
- func main() {
1274
- var s = Student{
1275
- Name: "qcrao",
1276
- Age: 18,
1277
- }
1278
-
1279
- fmt.Println(s)
1280
- }
1281
- ```
1282
-
1283
-
1284
- 因为 `Student` 结构体没有实现 `String()` 方法,所以 `fmt.Println` 会利用反射挨个打印成员变量:
1285
-
1286
-
1287
- ```text
1288
- {qcrao 18}
1289
- ```
1290
-
1291
-
1292
- 增加一个 `String()` 方法的实现:
1293
-
1294
-
1295
- ```text
1296
- func (s Student) String() string {
1297
- return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
1298
- }
1299
- ```
1300
-
1301
-
1302
- 打印结果:
1303
-
1304
-
1305
- ```text
1306
- [Name: qcrao], [Age: 18]
1307
- ```
1308
-
1309
-
1310
- 按照我们自定义的方法来打印了。
1311
-
1312
-
1313
- 【引申2】
1314
- 针对上面的例子,如果改一下:
1315
-
1316
-
1317
- ```text
1318
- func (s *Student) String() string {
1319
- return fmt.Sprintf("[Name: %s], [Age: %d]", s.Name, s.Age)
1320
- }
1321
- ```
1322
-
1323
-
1324
- 注意看两个函数的接受者类型不同,现在 `Student` 结构体只有一个接受者类型为 `指针类型` 的 `String()` 函数,打印结果:
1325
-
1326
-
1327
- ```text
1328
- {qcrao 18}
1329
- ```
1330
-
1331
-
1332
- 为什么?
1333
-
1334
-
1335
- > 类型 T 只有接受者是 T 的方法;而类型 *T 拥有接受者是 T 和 *T 的方法。语法上 T 能直接调 *T 的方法仅仅是 Go 的语法糖。
1336
-
1337
-
1338
- 所以, `Student` 结构体定义了接受者类型是值类型的 `String()` 方法时,通过
1339
-
1340
-
1341
- ```text
1342
- fmt.Println(s)
1343
- fmt.Println(&s)
1344
- ```
1345
-
1346
-
1347
- 均可以按照自定义的格式来打印。
1348
-
1349
-
1350
- 如果 `Student` 结构体定义了接受者类型是指针类型的 `String()` 方法时,只有通过
1351
-
1352
-
1353
- ```text
1354
- fmt.Println(&s)
1355
- ```
1356
-
1357
-
1358
- 才能按照自定义的格式打印。
1359
-
1360
-
1361
- # 接口与纯虚函数
1362
-
1363
-
1364
- 接口定义了一种规范,描述了类的行为和功能,而不做具体实现。
1365
-
1366
-
1367
- C++ 的接口是使用抽象类来实现的,如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。纯虚函数是通过在声明中使用 "= 0" 来指定的。例如:
1368
-
1369
-
1370
- ```c++
1371
- class Shape
1372
- {
1373
-   public:
1374
-      // 纯虚函数
1375
-      virtual double getArea() = 0;
1376
-   private:
1377
-      string name;      // 名称
1378
- };
1379
- ```
1380
-
1381
-
1382
- 设计抽象类的目的,是为了给其他类提供一个可以继承的适当的基类。抽象类不能被用于实例化对象,它只能作为接口使用。
1383
-
1384
-
1385
- 派生类需要明确地声明它继承自基类,并且需要实现基类中所有的纯虚函数。
1386
-
1387
-
1388
- C++ 定义接口的方式称为“侵入式”,而 Go 采用的是 “非侵入式”,不需要显式声明,只需要实现接口定义的函数,编译器自动会识别。
1389
-
1390
-
1391
- C++ 和 Go 在定义接口方式上的不同,也导致了底层实现上的不同。C++ 通过虚函数表来实现基类调用派生类的函数;而 Go 通过 `itab` 中的 `fun` 字段来实现接口变量调用实体类型的函数。C++ 中的虚函数表是在编译期生成的;而 Go 的 `itab` 中的 `fun` 字段是在运行期间动态生成的。**原因在于,Go 中实体类型可能会无意中实现 N 多接口,很多接口并不是本来需要的,所以不能为类型实现的所有接口都生成一个** **`itab`****, 这也是“非侵入式”带来的影响**;这在 C++ 中是不存在的,因为派生需要显示声明它继承自哪个基类。
1392
-