[] Dizesi [] arayüzüne {} dönüştürülemiyor


96

Bazı kodlar yazıyorum ve argümanları yakalayıp geçmesi için ona ihtiyacım var fmt.Println
(varsayılan davranışını, boşluklarla ayrılmış argümanlar yazmasını ve ardından bir satırsonu yazmasını istiyorum). Ancak alır []interface {}ancak bir flag.Args()döndürür []string.
İşte kod örneği:

package main

import (
    "fmt"
    "flag"
)

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

Bu, aşağıdaki hatayı döndürür:

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

Bu bir hata mı? Herhangi bir dizi fmt.Printlnalmamalı mı? Bu arada, şunu da yapmaya çalıştım:

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

ama şu hatayı alıyorum:

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

Bunu çözmenin bir "Git" yolu var mı?


1
Basit bir örnekle uğraşıyordum ( go run test.go some test flags) ve flags.Args()...sadece değişirken işe yarıyor gibiydi flag.Args()(çıktı [some test flags], ardından satırsonu; ayrıca gerçek bayrakların kaydedilmesiyle çalışıyor gibiydi). Nedenini anlıyormuş gibi yapmayacağım ve Stephen'ın cevabı zaten çok daha bilgilendirici :)
RocketDonkey

Yanıtlar:


116

Bu bir hata değil. fmt.Println()bir []interface{}tür gerektirir . Bu, interface{}"herhangi bir dilim" değil , bir değer dilimi olması gerektiği anlamına gelir . Dilimi dönüştürmek için, her bir öğeyi tekrarlamanız ve kopyalamanız gerekir.

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

Herhangi bir dilim kullanamamanızın nedeni, a []stringve a arasındaki dönüşümün []interface{}bellek düzeninin değiştirilmesini gerektirmesi ve O (n) zamanında gerçekleşmesidir. Bir türü bir tipe dönüştürmek interface{}için O (1) süre gerekir. Bunu for döngüsünü gereksiz hale getirirlerse, derleyicinin yine de eklemesi gerekir.


2
Bu arada, bu bağlantıyı golang-nut'ta buldum: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh

2
Evet, her bir yineleme O (1) zamanı gerektirir ve döngü O (n) zamanı gerektirir. Söylediğim buydu. A alan fonksiyona gelince, a []stringbekler interface{}. An interface{}, a'dan farklı bir bellek düzenine sahiptir, bu stringnedenle her öğenin dönüştürülmesi gerektiği gerçeği problemdir.
Stephen Weinberg

1
@karlrh: Hayır, Printlnişlevin dilimi değiştirdiğini ve bazı öğeleri ayarladığını varsayalım ( değiştirmiyor, ama öyle olduğunu varsayalım). Daha sonra interface{}dilime herhangi birini koyabilir , ki bu sadece strings içermelidir . Gerçekten istediğiniz şey Java Generics joker karakterine benziyor Slice<? extends []interface{}>, ancak bu Go'da mevcut değil.
newacct

4
Append büyülüdür. Yerleşiktir ve dil tarafından özel olarak işlenir. Diğer örnekler arasında new(), len()ve copy(). golang.org/ref/spec#Appending_and_copying_slices
Stephen Weinberg

1
Bugün 'yeni'nin yedek bir anahtar kelime olmadığını öğrendim! Yapmak da değil!
Sridhar

11

Yalnızca yazdırmak istediğiniz bir dizi dizeyse, katılarak dönüştürmeden kaçınabilir ve aynı çıktıyı elde edebilirsiniz:

package main

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

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

11

Bu durumda tür dönüşümü gerekli değildir. flag.Args()Değeri 'e aktarmanız yeterlidir fmt.Println.


Soru:

[] Dizesi [] arayüzüne {} dönüştürülemiyor

Bazı kodlar yazıyorum ve argümanları yakalayıp fmt.Println'den geçirmesi için ona ihtiyacım var (varsayılan davranışının, boşluklarla ayrılmış argümanlar yazmasını ve ardından bir satırsonu yazmasını istiyorum).

İşte kod örneği:

package main

import (
    "fmt"
    "flag"
)

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

Paket bayrağı

import "flag"

func Args

func Args() []string

Args bayrak dışı komut satırı bağımsız değişkenlerini döndürür.


Paket fmt

import "fmt"

func Println

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

Printlnişlenenleri için varsayılan biçimleri kullanarak biçimler ve standart çıktıya yazar. Boşluklar her zaman işlenenler arasına eklenir ve bir satırsonu eklenir. Yazılan bayt sayısını ve karşılaşılan herhangi bir yazma hatasını döndürür.


