Неможливо перетворити [] рядок в [] інтерфейс {}


96

Я пишу якийсь код, і він мені потрібен, щоб перехопити аргументи та передати їх fmt.Println
(я хочу, щоб поведінка за замовчуванням писала аргументи, відокремлені пробілами та нові рядки). Однак це приймає, []interface {}але flag.Args()повертає a []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брати будь-який масив? До речі, я також намагався зробити це:

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

але я отримую таку помилку:

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

Чи існує спосіб вирішити цю проблему?


1
Я возився з простим прикладом ( go run test.go some test flags), і, здавалося, це спрацювало при зміні flags.Args()...на just flag.Args()(висновок [some test flags], а потім новий рядок; також, здавалося, працював з реєстрацією фактичних прапорів). Не вдаватиме, що розуміє, чому, і відповідь Стівена в будь-якому випадку є набагато інформативнішою :)
RocketDonkey

Відповіді:


115

Це не помилка. fmt.Println()потрібен []interface{}тип. Це означає, що це повинен бути фрагмент interface{}значень, а не "будь-який фрагмент". Для того, щоб перетворити фрагмент, вам потрібно буде прокрутити і скопіювати кожен елемент.

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

Причиною того, що ви не можете використовувати будь-який фрагмент, є те, що для перетворення між a []stringта a []interface{}потрібно змінити макет пам'яті, що відбувається за час O (n). Перетворення типу в an interface{}вимагає часу O (1). Якщо вони зробили це для циклу непотрібним, компілятору все одно доведеться його вставити.


2
До речі, це посилання я знайшов у голанг-горіхах: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh

2
Так, кожна ітерація вимагає часу O (1), а цикл - часу O (n). Це я сказав. Що стосується функції, що отримує a []string, вона очікує a interface{}. interface{}Має іншу компоновку пам'яті від А stringтому той факт , що кожен елемент потреба бути перетворена є проблемою.
Стівен Вайнберг,

1
@karlrh: Ні, припустимо, Printlnфункція модифікує зріз і встановлює деякі елементи (це не так, але припустимо, що робить). Тоді він може помістити будь-який interface{}фрагмент, який повинен мати лише strings. Насправді ви хочете щось на зразок узагальнюючого символу Java Generics Slice<? extends []interface{}>, але такого в Go немає.
newacct

4
Аппенд чарівний. Він вбудований і обробляється спеціально мовою. Інші приклади включають в себе new(), len()і copy(). golang.org/ref/spec#Appending_and_copying_slices
Стівен Вайнберг

1
Сьогодні я дізнався, що "нове" - це не резервне ключове слово! Також не робить!
Шрідхар

11

Якщо це лише фрагмент рядків, який ви хочете надрукувати, ви можете уникнути перетворення і отримати точно такий же результат, приєднавшись:

package main

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

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

11

У цьому випадку перетворення типу непотрібне. Просто передайте flag.Args()значення fmt.Println.


Питання:

Неможливо перетворити [] рядок в [] інтерфейс {}

Я пишу якийсь код, і він мені потрібен, щоб зловити аргументи і передати їх через fmt.Println (я хочу, щоб поведінка за замовчуванням писала аргументи, відокремлені пробілами з наступним рядком).

Ось приклад коду:

package main

import (
    "fmt"
    "flag"
)

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

Пакет прапор

import "flag"

func Args

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

Якби я хотів опустити дужки, чи все одно перетворення було б непотрібним?
круїз

1

У Go функція може приймати лише аргументи тих типів, що вказані у списку параметрів у визначенні функції. Мова варіадичної мови параметрів трохи ускладнює, але вона дотримується чітко визначених правил.

Підпис функції для fmt.Println:

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

Відповідно до мовної специфікації ,

Кінцевий вхідний параметр у підписі функції може мати тип із префіксом .... Функція з таким параметром називається варіадичною і може бути викликана нулем або більше аргументів для цього параметра.

Це означає, що ви можете передати Printlnсписок аргументів interface{}типу. Оскільки всі типи реалізують порожній інтерфейс, ви можете передати список аргументів будь-якого типу, саме таким чином ви можете телефонувати Println(1, "one", true), наприклад, без помилок. Див. Розділ "Передача аргументів у ... параметри" специфікації мови:

передане значення - це новий зріз типу [] T з новим базовим масивом, послідовними елементами якого є фактичні аргументи, які всі мають бути призначені T.

Частина, яка доставляє вам проблеми, відразу після цього описується в специфікації:

Якщо кінцевий аргумент можна призначити типу зрізу [] T, він може бути переданий без змін як значення параметра ... T, якщо за аргументом слідує .... У цьому випадку новий зріз не створюється.

flag.Args()є типом []string. Оскільки Tв Printlnє interface{}, []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())

(що видаватиметься [elem0 elem1 ...]відповідно до документації пакета fmt )

або

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

(який буде виводити елементи фрагмента рядка, кожен розділений єдиним пробілом), використовуючи, наприклад, функцію Join у пакеті рядків із розділювачем рядків.


0

Я думаю, що це можливо за допомогою рефлексії, але я не знаю, чи це хороше рішення

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}

-1

fmt.Printlnприймає варіадичний параметр

func Println (... інтерфейс {}) (n int, помилка помилки)

Його можна друкувати flag.Args()без перетворення в[]interface{}

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

2
fmt.PrintlnПідпис не змінювався протягом 6 років (і це була просто зміна пакету error). Навіть якби це було, специфікація чітко говорить `variadic з кінцевим параметром p типу ... T, тоді в f тип p еквівалентний типу [] T.`, тому це не мало би значення. fmt.Println(flags.Args()...)як і раніше не працює (вам бракує розширення зрізу), fmt.Println(flags.Args())завжди працював.
Марк

Як ви отримали цю інформацію, що підпис fmt.Println не змінювався протягом 6 років? Ви перевіряли вихідний код?
Шахріар,


Коментар до оперування наводить на думку, що використання flag.Args()виключно як аргумент для того, щоб fmt.Printlnпрацювати в той час. (Було б дивно, якби цього не сталося на той час)
лист бібоп

Тому? Якщо є коментар, це означає, що за мою відповідь слід проголосувати?
Шахріар,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.