Không thể chuyển đổi chuỗi [] thành [] giao diện {}


96

Tôi đang viết một số mã và tôi cần nó để bắt các đối số và chuyển chúng qua fmt.Println
(Tôi muốn hành vi mặc định của nó, viết các đối số được phân tách bằng dấu cách và theo sau là một dòng mới). Tuy nhiên nó mất []interface {}nhưng flag.Args()trả về a []string.
Đây là ví dụ về mã:

package main

import (
    "fmt"
    "flag"
)

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

Điều này trả về lỗi sau:

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

Đây có phải là một lỗi? Không nên fmt.Printlnlấy bất kỳ mảng? Nhân tiện, tôi cũng đã cố gắng làm điều này:

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

nhưng tôi gặp lỗi sau:

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

Có cách "Go" nào để giải quyết vấn đề này không?


1
Tôi đã nhầm lẫn với một ví dụ đơn giản ( go run test.go some test flags) và nó dường như hoạt động khi thay đổi flags.Args()...thành just flag.Args()(đầu ra là [some test flags], theo sau là dòng mới; dường như cũng hoạt động với việc đăng ký cờ thực tế). Sẽ không giả vờ để hiểu tại sao, và câu trả lời của Stephen là cách thông tin mới hơn anyway :)
RocketDonkey

Câu trả lời:


115

Đây không phải là một lỗi. fmt.Println()yêu cầu một []interface{}loại. Điều đó có nghĩa là, nó phải là một lát interface{}giá trị chứ không phải "bất kỳ lát cắt nào". Để chuyển đổi lát cắt, bạn sẽ cần lặp lại và sao chép từng phần tử.

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

Lý do bạn không thể sử dụng bất kỳ lát cắt nào là việc chuyển đổi giữa a []stringvà a []interface{}yêu cầu bố cục bộ nhớ được thay đổi và xảy ra trong thời gian O (n). Chuyển đổi một loại thành một interface{}yêu cầu O (1) thời gian. Nếu họ làm cho vòng lặp for này là không cần thiết, trình biên dịch vẫn cần chèn nó.


2
Bằng cách này, tôi thấy liên kết này trong golang-hạt: groups.google.com/d/topic/golang-nuts/Il-tO1xtAyE/discussion
cruizh

2
Đúng, mỗi lần lặp yêu cầu O (1) thời gian và vòng lặp yêu cầu O (n) thời gian. Đó là những gì tôi đã nói. Đối với hàm nhận a []string, nó mong đợi a interface{}. An interface{}có cách bố trí bộ nhớ khác với a stringnên vấn đề là mỗi phần tử cần được chuyển đổi.
Stephen Weinberg

1
@karlrh: Không, giả sử Printlnhàm sửa đổi lát cắt và đặt một số phần tử (thì không, nhưng giả sử thì có). Sau đó, nó có thể đặt bất kỳ interface{}vào lát cắt, mà chỉ nên có strings. Những gì bạn thực sự muốn là một cái gì đó giống như ký tự đại diện Java Generics Slice<? extends []interface{}>, nhưng điều đó không tồn tại trong Go.
newacct 21/10/12

4
Nối là kỳ diệu. Nó được cài sẵn và xử lý đặc biệt bằng ngôn ngữ. Những ví dụ khác bao gồm new(), len()copy(). golang.org/ref/spec#Appending_and_copying_slices
Stephen Weinberg

1
Hôm nay tôi học được 'mới' không phải là một từ khóa dự trữ! Cũng không phải là make!
Sridhar

11

Nếu đó chỉ là một đoạn chuỗi bạn muốn in, bạn có thể tránh chuyển đổi và nhận được kết quả đầu ra chính xác bằng cách kết hợp:

package main

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

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

11

Trong trường hợp này, chuyển đổi kiểu là không cần thiết. Chỉ cần chuyển flag.Args()giá trị cho fmt.Println.


Câu hỏi:

Không thể chuyển đổi chuỗi [] thành [] giao diện {}

Tôi đang viết một số mã và tôi cần nó để bắt các đối số và chuyển chúng qua fmt.Println (Tôi muốn hành vi mặc định của nó, để viết các đối số được phân tách bằng dấu cách và theo sau là một dòng mới).

Đây là ví dụ về mã:

package main

import (
    "fmt"
    "flag"
)

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

Cờ gói

import "flag"

func Args

func Args() []string

Args trả về các đối số dòng lệnh không gắn cờ.


Gói fmt

import "fmt"

func Println

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

Printlncác định dạng sử dụng các định dạng mặc định cho các toán hạng của nó và ghi vào đầu ra tiêu chuẩn. Dấu cách luôn được thêm vào giữa các toán hạng và một dòng mới được thêm vào. Nó trả về số byte được ghi và bất kỳ lỗi ghi nào gặp phải.


