Go是面向对象编程语言吗?
是又不是,尽管Go有类型和方法并且允许面先对象的编程风格,但它没有类型层次结构。Go中的“接口”概念提供了一种不同的方法,我们相信它更容易使用,在某种程度上更通用。还有也在其他类型嵌入类型提供类似但并不完全相同于子类的方式。而且,Go的方法比C++或者Java更加通用:它们可以被如何数据类型定义,甚至是内置类型比如plain,“未拆箱”的整型。它们不受结构(类)限制。
因此,缺乏类型层次结构使得Go中的“对象”感觉比C ++或Java等语言更轻量。
怎样动态调度方法?
动态调度方法的唯一方法是通过接口。 结构体或任何其他具体类型的方法都是静态解析的。
为什么没有类型继承?
面向对象编程,至少在最知名的语言中,涉及类型之间的关系描述过多,而这些关系通常可以自动派生。Go采用了不同的方法。
在go中类型自动满足任何实现指定方法子集接口而不是要求程序员提前声明两种类型的关系。除了减少语句标签,这种方法确实是有好处的。类型可以一次满足许多接口,而没有传统多重继承的复杂性。 接口可以非常轻量级的-具有一个或甚至零个方法的接口可以表达一个有用的概念。如果有新的想法或要进行测试,则可以在过程之后添加接口,而无需注释原始类型。因为类型和接口之间没有显式的关系,没有要管理或描述的类型层次结构。
可以使用这些想法来构造类似于类型安全的Unix管道的东西。 例如,查看fmt.Fprintf如何可以对任何输出(不仅是文件)的格式化打印,如何将bufio包与文件I / O完全分开,或者图像包如何生成压缩的图像文件。 所有这些想法都源自代表一个方法(Write)的接口(io.Writer)。这还只是冰山一角。 Go的接口对程序的构建产生了深远的影响。
这需要一些时间来适应,这种隐式的类型依赖样式是Go最具生产力的内容之一。
为什么len是函数而不是方法?
我们讨论了这个问题还是决定作为函数来实现len和friends在实践中是更好,并且没有使相关基本类型的接口(从Go类型意义上)的问题变得复杂。
什么Go不支持方法和运算符的重载?
如果不需要进行类型匹配,那么方法调度也会得到简化。使用其他语言的经验告诉我们,拥有各种具有相同名称但不同签名的方法有时是有用的,但在实践中也可能会混淆和不坚固。 在Go的类型系统中,仅按名称进行匹配并要求类型一致是一个简化的主要决策。 关于操作符重载,这看起来更像是一种方便,而不是绝对的要求。 同样,没有它,事情会更简单。
为什么Go没有 "implements"声明?
Go类型通过实现接口的所有方法来满足该接口,仅此而已。 此属性使得接口被定义和使用而无需修改现有代码。 它支持结构类型化的一种,可以使得关键点分散并提高代码重用性,并随着代码的开发更加容易构建模式。 接口是感觉Go灵活,轻便的主要原因之一。
有关更多详细信息,请参考question on type inheritance。
如何保证我的类型满足接口要求?
您可以通过尝试使用T的零值或指向T的指针进行赋值要求编译器来检查类型T是否实现了接口:
type T struct{}
var _ I = T{} // Verify that T implements I.
var _ I = (*T)(nil) // Verify that *T implements I.
如果T(或相应的*T)没有实现I,会在编译时捕获这个错误。
如果你希望接口的用户显式声明他们实现了该接口,则可以在接口的方法集中添加一个具有描述性名称的方法。 例如:
type Fooer interface {
Foo()
ImplementsFooer()
}
然后,一个类型必须实现ImplementsFooer方法成为Fooer。go doc清楚地记录过。
为什么T不满足Equal接口?
考虑这样一个简单的接口来表示一个可以与自己另一个值进行比较的对象:
type Equaler interface {
Equal(Equaler) bool
}
和这样一个类型T:
type T int
func (t T) Equal(u T) bool { return t == u } // does not satisfy Equaler
与某些动态类型系统中的类似情况不同,T没有实现Equaler。 T.Equal的参数类型是T,而不是字面意义上要求的Equaler类型。 在Go中,类型系统不会转换Equal中的参数的,这是程序员的责任,如T2类型所示,它确实实现了Equaler:
type T2 int
func (t T2) Equal(u Equaler) bool { return t == u.(T2) } // satisfies Equaler
尽管即使这与其他类型系统都不一样,因为在Go语言中,任何满足Equaler的类型都可以作为T2.Equal的参数传递,并且在运行时我们必须检查参数是否为T2类型。 一些语言安排在编译时就做出保证。 另一个相关的例子是:
type Opener interface {
Open() Reader
}
func (t T3) Open() *os.File
在这种情况下,Go的类型系统确实为程序员做的较少,子类型的缺乏使得关于接口满足的规则非常容易表述:只要判断函数的名称和签名是否与接口的名称和签名完全相同? Go的规则也易于有效地去实现。 我们认为这些好处弥补了自动类型升级的不足。 如果有一天可以采用某种动态类型形式,我们希望将有一种表达这些示例思想的方法,并且可以对它们进行静态检查。
我可以将[] T转换为[]interface{}吗?
不可以直接。 语言规范不允许这样做,因为这两种类型在内存中的表示形式不同。 有必要将元素分别复制到目标切片。 此示例将int的slice转换为interface {}的类型slice:
t := []int{1, 2, 3, 4}
s := make([]interface{}, len(t))
for i, v := range t {
s[i] = v
}
如果T1和T2有同样的底层类型,我可以将[]T1转换[]T2类型吗?
代码示例的最后一行编译不通过。
type T1 int
type T2 int
var t1 T1
var x = T2(t1) // OK
var st1 []T1
var sx = ([]T2)(st1) // NOT OK
在Go中,类型跟方法紧密关联的,因为每个命名类型都有一个(可能为空)方法集。 一般规则是,您可以更改要转换的类型的名称(并这样有可能改变其方法集),但不能更改复合类型的元素的名称(和方法集)。 Go要求显示类型转换。
为什么nil error值不等于nil?
在底层,接口保存两个元素,类型T和值V。V代表具体值,例如int,struct或指针,绝不是接口本身,类型是T。例如,如果我们将int值3存储在一个interface中,则得到的接口值示意图为(T = int,V = 3)。 值V也称为接口的动态值,因为给定的接口变量在程序执行期间可能拥有不同的值V(对应的类型T)。
仅当V和T都未设置(T = nil,V没有赋值)时接口值才为nil。特别是,一个nil接口将始终为nil类型。
如果存储一个int类型的nil指针在接口值中,无论指针的值是什么,内部类型都将是int型:(T = * int,V = nil) 因此,即使当指针值内部V值为nil时,该接口值也将为非nil。
这种情况可能令人困惑的,当一个nil值被存储在一个接口值中,比如一个错误返回值时,就会出现这种情况:
func returnsError() error {
var p *MyError = nil
if bad() {
p = ErrBad
}
return p // Will always return a non-nil error.
}
如果执行成功函数将返回nil p,因此返回值是一个error接口值包含有(T = * MyError,V = nil)。 这意味着,如果调用者将返回的错误与nil进行比较,即使没有什么不好的事情发生,它也总是看起来像有错误。 要将正确的nil错误返回给调用者,该函数必须返回一个显式的nil:
func returnsError() error {
if bad() {
return ErrBad
}
return nil
}
对于总是返回错误的函数,最好总是在其签名中使用error 类型(如上所示),而不是使用诸如* MyError之类的具体类型,以帮助确保正确创建错误。 例如,os.Open返回error ,即使,如果不是nil,也始终是* os.PathError具体类型。 每当使用接口时,都可能发生与这些描述的情况类似。 请记住,如果接口中已存储任何具体值,则该接口将不会为nil。 有关更多信息 The Laws of Reflection.。
为什么Go没有变体类型?
变体类型,也称为代数类型,提供一种方法来指定一个值可以接受一组其他类型中的一个,但只能是那些类型。 在系统编程中的一个常见示例是比如说指定一个错误为网络错误,安全错误或应用程序错误,并允许调用者通过检查错误的类型来区分问题的根源。 另一个示例是语法树,其中每个节点可以是不同的类型:声明,语句,赋值等等。
我们曾考虑将变体类型添加到Go中,但是经过讨论后,我们决定将它们排除在外,因为它们与接口的混淆方式相互重叠。 如果变体类型的元素本身是接口,将会发生什么?
同样,语言已经涵盖了某些变体类型所针对的内容。 使用一个接口值来保存错误,并使用一个类型切换来区分情况是很容易表达错误示例的。 语法树示例也是可行的,尽管不那么优雅。
为什么Go没有协变结果类型?
因为Value实现了空接口。 在Go中,方法类型必须完全匹配,因此Value没有实现Copyable。 Go将类型的作用(方法)与类型的实现分开。 如果两个方法返回不同的类型,则它们不会做相同的事情。 想要协变结果类型的程序员通常试图通过接口表达类型层次。 在Go中,接口与实现之间的清晰区分是很自然的。