如何确定切片 interface{} 的元素类型?

huangapple go评论77阅读模式
英文:

How to determine the element type of slice interface{}?

问题

我有以下代码来将切片的长度加倍。

func doubleSlice(s []int) []int  {
  t := make([]int, len(s), (cap(s) + 1) * 2 )
  for i := range s {
    t[i] = s[i]
  }
  return t
}

我想要将这个函数改成可以加倍任何类型的切片。但是我需要先知道元素的类型。

func showInterfaceItem(s interface{}) interface{} {
  if reflect.TypeOf(s).Kind() != reflect.Slice {
    fmt.Println("The interface is not a slice.")
    return
  }

  var t interface{}
  newLen := reflect.ValueOf(s).Len()
  newCap := (cap(reflect.ValueOf(s).Cap()) + 1) * 2
  t = make([]reflect.TypeOf(s), newLen, newCap)

  return t
}

reflect.TypeOf(s) 返回的是 interface{} 的类型,而不是切片元素的类型。我该如何获取切片接口的元素类型?

英文:

I have the following code to double the slice.

func doubleSlice(s []int) []int  {
  t := make([]int, len(s), (cap(s) + 1) * 2 )
  for i := range s {
    t[i] = s[i]
  }
  return t
}

I want to make the func to double any type of slice. And I need to know the element type first.

func showInterfaceItem(s interface{}) interface{} {
  if reflect.TypeOf(s).Kind() != reflect.Slice {
    fmt.Println("The interface is not a slice.")
    return
  }

  var t interface{}
  newLen := reflect.ValueOf(s).Len()
  newCap := (cap(reflect.ValueOf(s).Cap()) + 1) * 2
  t = make([]reflect.TypeOf(s), newLen, newCap)

  return t
}

The reflect.TypeOf(s) return the type of interface{}, not the type of element. How can I get the element type of slice interface?

答案1

得分: 13

你可以使用reflect.TypeOf(s).Elem()来获取切片元素的类型。

package main

import (
	"fmt"
	"reflect"
)

func doubleSlice(s interface{}) interface{} {
	if reflect.TypeOf(s).Kind() != reflect.Slice {
		fmt.Println("The interface is not a slice.")
		return nil
	}

	v := reflect.ValueOf(s)
	newLen := v.Len()
	newCap := (v.Cap() + 1) * 2
	typ := reflect.TypeOf(s).Elem()

	t := reflect.MakeSlice(reflect.SliceOf(typ), newLen, newCap)
	reflect.Copy(t, v)
	return t.Interface()
}

func main() {
	xs := doubleSlice([]string{"foo", "bar"}).([]string)
	fmt.Println("data =", xs, "len =", len(xs), "cap =", cap(xs))

	ys := doubleSlice([]int{3, 1, 4}).([]int)
	fmt.Println("data =", ys, "len =", len(ys), "cap =", cap(ys))
}

输出结果将会是:

data = [foo bar] len = 2 cap = 6
data = [3 1 4] len = 3 cap = 8

Go Playground 上可以进行测试。

英文:

You can use reflect.TypeOf(s).Elem()
to get the type of element of slice.

package main

import (
	"fmt"
	"reflect"
)

func doubleSlice(s interface{}) interface{} {
	if reflect.TypeOf(s).Kind() != reflect.Slice {
		fmt.Println("The interface is not a slice.")
		return nil
	}

	v := reflect.ValueOf(s)
	newLen := v.Len()
	newCap := (v.Cap() + 1) * 2
	typ := reflect.TypeOf(s).Elem()
	
	t := reflect.MakeSlice(reflect.SliceOf(typ), newLen, newCap)
	reflect.Copy(t, v)
	return t.Interface()
}

func main() {
	xs := doubleSlice([]string{"foo", "bar"}).([]string)
	fmt.Println("data =", xs, "len =", len(xs), "cap =", cap(xs))

	ys := doubleSlice([]int{3, 1, 4}).([]int)
	fmt.Println("data =", ys, "len =", len(ys), "cap =", cap(ys))
}

The output will be:

data = [foo bar] len = 2 cap = 6
data = [3 1 4] len = 3 cap = 8

Check it in: Go Playground

答案2

得分: 0

这在Go语言中是可行的,并且花了我一整天的时间来发现这个模式。

首先,我们想要获取一个切片的指针,以使gorm库满意,其类型为"*[]Obj"。在Go语言中,我们可以创建一个包装函数来实现:

func makeWrapper(cap uint) interface{} {
    arr := make([]Sth, 0, cap)
    return &arr
}

注意,我们不能直接引用创建的值,因为它可能是需要有一个堆栈空间来存储的内部数据。

// 不起作用的示例
func makeWrapper(cap uint) interface{} {
    return &(make([]Sth, 0, cap))
}

正如之前的答案所述,reflect.MakeSlice(reflect.SliceOf(typ), 0, capacity).Interface() 返回的是interface{[]Sth}。(这里的typ是指reflect.TypeOf(Sth{}),相当于typ == reflect.TypeOf(v)

因此,我们需要创建一个返回类型为*[]Sth的对象,其中包含一个容量为capacity的切片[]Sth。在理解了目标之后,我们可以编写以下代码:

package main

import (
    "reflect"
)

type Sth struct {
    a, b string
}

func main() {
    af := createSlice(Sth{})
    arr := makeWrapper(10).(*[]Sth)
    println(reflect.TypeOf(arr).String())

    // 等效于 makeWrapper,但我们通过反射来实现
    arr = af(10).(*[]Sth)
    println(reflect.TypeOf(arr).String())
}

func makeWrapper(cap uint) interface{} {
    arr := make([]Sth, 0, cap)
    return &arr
}

func createSlice(v interface{}) func(int) interface{} {
    var typ reflect.Type
    if reflect.ValueOf(v).Kind() == reflect.Ptr {
        typ = reflect.ValueOf(v).Elem().Type()
    } else if reflect.ValueOf(v).Kind() == reflect.Struct {
        typ = reflect.TypeOf(v)
    } else {
        panic("only support instance of struct or pointer of that instance")
    }

    return func(capacity int) interface{} {
        // 创建保存切片的外部对象
        outerObj := reflect.New(reflect.SliceOf(typ))
        // 创建切片并保存到返回值中
        outerObj.Elem().Set(reflect.MakeSlice(reflect.SliceOf(typ), 0, capacity))
        // 获取外部对象的接口
        return outerObj.Interface()
    }
}
英文:

This is doable in golang and takes me whole day to discover the pattern.

Firstly, we want to get a pointer of slice to make gorm happy, which is has type "*[]Obj". To achieve that in golang, we can create a make wrapper like so:

func makeWrapper(cap uint) interface{} {
	arr:= make([]Sth, 0, cap)
	return &arr
}

Notice that, we can't directly reference the maked value, which might be the book keeping data need to have a stack space to store.

//Not working example
func makeWrapper(cap uint) interface{} {
	return &(make([]Sth, 0, cap))
}

And as the answer before, the reflect.MakeSlice(reflect.SliceOf(typ), 0, capacity).Interface() returns interface{[]Sth}. (the typ here is refer to reflect.TypeOf(Sth{}), which equiv to typ == reflect.TypeOf(v))

Thus we need to create a return object of *[]Sth and the value inside is a slice []Sth with capacity. After understanding the objective, we can have this code:

package main

import (
	"reflect"
)

type Sth struct {
	a, b string
}
func main() {
	af:= createSlice(Sth{})
	arr := makeWrapper(10).(*[]Sth)
	println(reflect.TypeOf(arr).String())

    // equiv to makeWrapper, but we do it via reflection
	arr = af(10).(*[]Sth)
	println(reflect.TypeOf(arr).String())
}

func makeWrapper(cap uint) interface{} {
	arr:= make([]Sth, 0, cap)
	return &arr
}

func createSlice(v interface{}) func(int) interface{} {
	var typ reflect.Type
	if reflect.ValueOf(v).Kind() == reflect.Ptr {
		typ = reflect.ValueOf(v).Elem().Type()
	} else if  reflect.ValueOf(v).Kind() == reflect.Struct {
		typ = reflect.TypeOf(v)
	} else {
		panic("only support instance of struct or pointer of that instance")
	}

	return func(capacity int) interface{}{
        // create the outer object saves our slice
		outerObj:=reflect.New(reflect.SliceOf(typ))
        // create the slice and save it to return
		outerObj.Elem().Set(reflect.MakeSlice(reflect.SliceOf(typ), 0, capacity))
        // retrive the interface of outer object
		return outerObj.Interface()
	}
}

huangapple
  • 本文由 发表于 2017年2月10日 12:02:57
  • 转载请务必保留本文链接:https://go.coder-hub.com/42151307.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定