Piszę jakiś kod i potrzebuję go do przechwytywania argumentów i przekazywania ich fmt.Println
(chcę jego domyślnego zachowania, zapisywania argumentów oddzielonych spacjami i zakończonych znakiem nowej linii). Jednak trwa, []interface {}
ale flag.Args()
zwraca []string
.
Oto przykład kodu:
package main
import (
"fmt"
"flag"
)
func main() {
flag.Parse()
fmt.Println(flag.Args()...)
}
To zwraca następujący błąd:
./example.go:10: cannot use args (type []string) as type []interface {} in function argument
Czy to błąd? Nie powinienem fmt.Println
brać żadnej tablicy? Swoją drogą, też próbowałem to zrobić:
var args = []interface{}(flag.Args())
ale pojawia się następujący błąd:
cannot convert flag.Args() (type []string) to type []interface {}
Czy istnieje sposób obejścia tego problemu?
type-conversion
go
cruizh
źródło
źródło
go run test.go some test flags
) i wydawało się, że działa przy zmianieflags.Args()...
na justflag.Args()
(na wyjściu znajduje się znak[some test flags]
nowej linii; również wydawało się, że działa z rejestrowaniem rzeczywistych flag). Nie będę udawać, że rozumiem, dlaczego, a odpowiedź Stephena i tak jest o wiele bardziej pouczająca :)Odpowiedzi:
To nie jest błąd.
fmt.Println()
wymaga[]interface{}
typu. Oznacza to, że musi to być wycinekinterface{}
wartości, a nie „dowolny wycinek”. Aby przekonwertować wycinek, musisz zapętlić i skopiować każdy element.old := flag.Args() new := make([]interface{}, len(old)) for i, v := range old { new[i] = v } fmt.Println(new...)
Powodem, dla którego nie możesz użyć żadnego wycinka, jest to, że konwersja między a
[]string
i a[]interface{}
wymaga zmiany układu pamięci i odbywa się w czasie O (n). Konwersja typu na typinterface{}
wymaga czasu O (1). Gdyby ta pętla for była niepotrzebna, kompilator nadal musiałby ją wstawić.źródło
[]string
, oczekuje ainterface{}
. Aninterface{}
ma inny układ pamięci niż a,string
więc problemem jest fakt, że każdy element musi zostać przekonwertowany.Println
funkcja modyfikuje wycinek i ustawia niektóre elementy (nie robi, ale przypuśćmy, że tak). Następnie może włożyćinterface{}
do plastra dowolny , który powinien mieć tylkostring
s. To, czego naprawdę chcesz, to coś w rodzaju symbolu wieloznacznego Java GenericsSlice<? extends []interface{}>
, ale nie istnieje w Go.new()
,len()
icopy()
. golang.org/ref/spec#Appending_and_copying_slicesJeśli chcesz wydrukować tylko kawałek ciągów, możesz uniknąć konwersji i uzyskać dokładnie ten sam wynik, łącząc:
package main import ( "fmt" "flag" "strings" ) func main() { flag.Parse() s := strings.Join(flag.Args(), " ") fmt.Println(s) }
źródło
W takim przypadku konwersja typu jest niepotrzebna. Po prostu przekaż
flag.Args()
wartość dofmt.Println
.W takim przypadku konwersja typu jest niepotrzebna. Po prostu przekaż
flag.Args()
wartość dofmt.Println
, która używa odbicia, aby zinterpretować wartość jako typ[]string
. Pakietreflect
implementuje odbicie w czasie wykonywania, umożliwiając programowi manipulowanie obiektami o dowolnych typach. Na przykład,args.go
:package main import ( "flag" "fmt" ) func main() { flag.Parse() fmt.Println(flag.Args()) }
Wynik:
$ go build args.go $ ./args arg0 arg1 [arg0 arg1] $
źródło
W Go funkcja może akceptować tylko argumenty typu określonego na liście parametrów w definicji funkcji. Wariadyczna funkcja języka parametrów nieco to komplikuje, ale działa zgodnie z dobrze zdefiniowanymi regułami.
Podpis funkcji dla
fmt.Println
:func Println(a ...interface{}) (n int, err error)
Zgodnie ze specyfikacją językową ,
Oznacza to, że możesz przekazać
Println
listę argumentówinterface{}
typu. Ponieważ wszystkie typy implementują pusty interfejs, możesz przekazać listę argumentów dowolnego typu, dzięki czemu możeszPrintln(1, "one", true)
na przykład wywołać bez błędu. Zobacz sekcję „Przekazywanie argumentów do ... parametrów” specyfikacji języka:Część, która sprawia kłopoty, znajduje się zaraz po tej w specyfikacji:
flag.Args()
jest typem[]string
. PonieważT
wPrintln
tointerface{}
,[]T
to[]interface{}
. Zatem pytanie sprowadza się do tego, czy wartość wycinka łańcucha można przypisać do zmiennej typu wycinka interfejsu. Możesz to łatwo sprawdzić w swoim kodzie go, próbując przypisać, na przykład:s := []string{} var i []interface{} i = s
Jeśli spróbujesz takiego przypisania, kompilator wyświetli następujący komunikat o błędzie:
cannot use s (type []string) as type []interface {} in assignment
Dlatego nie możesz użyć wielokropka po kawałku łańcucha jako argumentu funkcji
fmt.Println
. To nie jest błąd, działa zgodnie z przeznaczeniem.Nadal istnieje wiele sposobów można drukować
flag.Args()
zPrintln
takich jak(który zostanie wyświetlony jako
[elem0 elem1 ...]
, zgodnie z dokumentacją pakietu fmt )lub
fmt.Println(strings.Join(flag.Args(), ` `)
(co spowoduje wyświetlenie elementów wycinka ciągu, każdy oddzielony pojedynczą spacją) , na przykład przy użyciu funkcji Join w pakiecie ciągów z separatorem ciągu.
źródło
Myślę, że przy użyciu refleksji jest to możliwe, ale nie wiem, czy to dobre rozwiązanie
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) }
Wynik:
$ go run .\main.go arg1 arg2 arg1,arg2 hello,world 1,2,3,4,5,6 {John 30},{Not John 20}
źródło
fmt.Println
przyjmuje parametr wariadycznyMożliwe jest drukowanie
flag.Args()
bez konwersji do formatu[]interface{}
func main() { flag.Parse() fmt.Println(flag.Args()) }
źródło
fmt.Println
Podpis nie zmienił się od ponad 6 lat (a to była tylko zmiana pakietuerror
). Nawet gdyby tak było, specyfikacja wyraźnie mówi: „wariadyczny z końcowym parametrem p typu ... T, to wewnątrz f typ p jest równoważny typowi [] T.”, więc nie miałoby to znaczenia.fmt.Println(flags.Args()...)
nadal nie działa (brakuje ci rozszerzenia plasterka),fmt.Println(flags.Args())
zawsze działał.flag.Args()
wyłącznie jako argumentu dofmt.Println
cofnięcia się w tamtym czasie. (Byłoby zaskakujące, gdyby tak nie było w tym czasie)