Trong trường hợp này, chuyển đổi kiểu là không cần thiết. Đơn giản chỉ cần chuyển flag.Args()giá trị tới fmt.Println, sử dụng phản xạ để diễn giải giá trị dưới dạng kiểu []string. Gói reflectthực hiện phản chiếu thời gian chạy, cho phép một chương trình thao tác với các đối tượng với các kiểu tùy ý. Ví dụ,

args.go:

package main

import (
    "flag"
    "fmt"
)

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

Đầu ra:

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

Nếu tôi muốn bỏ qua các dấu ngoặc, việc chuyển đổi có còn không cần thiết không?
cruizh

1

Trong Go, một hàm chỉ có thể chấp nhận các đối số của các kiểu được chỉ định trong danh sách tham số trong định nghĩa hàm. Tính năng ngôn ngữ tham số đa dạng làm phức tạp điều đó một chút, nhưng nó tuân theo các quy tắc được xác định rõ.

Chữ ký hàm cho fmt.Printlnlà:

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

Theo đặc điểm ngôn ngữ ,

Tham số đến cuối cùng trong một chữ ký hàm có thể có kiểu được bắt đầu bằng .... Một hàm có tham số như vậy được gọi là variadic và có thể được gọi với 0 hoặc nhiều đối số cho tham số đó.

Điều này có nghĩa là bạn có thể chuyển Printlnmột danh sách các đối số interface{}kiểu. Vì tất cả các loại đều triển khai giao diện trống, nên bạn có thể chuyển một danh sách các đối số thuộc bất kỳ loại nào, đó là cách bạn có thể gọi Println(1, "one", true), ví dụ, mà không bị lỗi. Xem phần "Truyền đối số cho ... tham số" của đặc tả ngôn ngữ:

giá trị được truyền là một lát cắt kiểu mới [] T với một mảng cơ bản mới có các phần tử kế tiếp là các đối số thực tế, tất cả các phần tử này phải được gán cho T.

Phần gây rắc rối cho bạn nằm ngay sau đó trong thông số kỹ thuật:

Nếu đối số cuối cùng có thể gán cho kiểu lát cắt [] T, thì nó có thể được chuyển không thay đổi làm giá trị cho tham số ... T nếu đối số được theo sau bởi .... Trong trường hợp này, không có lát cắt mới nào được tạo.

flag.Args()là loại []string. Kể từ Ttrong Printlninterface{}, []T[]interface{}. Vì vậy, câu hỏi đặt ra là liệu một giá trị lát cắt chuỗi có thể gán cho một biến của kiểu lát giao diện hay không. Bạn có thể dễ dàng kiểm tra điều đó trong mã go của mình bằng cách thử một nhiệm vụ, ví dụ:

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

Nếu bạn cố gắng gán như vậy, trình biên dịch sẽ xuất ra thông báo lỗi sau:

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

Và đó là lý do tại sao bạn không thể sử dụng dấu chấm lửng sau một lát chuỗi làm đối số fmt.Println. Nó không phải là một lỗi, nó đang hoạt động như dự định.

Có rất nhiều vẫn cách bạn có thể in flag.Args()với Println, chẳng hạn như

fmt.Println(flag.Args())

(sẽ xuất ra [elem0 elem1 ...], theo tài liệu gói fmt )

hoặc là

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

(ví dụ sẽ xuất ra các phần tử lát chuỗi, mỗi phần tử được phân tách bằng một khoảng trắng) bằng cách sử dụng hàm Tham gia trong gói chuỗi có dấu phân tách chuỗi, chẳng hạn.


0

Tôi nghĩ có thể sử dụng phản xạ, nhưng tôi không biết đó có phải là giải pháp tốt không

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

Đầu ra:

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

-1

fmt.Printlnlấy tham số đa dạng

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

Có thể in flag.Args()mà không cần chuyển đổi thành[]interface{}

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

2
fmt.PrintlnChữ ký của đã không thay đổi trong hơn 6 năm (và đó chỉ là một sự thay đổi gói cho error). Ngay cả khi nó có, thông số rõ ràng nói rằng `không đúng với tham số cuối cùng p thuộc loại ... T, thì trong f loại p tương đương với loại [] T.` nên nó sẽ không thành vấn đề. fmt.Println(flags.Args()...)vẫn không hoạt động (bạn đang thiếu phần mở rộng), fmt.Println(flags.Args())luôn hoạt động.
Marc

Làm thế nào bạn có được thông tin này mà chữ ký của fmt.Println không thay đổi trong hơn 6 năm? Bạn đã kiểm tra mã nguồn chưa?
Shahriar


Nhận xét trên op gợi ý rằng flag.Args()chỉ sử dụng làm đối số để fmt.Printlnlàm việc trở lại thời điểm đó. (Nó sẽ được ngạc nhiên nếu nó đã không tại thời điểm đó)
lá Bebop

Vì thế? Nếu tồn tại một bình luận, điều đó có nghĩa là câu trả lời của tôi sẽ bị bỏ phiếu?
Shahriar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.