72

我有一个接口Model,由 struct 实现Person

要获取模型实例,我有以下辅助函数:

func newModel(c string) Model {
    switch c {
    case "person":
        return newPerson()
    }
    return nil
}

func newPerson() *Person {
    return &Person{}
}

上述方法允许我返回一个正确类型的 Person 实例(以后可以使用相同的方法轻松添加新模型)。

当我试图做类似的事情来返回一片模型时,我得到了一个错误。代码:

func newModels(c string) []Model {
    switch c {
    case "person":
        return newPersons()
    }
    return nil
}

func newPersons() *[]Person {
    var models []Person
    return &models
}

Go 抱怨:cannot use newPersons() (type []Person) as type []Model in return argument

我的目标是返回请求的任何模型类型的切片(无论是、、、、w []Person/ e)。我错过了什么,如何正确实施这样的解决方案?[]FutureModel[]Terminator2000

4

6 回答 6

111

这与我刚刚回答的问题非常相似:https ://stackoverflow.com/a/12990540/727643

简短的回答是你是对的。结构切片不等于结构实现的接口切片。

A[]Person和 a[]Model具有不同的内存布局。这是因为它们是切片的类型具有不同的内存布局。AModel是一个接口值,这意味着在内存中它的大小是两个字。一个字用于类型信息,另一个用于数据。APerson是一个结构,其大小取决于它包含的字段。为了从 a 转换[]Person为 a []Model,您需要遍历数组并对每个元素进行类型转换。

由于这种转换是一个 O(n) 操作并且会导致创建一个新切片,因此 Go 拒绝隐式执行此操作。您可以使用以下代码显式执行此操作。

models := make([]Model, len(persons))
for i, v := range persons {
    models[i] = Model(v)
}
return models

正如dskinner 指出的那样,您很可能想要一个指针切片,而不是指向切片的指针。通常不需要指向切片的指针。

*[]Person        // pointer to slice
[]*Person        // slice of pointers
于 2012-10-21T03:59:13.223 回答
8

也许这是您的返回类型的问题*[]Person,实际上应该[]*Person如此引用切片的每个索引都是对 a 的引用Person,而切片[]本身就是对数组的引用。

查看以下示例:

package main

import (
    "fmt"
)

type Model interface {
    Name() string
}

type Person struct {}

func (p *Person) Name() string {
    return "Me"
}

func NewPersons() (models []*Person) {
    return models
}

func main() {
    var p Model
    p = new(Person)
    fmt.Println(p.Name())

    arr := NewPersons()
    arr = append(arr, new(Person))
    fmt.Println(arr[0].Name())
}
于 2012-10-21T04:05:35.037 回答
7

正如斯蒂芬已经回答了这个问题并且您是初学者,我强调提供建议。

使用 go 接口的一种更好的方法不是让构造函数返回接口,就像您可能从其他语言(如 java)中习惯的那样,而是为每个对象独立地设置一个构造函数,因为它们隐式地实现了接口。

代替

newModel(type string) Model { ... }

你应该做

newPerson() *Person { ... }
newPolitician() *Politician { ... }

withPersonPoliticianboth 实现Model. 您仍然可以在接受a 的任何地方使用Person或使用 ,但您也可以实现其他接口。PoliticianModel

使用您的方法,您将受到限制,Model直到您手动转换为另一种接口类型。

假设我有一个Personwhich implements the methodWalk()和一个Modelimplements ShowOff(),以下将无法直接工作:

newModel("person").ShowOff()
newModel("person").Walk() // Does not compile, Model has no method Walk

但是,这将:

newPerson().ShowOff()
newPerson().Walk()
于 2012-10-21T04:12:19.617 回答
3

正如其他人已经回答的那样, []T 是一种独特的类型。我只想补充一点,可以使用一个简单的实用程序对它们进行一般转换。

import "reflect"

// Convert a slice or array of a specific type to array of interface{}
func ToIntf(s interface{}) []interface{} {
    v := reflect.ValueOf(s)
    // There is no need to check, we want to panic if it's not slice or array
    intf := make([]interface{}, v.Len())
    for i := 0; i < v.Len(); i++ {
        intf[i] = v.Index(i).Interface()
    }
    return intf
}

现在,您可以像这样使用它:

ToIntf([]int{1,2,3})
于 2015-01-28T05:00:52.510 回答
2

类型 T 和 []T 是不同的类型,它们的方法也不同,即使满足相同的接口也是如此。IOW,每个满足 Model 的类型都必须自己实现 Model 的所有方法——方法接收者只能是一种特定类型。

于 2012-10-21T03:58:56.363 回答
2

Even if Go's implementation allowed this, it's unfortunately unsound: You can't assign a []Person to a variable of type []Model because a []Model has different capabilities. For example, suppose we also have Animal which implements Model:

var people []Person = ...
var models []Model = people // not allowed in real Go
models[0] = Animal{..} // ???
var person Person = people[0] // !!!

If we allow line 2, then line 3 should also work because models can perfectly well store an Animal. And line 4 should still work because people stores Persons. But then we end up with a variable of type Person holding an Animal!

Java actually allows the equivalent of line 2, and it's widely considered a mistake. (The error is caught at run time; line 3 would throw an ArrayStoreException.)

于 2019-03-26T22:35:28.603 回答