今天突然意识到一个问题:go 的代码中,关于调用结构体指针绑定的方法的时候,方法里面似乎都没有判断该指针是否为 nil 的情况。
type A struct {
a int
}
func (a *A) Run() {
fmt.Print(a.a)
}
func TestA(t *testing.T) {
var a *A
a.Run()
}
以上代码会报错,原因也很明显,a 为 nil, a.Run() 方法中,访问 a.a 报错。 看了一下自己之前写的所有 go 代码,所有 结构体指针绑定的方法里面,都没有判断是否为 nil 的情况。
1
airplayxcom 2022-10-18 16:26:06 +08:00 33
会 go 不会 go 的都哑口无言了
|
2
0o0O0o0O0o 2022-10-18 16:26:50 +08:00 via iPhone
在一些项目源码里见到过判断的,不过我是觉得没必要。
|
3
randm 2022-10-18 16:33:24 +08:00
会报 panic
|
4
coderxy 2022-10-18 16:36:15 +08:00
if a!=nil ?
|
5
hsfzxjy 2022-10-18 16:38:21 +08:00 via Android
这种一般是调用者保证它非 nil
|
6
warlock 2022-10-18 16:38:36 +08:00
这取决于哪里需要关心 a 为 nil 这件事
|
7
charmToby 2022-10-18 16:41:37 +08:00
|
8
XyIsMy 2022-10-18 16:45:31 +08:00
你这代码只是声明了 变量 a 是 A 类型。没有实例化
|
9
fuxiaohei 2022-10-18 16:48:55 +08:00
判不判断 nil 看需求,有时候结构体绑定的方法是当函数来用的
```golang import "fmt" type A struct { a int } func (a *A) Run() { fmt.Print(a.a) } func main() { var fn = (*A).Run fmt.Println(fn) fn(&A{111}) } ``` |
10
zhs227 2022-10-18 16:50:51 +08:00
变量没初始化不能怪函数绑定。
|
11
pkoukk 2022-10-18 16:54:53 +08:00
|
12
JKeita 2022-10-18 17:02:30 +08:00
你只是声明了个 A 指针类型变量,肯定会报错啊。
|
13
tianyou666shen 2022-10-18 17:07:43 +08:00 3
家人们 见鬼了
声明变量但不初始化对象 居然不能调用对象方法 |
15
zhuweiyou 2022-10-18 17:22:19 +08:00
要么 new, 要么 &A{} 吧
|
16
siloong 2022-10-18 17:26:26 +08:00
想到了开心的事
```golang type A struct{} func (*A) hello() { fmt.Println("hello, world") } func main() { var a *A fmt.Printf("a is nil? %v\n", a == nil) a.hello() } ``` |
18
sardina 2022-10-18 17:32:31 +08:00 1
我要调某个结构体的方法,但是我不初始化这个结构体,哎,我就是玩
|
19
Citrus 2022-10-18 17:33:03 +08:00
@siloong 没毛病啊,你的 hello 又没用 A 里的东西,当然能跑。改成这样比较有意思:
type A struct{} func (*A) hello() { fmt.Println("hello, world") } func main() { var a *A var b interface{} b = a fmt.Printf("a is nil? %v\n", a == nil) fmt.Printf("b is nil? %v\n", b == nil) a.hello() } |
21
lxdlam 2022-10-18 17:58:55 +08:00 1
Go 实际上编译生成的函数只是在语义层面上跟原始类型绑定,而在之后的代码生成过程中,Go 隐式生成了一个对应的函数,这个新的函数跟老的类型是解耦的,并把对象本身作为第一个参数传入。
举个例子来说,考虑你的测试代码,Go 编译器会生成下面的 stub: ```go func (*A).Run(a *A) { fmt.Print(a.a) } ``` 这种函数签名是你不能写出来的,因为在用户层面这种语法是 invalid 的,但是你可以调用。尝试在你的 Test 中插入: ```go func TestA(t *testing.T) { var a *A a.Run() } func TestAnother(t *testing.T) { var a *A (*A).Run(a) } ``` 代码可以正常编译执行,然后报错。 而假如我定义另一个方法,不访问 A 的内部参数,即不依赖 A 的值,你会发现代码能够安全运行,没有任何问题( 16 楼老哥已经有 runnable 了)。 这种情况是不是比较反直觉?而实际上 GCC 和 clang 也是这么做的: https://godbolt.org/z/xb9WWz53x 。当然,如果你打开 undefined behavior santizer ,你会发现编译器报错了,因为标准并没有对这个情况作出规定。 所以是否需要检查 receiver 是不是 nil ?取决于你。如果这个方法你不想 panic ,那你可以检查 `if p != nil` 并做出恰当报错,但是这会影响大家对于 method call 语义的共识理解(如果 a 为 nil ,我调用它的方法应该 panic 啊?为什么没有 panic ?)。或者直接 let it crash ,也方便查错。 |
22
chengyunbo 2022-10-18 18:08:04 +08:00
蚌埠住了
|
23
yougg 2022-10-18 20:06:15 +08:00
```go
// 以下是定义在结构体值上的方法原型,通过调用结构体类型上定义的函数,传入结构体的值 A.echo_A(a) // (_ A) A.echoA(a, "a") // (A) a // A.echo_жA(a) // A.echo_жA 未定义 // A.echoжA(a) // A.echoжA 未定义 A.setX(a, 4) // A.setY(a, 7) // A.setY 未定义 println(a.x) // 0 ``` 给你参考一下: https://github.com/yougg/gonote/blob/main/gogrammar.md#%E6%96%B9%E6%B3%95-method |
24
tairan2006 2022-10-18 20:16:16 +08:00
go 的习惯一般是外围判断的,这算是一种约定
|
25
joesonw 2022-10-18 21:54:21 +08:00 via iPhone
go 的方法你理解成 fun Run(a *A)就好了
|
26
littlewing 2022-10-18 21:59:51 +08:00
```cpp
class A { public: void f1() { cout << "foobar " << this->a;} private: int a; }; int main() { A *a = nullptr; a->f1(); } ``` 也不需要判断 this 是不是 NULL 啊,这样是不是好理解多了 |
27
Jaron0608 2022-10-18 22:29:34 +08:00
这回复楼层不支持 markdown ,真是看的头皮发麻
|
28
lanlanye 2022-10-18 23:33:56 +08:00
虽然空值设计确实很麻烦,而且 Go 的 nil 有时候让人感到别扭,但你这个例子……
我更想知道结构体里的 *bool 和 *string 这种东西有没有什么好的解决方案…… |
29
AnroZ 2022-10-19 00:09:13 +08:00
struct 指针判断是否为空还是很容易理解,毕竟 c/c++都是如此,golang 不显性的是 interface{} 对象无法通过 != nil 来直接判断是否为空,我也是写了好几个月代码后才发现。
|
30
Trim21 2022-10-19 00:11:54 +08:00
你在(a *A) Run()里面也是可以判断 a 是不是 nil 的。
|
31
colatin 2022-10-19 01:31:02 +08:00
总有一个人要承担责任,显然调用者是最佳人选。
|
32
iceheart 2022-10-19 06:45:29 +08:00 via Android
所以使用 this 指针的时候要不要判断它是不是 null?
要啊! go 又没有异常处理,你得返回一个 error |
34
Nzelites 2022-10-19 10:40:07 +08:00
这个问题其实挺好玩的 理论上是可以弄成类似 static 函数的行为 但是 go 这么写显然会被打
|
35
DefoliationM 2022-10-19 11:21:34 +08:00
可以调用方法,但是用了结构体里的东西,没初始化就 panic 了
|
36
AmosAlbert 2022-10-19 22:42:23 +08:00
|