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

Fré*_*els 5 type-conversion go slice

我是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)
}
Run Code Online (Sandbox Code Playgroud)

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

从规范来看,它看起来像[]字符串与[]卡的底层类型不同,因此不能进行类型转换.

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

如果是这样的话,为什么会这样呢?假设,在一个非宠物示例程序中,我输入一个字符串片段,有没有办法将它"转换"成一片卡片,或者我是否必须创建一个新结构并将数据复制到其中?(我想避免使用,因为我需要调用的函数将修改切片内容).

icz*_*cza 8

底层类型Card可能与底层类型string(它本身string:) []Card相同,但底层类型与底层类型不同[]string(因此同样适用Hand).

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

但回到根源:如果你需要一片Cards,你为什么要在一开始创建一片strings?您所创建的类型 Card,因为它不是一个string(或至少不只是一个string).如果是这样且您需要[]Card,那么[]Card首先创建并解决所有问题:

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

请注意,您仍然可以Card使用无类型常量string 文字初始化切片,因为它可用于初始化其基础类型为的任何类型string.如果要涉及类型string常量或类型的非常量表达式string,则需要显式转换,如下例所示:

s := "ddd"
value := []Card{"a", "b", "c", Card(s)}
Run Code Online (Sandbox Code Playgroud)

如果你有[]string,你需要手动建立一个[]Card.没有"更容易"的方式.您可以创建辅助toCards()函数,以便在需要的任何地方使用它.

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

一些背景和推理链接:

Go语言规范:转换

为什么[]字符串无法在golang中转换为[] interface {}

无法将[]字符串转换为[] interface {}

那么内存布局意味着在Go中无法将[] T转换为[]界面?

  • 我不确定你的意思是什么。例如,“strings.Split”的输出是“[]string”。我创建了类型“Card”和“Hand”,因为我想要这些类型的“方法”,并且“strings.Split”的输出用作这些类型的输入,这似乎浪费时间/CPU/代码将输入复制到新的数据结构中。为什么 `[]string` 和 `[]Card` 有不同的内存布局?(“[]int32”和“[]byte”的例子很明显,但就我而言,它对我来说并不那么明显) (2认同)

tom*_*tom 5

出于技术上的原因,禁止在元素具有相同基础类型(例如[]string[]Card)的切片之间进行转换。这是一项规范决策,旨在避免偶然的具有相同结构的不相关类型之间的意外转换。

安全的解决方案是复制切片。但是,可以使用不安全的软件包直接进行转换(不进行复制):

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

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

软件包文档中的强制性警告:

unsafe.Pointer允许程序击败类型系统并读写任意内存。使用时应格外小心。

在2011 年的邮件列表中,对转换和基础类型进行了讨论,并在2016年提出了允许在递归等效类型之间进行转换提议,但该提议被拒绝,直到有更令人信服的理由为止。