web-dev-qa-db-ja.com

[] stringを[] interface {}に変換できません

私はいくつかのコードを書いていますが、引数をキャッチして_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 {}
_

これを回避する「移動」方法はありますか?

79
cruizh

これはバグではありません。 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) time 。型をinterface{}に変換するには、O(1)時間が必要です。これらのforループが不要になった場合、コンパイラはそれを挿入する必要があります。

95

この場合、型変換は不要です。 flag.Args()値を_fmt.Println_に渡すだけです。


質問:

[] stringを[] interface {}に変換できません

私はいくつかのコードを書いていますが、引数をキャッチしてfmt.Printlnに渡すために必要です(デフォルトの動作、スペースで区切られた後に改行が続く引数が必要です)。

コード例は次のとおりです。

_package main

import (
    "fmt"
    "flag"
)

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

パッケージフラグ

_import "flag"_

関数引数

func Args() []string

Argsは、フラグ以外のコマンドライン引数を返します。


パッケージ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]
$ 
_
10
peterSO

印刷したい文字列のスライスだけの場合、変換を回避し、結合することでまったく同じ出力を取得できます。

package main

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

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

リフレクションを使用することは可能だと思いますが、それが良い解決策かどうかわかりません

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}
0
Juan Torres

Goでは、関数は、関数定義のパラメーターリストで指定された型の引数のみを受け入れることができます。可変引数パラメーター言語機能はそれを少し複雑にしますが、明確に定義された規則に従います。

_fmt.Println_の関数シグネチャは次のとおりです。

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

言語仕様

関数シグネチャの最後の着信パラメーターには、...という接頭辞が付いた型があります。そのようなパラメーターを持つ関数は、可変引数と呼ばれ、そのパラメーターのゼロ個以上の引数で呼び出すことができます。

これは、Printlnに_interface{}_型の引数のリストを渡すことができることを意味します。すべての型は空のインターフェイスを実装するため、任意の型の引数のリストを渡すことができます。これにより、たとえばエラーなしでPrintln(1, "one", true)を呼び出すことができます。言語仕様の "...パラメーターへの引数の受け渡し"セクション を参照してください。

渡される値は、[] T型の新しいスライスであり、連続する要素が実際の引数であり、すべてがTに割り当て可能でなければならない新しい基本配列を持つ配列です。

トラブルを引き起こしている部分は、仕様のすぐ後です。

最後の引数がスライスタイプ[] Tに割り当て可能な場合、引数の後に...が続くと、... Tパラメータの値としてそのまま渡されます。この場合、新しいスライスは作成されません。

flag.Args()は、タイプ_[]string_です。 TPrintlnは_interface{}_であるため、_[]T_は_[]interface{}_です。したがって、問題は、文字列スライス値がインターフェイススライスタイプの変数に割り当て可能かどうかになります。たとえば、割り当てを試みることで、goコードで簡単にテストできます。

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

このような割り当てを試みると、コンパイラは次のエラーメッセージを出力します。

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

そして、それがあなたが_fmt.Println_への引数として文字列スライスの後に省略記号を使用できない理由です。これはバグではなく、意図したとおりに機能しています。

Printlnflag.Args()を印刷する方法はまだたくさんあります。

_fmt.Println(flag.Args())
_

fmtパッケージドキュメント ごとに_[elem0 elem1 ...]_として出力されます)

または

_fmt.Println(strings.Join(flag.Args(), ` `)
_

(これは、それぞれが単一のスペースで区切られた文字列スライス要素を出力します) stringsパッケージのJoin関数 を使用して、たとえば文字列セパレータを使用します。

0
jrefior