【问题标题】:Cannot convert []string to []interface {}无法将 [] 字符串转换为 [] 接口 {}
【发布时间】:2012-10-11 00:25:32
【问题描述】:

我正在编写一些代码,我需要它来捕获参数并将它们传递给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 不应该采用 any 数组吗?顺便说一句,我也尝试过这样做:

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

但我收到以下错误:

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

是否有解决此问题的“Go”方式?

【问题讨论】:

  • 我在搞砸一个简单的例子 (go run test.go some test flags),当将 flags.Args()... 更改为 flag.Args() 时它似乎有效(输出为 [some test flags],后跟换行符;似乎也与注册实际标志一起工作)。不会假装理解为什么,斯蒂芬的回答无论如何都会提供更多信息:)

标签: type-conversion go


【解决方案1】:

另一种选择是只迭代切片:

package main
import "flag"

func main() {
   flag.Parse()
   for _, each := range flag.Args() {
      println(each)
   }
}

【讨论】:

    【解决方案2】:

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

    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}
    

    【讨论】:

      【解决方案3】:

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

      fmt.Println 的函数签名是:

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

      根据language specifiction

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

      这意味着您可以将Println 传递给interface{} 类型的参数列表。由于所有类型都实现了空接口,因此您可以传递任何类型的参数列表,这就是您能够调用Println(1, "one", true) 的方式,例如,没有错误。参见语言规范的"Passing arguments to ... parameters" section

      传递的值是 []T 类型的新切片,带有一个新的底层数组,其连续元素是实际参数,所有这些都必须可分配给 T。

      给你带来麻烦的部分就在规范的后面:

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

      flag.Args()[]string 类型。因为Println中的Tinterface{},所以[]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())
      

      (根据fmt package documentation,将输出为[elem0 elem1 ...]

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

      (将输出字符串切片元素,每个元素由一个空格分隔)使用带有字符串分隔符的the Join function in the strings package,例如。

      【讨论】:

        【解决方案4】:

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


        问题:

        无法将 []string 转换为 []interface {}

        我正在编写一些代码,我需要它来捕获参数并通过 他们通过 fmt.Println (我想要它的默认行为,写 参数以空格分隔,后跟换行符)。

        代码示例如下:

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

        Package flag

        import "flag"

        func Args

        func Args() []string

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


        Package fmt

        import "fmt"

        func Println

        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]
        $ 
        

        【讨论】:

        • 如果我想省略括号,是否仍然不需要转换?
        【解决方案5】:

        fmt.Println 采用可变参数

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

        可以打印flag.Args()而不转换为[]interface{}

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

        【讨论】:

        • fmt.Println 的签名已超过 6 年没有改变(这只是 error 的包更改)。即使它有,spec 清楚地表示`带有 ...T 类型的最终参数 p 的可变参数,然后在 f 内 p 的类型等效于类型 []T。` 所以这无关紧要。 fmt.Println(flags.Args()...) 仍然不起作用(您缺少切片扩展),fmt.Println(flags.Args()) 始终有效。
        • 您是如何获得 fmt.Println 的签名 6 年多未更改的信息的?你检查源代码了吗?
        • 对操作的评论表明,仅使用 flag.Args() 作为 fmt.Println 的参数可以追溯到那个时候。 (如果当时没有,那就太奇怪了)
        • 所以?如果存在评论,这意味着我的答案应该被否决吗?
        【解决方案6】:

        如果你只是想打印一段字符串,你可以通过加入来避免转换并获得完全相同的输出:

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

        【讨论】:

          【解决方案7】:

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

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

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

          【讨论】:

          • 对了,我在 golang-nuts 中找到了这个链接:groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
          • 是的,每次迭代需要 O(1) 时间,循环需要 O(n) 时间。这就是我所说的。至于接收[]string 的函数,它需要interface{}interface{}string 具有不同的内存布局,因此每个元素都需要转换是问题所在。
          • @karlrh:不,假设Println 函数修改了切片,并设置了一些元素(它没有,但假设它有)。然后它可以将任何interface{} 放入切片中,切片中应该只有strings。您真正想要的是类似于 Java 泛型通配符 Slice&lt;? extends []interface{}&gt;,但在 Go 中不存在。
          • 追加很神奇。它是内置的并由语言进行特殊处理。其他示例包括new()len()copy()golang.org/ref/spec#Appending_and_copying_slices
          • 今天我了解到“新”不是保留关键字!也不是make!
          猜你喜欢
          • 2015-02-25
          • 2013-02-21
          • 2018-01-15
          • 2012-09-19
          • 2016-02-28
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多