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


96

我正在编写一些代码,并且我需要它来捕获参数并将它们传递给fmt.Println
我(我想要它的默认行为,编写由空格分隔并后跟换行符的参数)。但是它需要[]interface {}flag.Args()返回[]string
这是代码示例:

package main

import (
    "fmt"
    "flag"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args()...)
}

这将返回以下错误:

./example.go:10: cannot use args (type []string) as type []interface {} in function argument

这是一个错误吗?不应该fmt.Println接受任何数组吗?顺便说一句,我也尝试过这样做:

var args = []interface{}(flag.Args())

但出现以下错误:

cannot convert flag.Args() (type []string) to type []interface {}

有“解决方案”解决此问题吗?


1
我搞砸了一个简单的示例(go run test.go some test flags),当更改flags.Args()...为just时,它似乎可以工作flag.Args()(输出为[some test flags],后跟换行符;也似乎与注册实际标志一起工作)。不会假装理解为什么,而斯蒂芬的答案
反而会提供

Answers:


115

这不是错误。fmt.Println()需要一个[]interface{}类型。这意味着它必须是interface{}值的切片,而不是“任何切片”。为了转换切片,您将需要循环并复制每个元素。

old := flag.Args()
new := make([]interface{}, len(old))
for i, v := range old {
    new[i] = v
}
fmt.Println(new...)

您无法使用任何分片的原因是a[]string与a之间的转换[]interface{}需要更改内存布局,并且发生时间为O(n)。将类型转换为interface{}需要O(1)时间。如果他们不需要此for循环,则编译器仍将需要插入它。


2
顺便说一下,我在golang-nuts中找到了此链接:groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh 2012年

2
是的,每次迭代都需要O(1)时间,而循环则需要O(n)时间。我就是这么说的 至于接收a的函数[]string,则期望a interface{}。Aninterface{}与a具有不同的内存布局,string因此每个元素都需要转换的事实就成为问题。
史蒂芬·温伯格

1
@karlrh:不,假设Println函数修改了切片,并设置了一些元素(不是,但是假设是)。然后,它可以将任何内容interface{}放入切片中,该切片应仅包含strings。您真正想要的是类似Java Generics通配符的东西Slice<? extends []interface{}>,但是Go中不存在。
newacct 2012年

4
追加是神奇的。它是内置的,并由该语言专门处理。其他例子包括new()len(),和copy()golang.org/ref/spec#Appending_and_copying_slices
Stephen Weinberg

1
今天,我了解到“ new”不是保留关键字!也不是make!
斯里达(Sridhar),2015年

11

如果仅是要打印的字符串片段,则可以通过加入以下命令避免转换并获得完全相同的输出:

package main

import (
    "fmt"
    "flag"
    "strings"
)

func main() {
    flag.Parse()
    s := strings.Join(flag.Args(), " ")
    fmt.Println(s)
}

11

在这种情况下,不需要类型转换。只需将flag.Args()值传递给fmt.Println


题:

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

我正在编写一些代码,我需要它来捕获参数并将其传递给fmt.Println(我希望它的默认行为是,编写由空格分隔并后跟换行符的参数)。

这是代码示例:

package main

import (
    "fmt"
    "flag"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args()...)
}

包装标志

import "flag"

功能Args

func Args() []string

Args 返回非标志命令行参数。


软件包fmt

import "fmt"

功能印刷

func Println(a ...interface{}) (n int, err error)

Println格式使用其操作数的默认格式并写入标准输出。始终在操作数之间添加空格,并添加换行符。它返回已写入的字节数以及遇到的任何写入错误。


在这种情况下,不需要类型转换。只需将flag.Args()值传递给即可fmt.Println,后者使用反射将值解释为类型[]string。包reflect实现了运行时反射,从而允许程序处理任意类型的对象。例如,

args.go

package main

import (
    "flag"
    "fmt"
)

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}

输出:

$ go build args.go
$ ./args arg0 arg1
[arg0 arg1]
$ 

如果我想省略方括号,是否仍不需要进行转换?
cruizh18 '02

1

