WRY

Where Are You?
You are on the brave land,
To experience, to remember...

0%

奇怪的Go,不!奇怪的你

Bug 1

在一个成员函数中对一个对象的成员变量进行赋值之后,在其他地方使用无效。

成员函数的说法存疑

v1

源代码

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

import "fmt"

type FileHandler struct {
filename string
}
type Engine struct {
fh *FileHandler
}

func (engine Engine) init() {
fh := &FileHandler{
filename: "a special filename",
}
engine.fh = fh
fmt.Println(fmt.Sprintf("init: fh: %v", engine.fh))
}
func (engine Engine) doSomething() {
fmt.Println("doSomething", engine.fh)
}

func main() {
a := &Engine{}
a.init()
a.doSomething()
}

输出

1
2
init: fh: &{a special filename}
doSomething <nil>

v2

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"

type FileHandler struct {
filename string
}
type Engine struct {
fh *FileHandler
}

func (engine Engine) init() {
fh := new(FileHandler)
fh.filename = "new a special filename"
engine.fh = fh
fmt.Println(fmt.Sprintf("init: fh: %v", engine.fh))
}
func (engine Engine) doSomething() {
fmt.Println("doSomething", engine.fh)
}

func main() {
a := &Engine{}
a.init()
a.doSomething()
}

输出

1
2
init: fh: &{new a special filename}
doSomething <nil>

v3

源代码

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

import "fmt"

type FileHandler struct {
filename string
}
type Engine struct {
fh *FileHandler
}

func NewFileHandler() *FileHandler {
return &FileHandler{
filename: "new a special filename",
}
}

func (engine Engine) init() {
engine.fh = NewFileHandler()
fmt.Println(fmt.Sprintf("init: fh: %v", engine.fh))
}
func (engine Engine) doSomething() {
fmt.Println("doSomething", engine.fh)
}

func main() {
a := &Engine{
fh: NewFileHandler(),
}
//a.init()
a.doSomething()
}

输出

1
doSomething &{new a special filename}

问题探讨

在v1和v2版本的代码中,在init函数中对engine的fh变量赋值之后,在其他的成员函数中进行使用发现指针仍为nil。这完全颠覆了我的理解。(v1和v2的结果相同倒是可以理解,Go有逃逸机制)。只有v3这种写法才能让指针初始化成功。

应该对V1和V2进行如下改动,Engine需要加指针

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"

type FileHandler struct {
filename string
}
type Engine struct {
fh *FileHandler
val int
}

func (engine *Engine) init() {
fmt.Println(fmt.Sprintf("before init: val: %v", engine.val))
fh := &FileHandler{filename: "new a special filename"}
engine.fh = fh
engine.val = 100
fmt.Println(fmt.Sprintf("init: fh: %v", engine.fh))
}
func (engine *Engine) doSomething() {
fmt.Println("doSomething", engine.fh, " ", engine.val)
}

func main() {
a := Engine{val: -1}
a.init()
a.doSomething()
fmt.Println(a.val)
}

输出

1
2
3
4
before init: val: -1
init: fh: &{new a special filename}
doSomething &{new a special filename} 100
100

通过修正的写法,可以猜测到不添加指针的时候使用的是拷贝的一份engine执行函数操作,只有声明成指针的时候,拷贝的是对象的地址,执行函数操作才会改变原来对象的值,十分的interesting。

显然Go并不希望我们叫init和doSomething为成员函数。

好智障,果然奇怪的是我

补充一点,这里提到了new的用法,实际作用就是创建一个空间,初始化空间为0,返回空间的地址。本质上和&FileHandler{}没有区别

刚遇到这个问题的时候,有一种三体里面的物理学家被智子困扰时的绝望,完全颠覆了我的理解。 果然我不是当科学家的料,只是个渣渣,想当然给写错了。