Bu durumda tür dönüşümü gerekli değildir. Değeri tür olarak yorumlamak için yansımayı kullanan flag.Args()değeri basitçe iletin . Paket , bir programın nesneleri rastgele türlerde işlemesine izin vererek çalışma zamanı yansımasını uygular. Örneğin,fmt.Println[]stringreflect

args.go:

package main

import (
    "flag"
    "fmt"
)

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

Çıktı:

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

Parantezleri atlamak isteseydim, dönüştürme yine de gereksiz olur mu?
cruizh

1

Go'da, bir işlev yalnızca işlev tanımındaki parametre listesinde belirtilen türlerin bağımsız değişkenlerini kabul edebilir. Değişken parametre dili özelliği bunu biraz karmaşıklaştırır, ancak iyi tanımlanmış kuralları izler.

İşlev imzası fmt.Printlnşudur:

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

Başına dil specifiction ,

Bir işlev imzasındaki son gelen parametrenin ön ekli bir türü olabilir. Böyle bir parametreye sahip bir işlev, variadic olarak adlandırılır ve bu parametre için sıfır veya daha fazla argümanla çağrılabilir.

Bu Println, interface{}türdeki argümanların bir listesini iletebileceğiniz anlamına gelir . Tüm türler boş arabirimi uyguladığından, herhangi bir türden bağımsız değişkenlerin bir listesini iletebilirsiniz; bu, Println(1, "one", true)örneğin, hatasız bir şekilde arama yapabilirsiniz . Bkz "için ... parametrelerin argümanları iletme" bölümüne dil şartnamenin:

aktarılan değer, [] T türünün yeni bir dilimidir ve ardışık öğeleri gerçek bağımsız değişkenlerdir ve tümü T'ye atanabilir olmalıdır.

Size sorun çıkaran kısım, şartnamede hemen ardından:

Son bağımsız değişken bir dilim türüne [] T atanabilirse, bağımsız değişkenden sonra .... gelirse, bir ... T parametresinin değeri olarak değiştirilmeden geçirilebilir. Bu durumda yeni dilim oluşturulmaz.

flag.Args()tipidir []string. Beri Tiçinde Printlnolduğu interface{}, []Tolduğunu []interface{}. Dolayısıyla soru, bir dizi dilim değerinin arabirim dilim türündeki bir değişkene atanabilir olup olmadığına bağlıdır. Bunu go kodunuzda bir atamayı deneyerek kolayca test edebilirsiniz, örneğin:

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

Böyle bir atamayı denerseniz, derleyici şu hata mesajını verecektir:

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

İşte bu yüzden bir dize diliminden sonraki üç noktayı argüman olarak kullanamazsınız fmt.Println. Bu bir hata değil, amaçlandığı gibi çalışıyor.

Yazdırmak yolları hala çok var flag.Args()olan Printlngibi,

fmt.Println(flag.Args())

( fmt paket dokümantasyonuna[elem0 elem1 ...] göre çıktı olarak verilir )

veya

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

(bu, her biri tek bir boşlukla ayrılmış dize dilim öğelerini çıkarır), örneğin bir dize ayırıcı ile dizeler paketindeki Join işlevini kullanarak .


0

Yansıma kullanarak mümkün olduğunu düşünüyorum, ancak bunun iyi bir çözüm olup olmadığını bilmiyorum

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

Çıktı:

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

-1

fmt.Printlndeğişken parametre alır

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

flag.Args()Dönüştürmeden yazdırmak mümkündür[]interface{}

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

2
fmt.Printlnimzası 6 yıldan fazla bir süredir değişmedi (ve bu sadece bir paket değişikliğiydi error). Olmuş olsa bile, spesifikasyon açıkça `son parametresi p ... T tipinde varyadik diyor, o zaman f içinde p tipi [] T tipine eşdeğerdir, bu yüzden fark etmez. fmt.Println(flags.Args()...)hala çalışmıyor (dilim genişletmesini kaçırıyorsunuz), fmt.Println(flags.Args())her zaman çalışıyor.
Marc

Fmt.Println'in imzasının 6 yıldır değişmediği bu bilgiyi nasıl elde ettiniz? Kaynak kodunu kontrol ettiniz mi?
Shahriar


Operasyonla ilgili yorum, flag.Args()yalnızca fmt.Printlno zamanı geri almak için argüman olarak kullanılmasını önermektedir . (O zaman olmasaydı şaşırtıcı olurdu)
leaf bebop

Yani? Bir yorum varsa, bu, cevabımın olumsuz oylanması gerektiği anlamına mı geliyor?
Shahriar
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.