Golang 中国

程序:

func main() {
    var t *T
    var i interface{} = t
    fmt.Println(t == nil, i == t, i == nil)
}

结果:true true false

t,i,nil三者间只有两个==成立,我知道原因是interface{}两个成员造成。但这样一个违背常识的语义特征就这么永远保持下去吗?

22 回复
stevewang
#1 stevewang • 2014-08-28 19:15

说到常识,我也很想知道为什么C里0.00003不等于0.00003,为什么这样一个违背常识的语义特征要在C里永远保持下去。

#include <stdio.h>

int main() {
    float f1 = 0.00001;
    float f2 = 0.00002;
    float f3 = f1 + f2;
    printf("%f, %d", f3, f3 == 0.00003);
    return 0;
}
snake117
#2 snake117 • 2014-08-28 19:20

nil接口和保管nil值的接口当然是两回事

stevewang
#3 stevewang • 2014-08-28 19:24

C++也违反了==传递性的常识。。。

#include <stdio.h>

struct A {
    int a;
};

struct B : public A {
    int b;
};

struct C : public A {
    int c;
};

int main() {
    A* a = 0;
    B* b = 0;
    C* c = 0;
    printf("%d, %d, %d", a == b, a == c, b == c);
    return 0;
}
dayn9
#4 dayn9 • 2014-08-28 23:12

浮点数的问题,目前在不牺牲性能的情况下绝无解决可能,是硬缺陷,而这里探讨的是语言设计的软问题,无可比性。楼上情况到有相似性,不过b == c毕竟不能比,其提示作用要鲜明得多。退一步说,假设两者便斤八两,但拿世界上坑最多的语言来佐证另一个语言的坑的合理性,恐怕欠说服力。

我也并非不知道是怎么回事,只是困惑于是不是有更好的解决方案。

stevewang
#5 stevewang • 2014-08-28 23:18

每种语言都有自己的惯用法,仅此而已。 go是工程语言,致力于解决实际问题,而不是去追求形式上的完美。这是我的理解。

ThoseFlowers
#6 ThoseFlowers • 2014-08-28 23:30

nil一词多义
第一个nil表示空指针,第二个nil表示没有“挂上东西”的接口
第一个比较式是判断t是否为空指针,第二个比较式是判断这个接口是否没“挂上东西”

Bluek404
#7 Bluek404 • 2014-08-31 15:33

这个和指针有关吧 如果这样写:

package main

import "fmt"

type T interface{}

func main() {
    var t T
    var i interface{} = t
    fmt.Println(t == nil, i == t, i == nil)
}

true true true

ggaaooppeenngg
#8 ggaaooppeenngg • 2014-08-31 22:21
package main

import (
    "fmt"
)

type T interface{}

func main() {
    var t *T
    var i interface{}
    var q interface{} = t
    fmt.Println(t == nil, i == nil, q == nil)
}

我试了一下,我觉得@Bluek404 说的是对的.

ggaaooppeenngg
#9 ggaaooppeenngg • 2014-08-31 22:24

还有 @ThoseFlowers 的说法也很有道理,如果挂上的是空的interface,也是nil,但是挂上了指针是有值的,虽然指针是空的。

stevewang
#10 stevewang • 2014-08-31 22:48

楼主不是不知道原因,他是不喜欢这种设计。

se7enday
#11 se7enday • 2014-08-31 23:46
Bluek404
#12 Bluek404 • 2014-08-31 23:51

我觉得这么设计也是有原因的

比如:

var a interface{}
var b interface{} = t

按理说这两个都应该等于nil的

但是如果都等于nil的话,那么a就==b了

但是a和b是明显不等于的

因为a里面存储的是nil,但是b是一个指向nil的指针

然后看楼主的程序:

var t *T的时候interface{}里储存的是指向t的指针,然后判断interface{}是否等于t是看指针指向的值是多少,而判断是否等于nil是判断的interface{}本身是否是nil

而当var t T的时候interface{}里储存的是t里面值的copy就是nil,所以interface{}就等于nil

要改变这个也很简单,和Golang的团队说一下,让他们把所有内容为指针的变量默认输出指针,而不是指针指向的内容,然后再加一个方法专门从指针提取指向的内容

不过这样写程序就太麻烦了,所以这点抽象还是有必要的

以上仅属个人见解,最好还是去官方邮件列表提问

snake117
#13 snake117 • 2014-09-01 00:04

@dayn9 @Bluek404

import "fmt"

func main() {
    var a *int = nil
    var x interface{} = nil
    var y interface{} = a
    if x == y {
        fmt.Println("x==y")
    }
    if x == nil {
        fmt.Println("x==nil")
    }
    if y == nil {
        fmt.Println("y==nil")
    }
}

