Tidak dapat mengubah [] string menjadi [] antarmuka {}

96

Saya sedang menulis beberapa kode, dan saya membutuhkannya untuk menangkap argumen dan meneruskannya fmt.Println
(saya ingin perilaku default-nya, untuk menulis argumen yang dipisahkan oleh spasi dan diikuti oleh baris baru). Namun yang dibutuhkan []interface {}tetapi flag.Args()mengembalikan a []string.
Berikut contoh kodenya:

package main

import (
    "fmt"
    "flag"
)

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

Ini mengembalikan kesalahan berikut:

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

Apakah ini bug? Tidak harus fmt.Printlnmengambil array apapun ? Ngomong-ngomong, saya juga mencoba melakukan ini:

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

tapi saya mendapatkan error berikut:

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

Apakah ada cara "Mulai" untuk mengatasinya?

cruizh
sumber
1
Saya mengotak-atik contoh sederhana ( go run test.go some test flags), dan tampaknya berfungsi ketika mengubah flags.Args()...menjadi hanya flag.Args()(keluaran adalah [some test flags], diikuti oleh baris baru; juga tampaknya bekerja dengan mendaftarkan bendera yang sebenarnya). Tidak akan berpura-pura mengerti mengapa, dan jawaban Stephen jauh lebih informatif :)
RocketDonkey

Jawaban:

116

Ini bukan bug. fmt.Println()membutuhkan []interface{}tipe. Artinya, itu harus berupa potongan interface{}nilai dan bukan "potongan apa pun". Untuk mengonversi potongan, Anda perlu mengulang dan menyalin setiap elemen.

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

Alasan Anda tidak dapat menggunakan potongan apa pun adalah karena konversi antara a []stringdan a []interface{}memerlukan tata letak memori untuk diubah dan terjadi dalam waktu O (n). Mengubah tipe menjadi an interface{}membutuhkan waktu O (1). Jika mereka membuat ini untuk loop tidak diperlukan, kompilator masih perlu memasukkannya.

Stephen Weinberg
sumber
2
Ngomong-ngomong, saya menemukan tautan ini di golang-nut: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh
2
Ya, setiap iterasi membutuhkan waktu O (1) dan loop membutuhkan waktu O (n). Itulah yang saya katakan. Adapun fungsi yang menerima a []string, mengharapkan a interface{}. An interface{}memiliki tata letak memori yang berbeda dari a stringsehingga fakta bahwa setiap elemen perlu diubah adalah masalahnya.
Stephen Weinberg
1
@ Karlrh: Tidak, misalkan Printlnfungsi memodifikasi irisan, dan menyetel beberapa elemen (tidak, tetapi anggaplah demikian). Kemudian ia bisa memasukkan apapun interface{}ke dalam irisan, yang seharusnya hanya memiliki strings. Yang Anda inginkan sebenarnya adalah wildcard Java Generics Slice<? extends []interface{}>, tetapi itu tidak ada di Go.
newacct
4
Menambahkan itu ajaib. Itu built-in dan diperlakukan secara khusus oleh bahasa. Contoh lain termasuk new(), len(), dan copy(). golang.org/ref/spec#Appending_and_copying_slices
Stephen Weinberg
1
Hari ini saya belajar 'baru' bukanlah kata kunci cadangan! Juga bukan make!
Sridhar
11

Jika itu hanya sepotong string yang ingin Anda cetak, Anda dapat menghindari konversi dan mendapatkan hasil yang sama persis dengan bergabung:

package main

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

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

Dalam kasus ini, konversi tipe tidak diperlukan. Cukup teruskan flag.Args()nilainya ke fmt.Println.


Pertanyaan:

Tidak dapat mengubah [] string menjadi [] antarmuka {}

Saya sedang menulis beberapa kode, dan saya membutuhkannya untuk menangkap argumen dan meneruskannya melalui fmt.Println (Saya ingin perilaku default-nya, untuk menulis argumen yang dipisahkan oleh spasi dan diikuti oleh baris baru).

Berikut contoh kodenya:

package main

import (
    "fmt"
    "flag"
)

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

Bendera paket

import "flag"

func Args

func Args() []string

Args mengembalikan argumen baris perintah non-bendera.


Paket fmt

import "fmt"

func Println

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

Printlnformat menggunakan format default untuk operannya dan menulis ke output standar. Spasi selalu ditambahkan antara operand dan baris baru ditambahkan. Ini mengembalikan jumlah byte yang ditulis dan setiap kesalahan penulisan yang ditemukan.


Dalam kasus ini, konversi tipe tidak diperlukan. Cukup teruskan flag.Args()nilainya ke fmt.Println, yang menggunakan refleksi untuk menafsirkan nilai sebagai tipe []string. Package reflectmengimplementasikan refleksi run-time, memungkinkan program untuk memanipulasi objek dengan tipe arbitrer. Sebagai contoh,

