将一段字符串转换为一段自定义类型

问题描述:

我对Go很新,所以这可能很明显。编译器不允许以下代码: (http://play.golang.org/p/3sTLguUG3l将一段字符串转换为一段自定义类型

package main 

import "fmt" 

type Card string 
type Hand []Card 

func NewHand(cards []Card) Hand { 
    hand := Hand(cards) 
    return hand 
} 

func main() { 
    value := []string{"a", "b", "c"} 
    firstHand := NewHand(value) 
    fmt.Println(firstHand) 
} 

错误是: /tmp/sandbox089372356/main.go:15: cannot use value (type []string) as type []Card in argument to NewHand

从规格,它看起来像[]串是不一样的基本类型为[]卡,所以不能进行类型转换。

确实是这样,或者我错过了什么?

如果是这样的话,为什么这样呢?假设在一个非宠物示例程序中,我输入了一串字符串,是否有任何方法可以将它“投射”到一张卡片中,还是必须创建一个新结构并将数据复制到它中? (我想避免,因为我需要调用的函数将修改切片内容)。

+2

通过'类型卡字符串'你引入了一个实际的新类型,而不是一个类型别名。 'Card'是一种新的类型,它可以拥有一套方法和行为。 – 2015-03-13 12:36:40

基础类型的Card可能是相同的基础类型的string(其本身是:string),但基础类型的[]Card是不一样的基础类型的[]string(因此同样适用于Hand)。

不能的T1片转换成的T2片,这不是他们有什么基础类型的问题,如果T1是不相同的T2,你就不能。为什么?由于不同元素类型的切片可能具有不同的内存布局(内存中的大小不同)。例如,类型[]byte的元素每个占用1个字节。 []int32的元素各占用4个字节。显然,即使所有值都在0..255范围内,也不能将其中一个转换为另一个。

但是回到根源:如果您需要一片Card s,为什么首先要创建一片string?您创建了类型Card因为它是而不是 a string(或至少不只是string)。如果是这样,你需要[]Card,然后在第一时间创建[]Card和你所有的问题消失:

value := []Card{"a", "b", "c"} 
firstHand := NewHand(value) 
fmt.Println(firstHand) 

请注意,您仍然可以与非类型化不断string文字因为初始化的Card片它可以用来初始化任何类型的基础类型为string。如果你想涉及类型string常量或string类型的非常量表达式,你需要显式转换,如下面的例子:

s := "ddd" 
value := []Card{"a", "b", "c", Card(s)} 

如果你有[]string,你需要从它手工创建一个[]Card 。没有“更容易”的方式。您可以创建帮助程序toCards()函数,以便您可以在需要它的任何地方使用它。

func toCards(s []string) []Card { 
    c := make([]Card, len(s)) 
    for i, v := range s { 
     c[i] = Card(v) 
    } 
    return c 
} 

背景和推理的一些链接:

Go Language Specification: Conversions

why []string can not be converted to []interface{} in golang

Cannot convert []string to []interface {}

What about memory layout means that []T cannot be converted to []interface in Go?

+0

正如我提到的,这是一个宠物示例,在我的真实设置中,'[] string'来自我无法控制的外部源。 – 2015-03-13 13:08:19

+0

@FDO是的,我知道,但如果这是一个宠物的例子,在更复杂的例子中,首先创建适当的类型更有理由。看看它,因为在这个例子中,你没有所需的输入切片,只有一些数据可以产生所需的输入切片。 – icza 2015-03-13 13:11:11

+1

我不确定这是什么意思。例如'strings.Split'的输出是一个'[]字符串'。我创建了'Card'和'Hand'类型,因为我需要这些类型的'方法','strings.Split'的输出用作这些类型的输入,看起来像是浪费时间/ cpu /代码将输入复制到新的数据结构中。 为什么'[] string'和'[] Card'具有不同的内存布局? ('[] int32'和'[] byte'的例子很明显,但在我看来,它并不是很明显) – 2015-03-13 13:19:33

从规格来看,[]字符串与[]卡的基本类型不同,因此不能进行类型转换。

没错。您必须通过在每个元素上循环和复制来转换它,在途中将类型从string转换为Card

如果是这样,为什么这样呢?假设在一个非宠物示例程序中,我输入了一串字符串,是否有任何方法可以将它“投射”到一张卡片中,还是必须创建一个新结构并将数据复制到它中? (我想避免,因为我需要调用的函数将修改切片内容)。

因为转换总是明确的,设计者认为转换隐含地涉及副本时,它也应该是明确的。

+1

不过,我不明白为什么需要一个副本(是否有任何方法可以避免它?)。我的理解仍然缺失。 – 2015-03-13 11:51:35

+0

@FDO,这是一个副本,因为一切都是一个值。没有办法做一个变量,并且分配一个值而不需要拷贝某些东西,即使它只是一个指针。 – JimB 2015-03-13 12:55:53

没有TECHNIC其原因是为什么禁止元素具有相同基础类型的切片(如[]string[]Card)之间的转换。这是一个规范决定,有助于避免偶然具有相同结构的无关类型之间的意外转换。

安全的解决方案是复制切片。然而,有可能使用unsafe包直接转换(无复制):

value := []string{"a", "b", "c"} 
// convert &value (type *[]string) to *[]Card via unsafe.Pointer, then deref 
cards := *(*[]Card)(unsafe.Pointer(&value)) 
firstHand := NewHand(cards) 

https://play.golang.org/p/tto57DERjYa

从包文档

强制性警告:

unsafe.Pointer允许程序打败类型系统和读写任意内存。应该非常小心地使用它。

有一个discussion on the mailing list有关转换和基本类型在2011年,并于2016年proposal to allow conversion between recursively equivalent types这是拒绝“直到有一个更令人信服的理由”。

+0

这是一个非常有趣的免费评论,我希望我可以不止一次提高其知名度。 – 2018-02-08 06:56:23