这个程序只返回 "x==nil"。

所以,你可以明确的知道,nil接口和装入nil的接口完全不是一回事。

应该说,如果是一回事才有问题。比如某个方法,出于一致性需要返回一个接口,那么返回nil接口和返回装入nil的接口,其意义是完全不同的。

Bluek404
#14 Bluek404 • 2014-09-01 12:50

@snake117 难道不是因为interface{} 里装的是一个nil指针?

snake117
#15 snake117 • 2014-09-01 15:24

@Bluek404 是啊。

我的意思是,不把nil接口和装入nil的接口等同有其意义,从各种方面来说,都不应该让它们等同。

Bluek404
#16 Bluek404 • 2014-09-01 15:39

@snake117 QQ群上一位朋友给出的方法

fmt.Println(reflect.TypeOf(t), reflect.TypeOf(i), reflect.TypeOf(a), reflect.TypeOf(b))

输出:

<nil> *main.T <nil> *main.T
Bluek404
#17 Bluek404 • 2014-09-01 15:40

啊对了补充一下16楼 http://golanghome.com/post/324

David
#18 David • 2014-09-01 22:35

主要的歧义在于nil,这样写是三个true的 (http://play.golang.org/p/2sJNi6Ad0n)

package main

import "fmt"

type T struct{}

func main() {
    var t *T
    var i interface{} = t
    fmt.Println(t == (*T)(nil), i == t, i == (*T)(nil))
}

但是编译器看到i == nil的时候,只能够把无类型的nil看成是interface{}nil,也就是==检查的是interface{}里的类型是空(此时应该不检查 value 的,因为interface{}如果没有类型,取值无意义)

没有想到更好的设计能解决这个问题,而且只为了满足==的传递性没有啥实用性。

sisyphsu
#19 sisyphsu • 2018-01-01 17:00

特意注册账号来发表一下意见。

最近我深深地踩了这个坑,并且在Google上见到不计其数的人,都在询问interface{}为什么不等于nil。在golang的Github ISSUE里面也见到许多类似的问题。

楼上拿“浮点数比较”与“接口类型比较”来反驳讽刺楼主,明显是错误的,因为问题的关键是“判空”,而不是判断类型,也不是判断误差。

Golang是一个非常优秀的语言,鄙人以前长期从事java、c++开发,过渡过来非常流畅。

但是就事论事,interface{}判nil的设计,实在是太愚蠢了。与数十年来、各种语言通用的“判空”设计,完完全全地冲突了。我相信大部分人,使用Golang进行开发时候,早晚会在这个坑上面,重重地摔一跤。

借路描述一下这个坑:
java里面你可能这样判断null
interfaceA != null
c++里面你可能这样判断null
baseClassPTR != nullptr
但是在Golang里面你这样做就有可能panic、crash
interfaceA != nil

你应该这样判断interface为nil:
interfaceA != nil && !reflect.ValueOf(interfaceA).IsNil()

假以时日,如果Golang在国内广泛使用之后,我敢断定“interface判nil”必然成为面试必考题。

topuk
#20 topuk • 2018-04-09 11:05

也是特意注册账号来回答,不过我只想指出第一个回复 @stevewang 的错误之处:

#include <stdio.h>
int main() {
    float f1 = 0.00001;
    float f2 = 0.00002;
    float f3 = f1 + f2;
    printf("%f, %d", f3, f3 == 0.00003);
    return 0;
}

f3 == 0.00003当然不可能正确,在C/C++中,浮点数的字面值类型默认是double类型,你用一个float类型去比较,会相等?
这不是违背常识的语义特征,而是你压根没弄明白。

topuk
#21 topuk • 2018-04-09 11:26

还有下面的代码

#include <stdio.h>
struct A {
    int a;
};
struct B : public A {
    int b;
};
struct C : public A {
    int c;
};
int main() {
    A* a = 0;
    B* b = 0;
    C* c = 0;
    printf("%d, %d, %d", a == b, a == c, b == c);
    return 0;
}

我真的不是不友善,这代码什么玩意?C++代码引入C的IO头文件(我知道这是兼容的),用printf去输出,然后a==b,这比较的是指针,我想问,你知道你自己这段代码是在表达什么吗?你能弄清楚对象及其指针,以及构造和运算符重载是什么吗?

flym03
#22 flym03 • 2018-04-09 15:33

一看好多c基础都不扎实

需要 登录 后方可回复, 如果你还没有账号你可以 注册 一个帐号。