ไม่สามารถแปลง [] สตริงเป็น [] อินเทอร์เฟซ {}


96

ฉันกำลังเขียนโค้ดบางอย่างและฉันต้องการให้มันจับอาร์กิวเมนต์และส่งผ่าน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ใช้อาร์เรย์ใด ๆ ? อย่างไรก็ตามฉันได้พยายามทำสิ่งนี้ด้วย:

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]ตามด้วยบรรทัดใหม่ดูเหมือนว่าจะทำงานร่วมกับการลงทะเบียนแฟล็กจริงด้วย) จะไม่แสร้งทำเป็นเข้าใจว่าทำไมและคำตอบของ Stephen ก็ให้ข้อมูลมากกว่าอยู่ดี :)
RocketDonkey

คำตอบ:


116

นี่ไม่ใช่บั๊ก 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) การแปลงประเภทเป็นเวลาที่interface{}ต้องการ O (1) หากพวกเขาทำให้สิ่งนี้ไม่จำเป็นสำหรับการวนซ้ำคอมไพเลอร์จะยังคงต้องแทรกมัน


2
ฉันพบลิงก์นี้ใน golang-nuts: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh

2
ใช่การวนซ้ำแต่ละครั้งต้องใช้เวลา O (1) และการวนซ้ำต้องใช้เวลา O (n) นั่นคือสิ่งที่ฉันพูด สำหรับฟังก์ชั่นที่ได้รับ a []stringนั้นคาดว่าจะมี a interface{}. An interface{}มีเค้าโครงหน่วยความจำที่แตกต่างไปจากนี้stringดังนั้นความจริงที่ว่าแต่ละองค์ประกอบจะต้องถูกแปลงเป็นปัญหา
Stephen Weinberg

1
@karlrh: ไม่สมมติว่าPrintlnฟังก์ชันปรับเปลี่ยนชิ้นส่วนและตั้งค่าองค์ประกอบบางอย่าง (ไม่ได้ แต่สมมติว่าเป็น) จากนั้นก็ใส่อะไรก็ได้interface{}ลงไปซึ่งควรมีแค่strings สิ่งที่คุณต้องการจริงๆคือบางอย่างเช่นอักขระตัวแทน Java Generics Slice<? extends []interface{}>แต่ไม่มีอยู่ใน Go
newacct

4
Append มีมนต์ขลัง มีอยู่ในตัวและได้รับการปฏิบัติเป็นพิเศษโดยภาษา ตัวอย่างอื่น ๆ ได้แก่new(), และlen() golang.org/ref/spec#Appending_and_copying_slicescopy()
Stephen Weinberg

1
วันนี้ฉันเรียนรู้ 'ใหม่' ไม่ใช่คีย์เวิร์ดสำรอง! ไม่ทำ!
Sridhar

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

หากฉันต้องการเว้นวงเล็บการแปลงจะยังคงไม่จำเป็นหรือไม่
cruizh

1

ใน Go ฟังก์ชันสามารถยอมรับได้เฉพาะอาร์กิวเมนต์ประเภทที่ระบุในรายการพารามิเตอร์ในข้อกำหนดฟังก์ชัน คุณลักษณะภาษาพารามิเตอร์แบบผันแปรซับซ้อนเล็กน้อย แต่เป็นไปตามกฎที่กำหนดไว้อย่างดี

ลายเซ็นฟังก์ชันสำหรับfmt.Printlnคือ:

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

ต่อspecifiction ภาษา ,

พารามิเตอร์ขาเข้าสุดท้ายในลายเซ็นฟังก์ชันอาจมีชนิดที่นำหน้าด้วย .... ฟังก์ชันที่มีพารามิเตอร์ดังกล่าวเรียกว่าตัวแปรและอาจเรียกใช้โดยมีอาร์กิวเมนต์เป็นศูนย์หรือมากกว่าสำหรับพารามิเตอร์นั้น