args.go:

package main

import (
    "flag"
    "fmt"
)

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

Keluaran:

$ go build args.go
$ ./args arg0 arg1
[arg0 arg1]
$ 
peterSO
sumber
Jika saya ingin menghilangkan tanda kurung, apakah konversi masih tidak diperlukan?
cruizh
1

Dalam Go, sebuah fungsi hanya dapat menerima argumen dari tipe yang ditentukan dalam daftar parameter dalam definisi fungsi. Fitur bahasa parameter variadic sedikit memperumitnya, tetapi mengikuti aturan yang ditentukan dengan baik.

Tanda tangan fungsi untuk fmt.Printlnadalah:

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

Sesuai dengan spesifikasi bahasa ,

Parameter masuk terakhir dalam tanda tangan fungsi mungkin memiliki tipe yang diawali dengan .... Fungsi dengan parameter seperti itu disebut variadic dan dapat dipanggil dengan nol atau lebih argumen untuk parameter itu.

Ini berarti Anda dapat memberikan Printlndaftar interface{}tipe argumen . Karena semua tipe mengimplementasikan antarmuka kosong, Anda bisa meneruskan daftar argumen tipe apa pun, begitulah cara Anda memanggil Println(1, "one", true), misalnya, tanpa kesalahan. Lihat bagian "Meneruskan argumen ke ... parameter" pada spesifikasi bahasa:

nilai yang diteruskan adalah potongan baru tipe [] T dengan larik dasar baru yang elemen-elemennya berurutan adalah argumen sebenarnya, yang semuanya harus dapat ditetapkan ke T.

Bagian yang memberi Anda masalah ada tepat setelah itu dalam spesifikasi:

Jika argumen terakhir dapat ditetapkan ke tipe slice [] T, argumen tersebut dapat dikirimkan tanpa perubahan sebagai nilai untuk parameter ... T jika argumen tersebut diikuti oleh .... Dalam kasus ini, tidak ada slice baru yang dibuat.

flag.Args()adalah tipe []string. Sejak Tdi Printlnadalah interface{}, []Tadalah []interface{}. Jadi pertanyaannya adalah apakah nilai potongan string dapat ditetapkan ke variabel jenis potongan antarmuka. Anda dapat dengan mudah mengujinya di kode go Anda dengan mencoba sebuah tugas, misalnya:

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

Jika Anda mencoba melakukan tugas seperti itu, kompilator akan menampilkan pesan kesalahan ini:

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

Dan itulah mengapa Anda tidak dapat menggunakan elipsis setelah potongan string sebagai argumen untuk fmt.Println. Ini bukan bug, ini berfungsi sebagaimana mestinya.

Ada banyak masih dari cara Anda dapat mencetak flag.Args()dengan Println, seperti

fmt.Println(flag.Args())

(yang akan ditampilkan sebagai [elem0 elem1 ...], sesuai dokumentasi paket fmt )

atau

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

(yang akan mengeluarkan elemen potongan string, masing-masing dipisahkan oleh satu spasi) menggunakan fungsi Gabung dalam paket string dengan pemisah string, misalnya.

jrefior
sumber
0

Saya pikir itu mungkin menggunakan refleksi, tetapi saya tidak tahu apakah itu solusi yang baik

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)
}

Keluaran:

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

fmt.Printlnmengambil parameter variadic

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

Mungkin untuk mencetak flag.Args()tanpa mengubahnya menjadi[]interface{}

func main() {
    flag.Parse()
    fmt.Println(flag.Args())
}
Shahriar
sumber
2
fmt.Printlntanda tangan tidak berubah selama lebih dari 6 tahun (dan itu hanya perubahan paket untuk error). Bahkan jika ada, spesifikasi dengan jelas mengatakan `variadic dengan parameter akhir p tipe ... T, maka dalam f tipe p sama dengan tipe [] T.` jadi tidak masalah. fmt.Println(flags.Args()...)masih tidak berfungsi (Anda kehilangan ekspansi slice), fmt.Println(flags.Args())selalu berfungsi.
Marc
Bagaimana Anda mendapatkan info ini bahwa tanda tangan fmt.Println tidak berubah selama lebih dari 6 tahun? Apakah Anda memeriksa kode sumber?
Shahriar
Komentar di operasi menyarankan bahwa menggunakan flag.Args()hanya sebagai argumen untuk fmt.Printlnbekerja di masa itu. (Akan mengherankan jika tidak pada saat itu)
leaf bebop
Begitu? Kalau ada komentar berarti jawaban saya harus down voting?
Shahriar