在Go中,函数只能接受函数定义的参数列表中指定的类型的参数。可变参数语言功能使这一点复杂化了一点,但是它遵循了明确定义的规则。

的功能签名为fmt.Println

func Println(a ...interface{}) (n int, err error)

根据语言规范

函数签名中的最终传入参数可以具有以...为前缀的类型。具有此类参数的函数称为可变参数(variadic),并且可以使用该参数的零个或多个参数来调用该函数。

这意味着您可以传递类型Println的参数列表interface{}。由于所有类型都实现了空接口,因此您可以传递任何类型的参数列表Println(1, "one", true),例如,这是您可以无误调用的方式。请参阅语言规范的“将参数传递给...参数”部分

传递的值是[] T类型的新切片,带有新的基础数组,其后续元素是实际参数,所有参数都必须可分配给T。

说明书中紧随其后的是给您带来麻烦的部分:

如果最后一个参数可分配给切片类型[] T,则如果参数后面跟有...,则可以将其不变地作为... T参数的值传递。在这种情况下,不会创建新的切片。

flag.Args()是type []string。由于TPrintlninterface{}[]T[]interface{}。因此,问题在于字符串切片值是否可分配给接口切片类型的变量。通过尝试分配,您可以轻松地在go代码中对其进行测试,例如:

s := []string{}
var i []interface{}
i = s

如果您尝试进行此类分配,则编译器将输出以下错误消息:

cannot use s (type []string) as type []interface {} in assignment

因此,您不能在字符串切片之后使用省略号作为的参数fmt.Println。这不是错误,而是按预期工作。

您仍然可以flag.Args()使用多种方法进行打印Println,例如

fmt.Println(flag.Args())

[elem0 elem1 ...]根据fmt软件包文档,其输出为)

要么

fmt.Println(strings.Join(flag.Args(), ` `)

(将输出字符串切片元素,每个字符串元素之间用一个空格分隔),例如,使用带有字符串分隔符的字符串包中的Join函数


0

我认为可以使用反射,但是我不知道这是否是一个好的解决方案

package main

import (
    "fmt"
    "reflect"
    "strings"
)

type User struct {
    Name string
    Age  byte
}

func main() {
    flag.Parse()
    fmt.Println(String(flag.Args()))
    fmt.Println(String([]string{"hello", "world"}))
    fmt.Println(String([]int{1, 2, 3, 4, 5, 6}))
    u1, u2 := User{Name: "John", Age: 30},
        User{Name: "Not John", Age: 20}
    fmt.Println(String([]User{u1, u2}))
}

func String(v interface{}) string {
    val := reflect.ValueOf(v)
    if val.Kind() == reflect.Array || val.Kind() == reflect.Slice {
        l := val.Len()
        if l == 0 {
            return ""
        }
        if l == 1 {
            return fmt.Sprint(val.Index(0))
        }
        sb := strings.Builder{}
        sb.Grow(l * 4)
        sb.WriteString(fmt.Sprint(val.Index(0)))
        for i := 1; i < l; i++ {
            sb.WriteString(",")
            sb.WriteString(fmt.Sprint(val.Index(i)))
        }
        return sb.String()
    }

    return fmt.Sprintln(v)
}

输出:

$ go run .\main.go arg1 arg2
arg1,arg2
hello,world
1,2,3,4,5,6
{John 30},{Not John 20}

-1

fmt.Println采用可变参数

func Println(a ... interface {})(n int,错误)

flag.Args()无需转换即可打印[]interface{}

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}

2
fmt.Println的签名已超过6年没有变化(而这只是的软件包更改error)。即使有,规范也清楚地表明“可变参数的最终参数p为... T类型,然后在f内p的类型等效于[] T。fmt.Println(flags.Args()...)仍然不起作用(您缺少切片扩展),fmt.Println(flags.Args())始终有效。
马克

您如何获得fmt.Println的签名在过去6年中没有发生变化的信息?您检查源代码了吗?
Shahriar


关于op的评论建议,flag.Args()仅将其用作参数可以fmt.Println追溯到那时。(如果当时不这样做,将令人惊讶)
leaf bebop '18

所以?如果有评论,那意味着我的答案应该被否决?
Shahriar
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.