ซึ่งหมายความว่าคุณสามารถส่งผ่านPrintlnรายการอาร์กิวเมนต์interface{}ประเภท เนื่องจากทุกประเภทใช้อินเทอร์เฟซว่างคุณจึงสามารถส่งรายการอาร์กิวเมนต์ประเภทใดก็ได้ซึ่งเป็นวิธีที่คุณสามารถโทรได้Println(1, "one", true)เช่นไม่มีข้อผิดพลาด ดูส่วน"การส่งอาร์กิวเมนต์ไปยัง ... พารามิเตอร์"ของข้อกำหนดภาษา:

ค่าที่ส่งผ่านคือสไลซ์ใหม่ของชนิด [] T ที่มีอาร์เรย์พื้นฐานใหม่ซึ่งมีองค์ประกอบที่ต่อเนื่องกันเป็นอาร์กิวเมนต์ที่แท้จริงซึ่งทั้งหมดจะต้องกำหนดให้กับ T.

ส่วนที่ทำให้คุณมีปัญหาหลังจากนั้นในข้อกำหนด:

หากอาร์กิวเมนต์สุดท้ายสามารถกำหนดให้กับประเภทสไลซ์ [] T อาร์กิวเมนต์อาจถูกส่งต่อโดยไม่เปลี่ยนแปลงเป็นค่าสำหรับพารามิเตอร์ ... T หากอาร์กิวเมนต์ตามด้วย .... ในกรณีนี้จะไม่มีการสร้างชิ้นส่วนใหม่

flag.Args()[]stringเป็นประเภท ตั้งแต่TในPrintlnเป็นinterface{}, คือ[]T []interface{}ดังนั้นคำถามจึงสรุปได้ว่าค่าสไลซ์ของสตริงสามารถกำหนดให้กับตัวแปรของประเภทส่วนติดต่อได้หรือไม่ คุณสามารถทดสอบได้อย่างง่ายดายใน go code ของคุณโดยพยายามมอบหมายงานตัวอย่างเช่น:

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(), ` `)

(ซึ่งจะเอาท์พุทองค์ประกอบสไลซ์สตริงซึ่งแต่ละส่วนคั่นด้วยช่องว่างเดียว) โดยใช้ฟังก์ชันเข้าร่วมในแพ็กเกจสตริงที่มีตัวคั่นสตริงเป็นต้น


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 (a ... interface {}) (n int, err error)

เป็นไปได้ที่จะพิมพ์flag.Args()โดยไม่ต้องแปลงเป็นไฟล์[]interface{}

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

2
fmt.Printlnลายเซ็นของไม่มีการเปลี่ยนแปลงในรอบ 6 ปี (และนั่นเป็นเพียงการเปลี่ยนแปลงแพ็คเกจสำหรับerror) แม้ว่าจะมีข้อมูลจำเพาะระบุอย่างชัดเจนว่า "ตัวแปรที่มีพารามิเตอร์สุดท้าย p ของประเภท ... T จากนั้นภายใน f ประเภทของ p จะเทียบเท่ากับประเภท [] T` ดังนั้นจึงไม่สำคัญ fmt.Println(flags.Args()...)ยังไม่ทำงาน (คุณไม่มีส่วนขยายชิ้นส่วน) ใช้งานได้fmt.Println(flags.Args())ตลอด
Marc

คุณได้ข้อมูลนี้มาได้อย่างไรว่าลายเซ็นของ fmt.Println ไม่ได้เปลี่ยนแปลงในช่วง 6 ปีที่ผ่านมา? คุณตรวจสอบซอร์สโค้ดหรือไม่
Shahriar


ความคิดเห็นเกี่ยวกับ op แนะนำว่าให้ใช้flag.Args()อาร์กิวเมนต์เพื่อfmt.Printlnย้อนเวลากลับไปเท่านั้น (มันจะน่าแปลกใจถ้าตอนนั้นไม่ได้)
leaf bebop

ดังนั้น? หากมีความคิดเห็นแสดงว่าคำตอบของฉันควรถูกโหวตลง?
Shahriar
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.