Ý nghĩa của giao diện {} là gì?


133

Tôi chưa quen với giao diện và đang thử thực hiện yêu cầu SOAP bằng github

Tôi không hiểu ý nghĩa của

Msg interface{}

trong mã này:

type Envelope struct {
    Body `xml:"soap:"`
}

type Body struct {
    Msg interface{}
}

Tôi đã quan sát cú pháp tương tự trong

fmt.Println

nhưng không hiểu những gì đang đạt được bởi

interface{}

20
interface{}ít nhiều tương đương với void *C. Nó có thể trỏ đến bất cứ thứ gì và bạn cần một xác nhận cast / type để sử dụng nó.
Nick Craig-Wood

Ý nghĩa của giao diện {} là gì? Xem stackoverflow.com/a/62337836/12817546 .
Tom J

Câu trả lời:


189

Bạn có thể tham khảo bài viết " Cách sử dụng giao diện trong Go " (dựa trên " mô tả giao diện của Russ Cox "):

Có gì một giao diện?

Một giao diện có hai điều:

  • nó là một tập hợp các phương thức
  • nhưng nó cũng là một loại

Các interface{}loại, giao diện rỗng là giao diện mà không có phương pháp.

Vì không có từ khóa thực hiện, tất cả các loại đều thực hiện ít nhất các phương thức bằng 0 và việc thỏa mãn một giao diện được thực hiện tự động, tất cả các loại đều thỏa mãn giao diện trống .
Điều đó có nghĩa là nếu bạn viết một hàm lấy một interface{}giá trị làm tham số, bạn có thể cung cấp hàm đó với bất kỳ giá trị nào .

(Đó là những gì Msgthể hiện trong câu hỏi của bạn: bất kỳ giá trị nào)

func DoSomething(v interface{}) {
   // ...
}

Đây là nơi nó gây nhầm lẫn:

bên trong DoSomethingchức năng, là những gì v's loại?

Những người mới bắt đầu được tin rằng, đó vlà bất kỳ loại nào, nhưng điều đó là sai.
vkhông thuộc loại nào; nó là interface{}loại .

Khi chuyển một giá trị vào DoSomethinghàm, bộ thực thi Go sẽ thực hiện chuyển đổi loại (nếu cần) và chuyển đổi giá trị thành interface{}giá trị .
Tất cả các giá trị có chính xác một loại trong thời gian chạy và vmột loại tĩnh là interface{}.

Một giá trị giao diện được xây dựng từ hai từ dữ liệu :

  • một từ được sử dụng để trỏ đến bảng phương thức cho kiểu cơ bản của giá trị,
  • và từ còn lại được sử dụng để chỉ dữ liệu thực tế được giữ bởi giá trị đó.

Phụ lục: Đây là bài viết của Nga khá đầy đủ về cấu trúc giao diện:

type Stringer interface {
    String() string
}

Các giá trị giao diện được biểu diễn dưới dạng một cặp hai từ cung cấp một con trỏ tới thông tin về loại được lưu trữ trong giao diện và một con trỏ tới dữ liệu liên quan.
Việc gán b cho một giá trị giao diện của kiểu Stringer đặt cả hai từ của giá trị giao diện.

http://research.swtch.com/gulum2.png

Từ đầu tiên trong giá trị giao diện chỉ vào cái mà tôi gọi là bảng giao diện hoặc có thể đọc được (phát âm là i-bảng; trong các nguồn thời gian chạy, tên triển khai C là Itab).
Nó có thể bắt đầu với một số siêu dữ liệu về các loại liên quan và sau đó trở thành một danh sách các con trỏ hàm.
Lưu ý rằng nó có thể tương ứng với loại giao diện, không phải loại động .
Theo ví dụ của chúng tôi, có thể Stringergiữ kiểu Binary liệt kê các phương thức được sử dụng để đáp ứng Stringer, đó là String: Các phương thức khác của Binary ( Get) không xuất hiện trong itable.

Từ thứ hai trong giá trị giao diện trỏ vào dữ liệu thực tế , trong trường hợp này là bản sao của b.
Việc chuyển nhượng var s Stringer = btạo ra một bản sao bchứ không phải tại bcùng một lý do var c uint64 = btạo ra một bản sao: nếu bsau này thay đổi scđược cho là có giá trị ban đầu, không phải là giá trị mới.
Các giá trị được lưu trữ trong các giao diện có thể lớn tùy ý, nhưng chỉ có một từ được dành riêng để giữ giá trị trong cấu trúc giao diện, do đó, việc gán sẽ phân bổ một đoạn bộ nhớ trên heap và ghi lại con trỏ trong khe một từ.


4
"Hai từ dữ liệu" nghĩa là gì? Cụ thể, "từ" có nghĩa là gì?
Mingyu

3
@Mingyu Tôi đã hoàn thành câu trả lời để minh họa hai từ đó (điểm 32 bit).
VonC

2
@Mingyu: VonC đang đề cập đến một từ theo nghĩa kiến ​​trúc máy tính - một tập hợp các bit xác định một đoạn dữ liệu có kích thước cố định. Kích thước từ bị chi phối bởi kiến ​​trúc bộ xử lý bạn đang sử dụng.
Dan Esparza

1
cảm ơn @VonC vì câu trả lời của bạn ... sự thật là tôi cảm thấy mệt mỏi khi bị hạ bệ khi tôi hỏi mọi thứ .. mọi người thường nói với tôi rằng tôi nên đọc tài liệu ... tôi sẽ nhớ lại đề nghị của bạn nếu tôi cảm thấy sẽ viết đúng một bài đăng cho nó ... nhưng tôi thực sự không thể nghĩ theo cách khác để hỏi. Vì vậy, cảm ơn dù sao và xin lỗi ý chí thấp của tôi. Bạn có thể xem qua điều này: stackoverflow.com/questions/45577602/ vào để làm rõ lý do tại sao tôi không muốn hỏi.
Victor

1
@vic không có vấn đề gì, và xin lỗi vì trải nghiệm tồi tệ trước đây của bạn khi hỏi. Chỉ có ý kiến ​​là không phù hợp cho câu hỏi và câu trả lời.
VonC

34

interface{}có nghĩa là bạn có thể đặt giá trị của bất kỳ loại nào, bao gồm loại tùy chỉnh của riêng bạn. Tất cả các loại trong Go đều đáp ứng giao diện trống ( interface{}là giao diện trống).
Trong ví dụ của bạn, trường Msg có thể có giá trị của bất kỳ loại nào.

Thí dụ:

package main

import (
    "fmt"
)

type Body struct {
    Msg interface{}
}

func main() {
    b := Body{}
    b.Msg = "5"
    fmt.Printf("%#v %T \n", b.Msg, b.Msg) // Output: "5" string
    b.Msg = 5

    fmt.Printf("%#v %T", b.Msg, b.Msg) //Output:  5 int
}

Đi chơi


12

Nó được gọi là giao diện trống và được triển khai bởi tất cả các loại, có nghĩa là bạn có thể đặt bất cứ thứ gì vào Msgtrường.

Thí dụ :

body := Body{3}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:3}

body = Body{"anything"}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:"anything"}

body = Body{body}
fmt.Printf("%#v\n", body) // -> main.Body{Msg:main.Body{Msg:"anything"}}

Đây là phần mở rộng hợp lý của thực tế là một loại thực hiện một giao diện ngay khi nó có tất cả các phương thức của giao diện.


có nghĩa là nó có thể là một cấu trúc int cho người dùng xác định ??
người dùng

11

Đã có câu trả lời tốt ở đây. Hãy để tôi thêm cái riêng của mình cho những người khác muốn hiểu nó bằng trực giác:


Giao diện

Đây là một giao diện với một phương thức:

type Runner interface {
    Run()
}

Vì vậy, bất kỳ loại nào có Run()phương thức đều thỏa mãn giao diện Runner:

type Program struct {
    /* fields */
}

func (p Program) Run() {
    /* running */
}

func (p Program) Stop() {
    /* stopping */
}
  • Mặc dù loại Chương trình cũng có phương thức Dừng, nhưng nó vẫn thỏa mãn giao diện Người chạy vì tất cả những gì cần thiết là có tất cả các phương thức của giao diện để đáp ứng.

  • Vì vậy, nó có một phương thức Run và nó đáp ứng giao diện Runner.


Giao diện trống

Đây là một giao diện trống có tên mà không có bất kỳ phương thức nào:

type Empty interface {
    /* it has no methods */
}

Vì vậy, bất kỳ loại thỏa mãn giao diện này. Bởi vì, không có phương pháp nào là cần thiết để đáp ứng giao diện này. Ví dụ:

// Because, Empty interface has no methods, following types satisfy the Empty interface
var a Empty

a = 5
a = 6.5
a = "hello"

Nhưng, loại chương trình trên có đáp ứng nó không? Đúng:

a = Program{} // ok

giao diện {} bằng với giao diện rỗng ở trên.

var b interface{}

// true: a == b

b = a
b = 9
b = "bye"

Như bạn thấy, không có gì bí ẩn về điều đó nhưng nó rất dễ bị lạm dụng. Tránh xa nó nhiều như bạn có thể.


https://play.golang.org/p/A-vwTddWJ7G


Không type Runner interfaceđược sử dụng trong ví dụ sân chơi Go.
Tom J

9

Từ thông số kỹ thuật Golang :

Một loại giao diện chỉ định một bộ phương thức gọi là giao diện của nó. Một biến của loại giao diện có thể lưu trữ một giá trị của bất kỳ loại nào với một tập phương thức là bất kỳ siêu bộ giao diện nào. Một loại như vậy được cho là để thực hiện giao diện. Giá trị của một biến chưa được khởi tạo của loại giao diện là nil.

Một kiểu thực hiện bất kỳ giao diện nào bao gồm bất kỳ tập hợp con các phương thức của nó và do đó có thể thực hiện một số giao diện riêng biệt. Ví dụ, tất cả các loại thực hiện giao diện trống:

giao diện {}

Các khái niệm về graps là:

  1. Mọi thứ đều có Loại . Bạn có thể định nghĩa một kiểu mới, chúng ta hãy gọi nó là T. Giả sử bây giờ Loại của chúng tôi Tcó 3 phương pháp: A, B, C.
  2. Tập hợp các phương thức được chỉ định cho một loại được gọi là " loại giao diện ". Hãy gọi nó trong ví dụ của chúng tôi: T_interface. BằngT_interface = (A, B, C)
  3. Bạn có thể tạo một "loại giao diện" bằng cách xác định chữ ký của các phương thức.MyInterface = (A, )
  4. Khi bạn xác định một biến của kiểu "kiểu giao diện", bạn có thể gán cho nó chỉ loại mà có một giao diện mà là một superset của giao diện của bạn. Điều đó có nghĩa là tất cả các phương thức chứa trong MyInterfacephải được chứa bên trongT_interface

Bạn có thể suy luận rằng tất cả các "loại giao diện" của tất cả các loại là một siêu bộ giao diện trống.


1

Một ví dụ mở rộng câu trả lời xuất sắc của @VonC và nhận xét của @ NickCraig-Wood. interface{}có thể chỉ ra bất cứ điều gì và bạn cần một xác nhận cast / type để sử dụng nó.

package main

import (
    . "fmt"
    "strconv"
)

var c = cat("Fish")
var d = dog("Bone")

func main() {
    var i interface{} = c
    switch i.(type) {
    case cat:
        c.Eat() // Fish
    }

    i = d
    switch i.(type) {
    case dog:
        d.Eat() // Bone
    }

    i = "4.3"
    Printf("%T %v\n", i, i) // string 4.3
    s, _ := i.(string)      // type assertion
    f, _ := strconv.ParseFloat(s, 64)
    n := int(f)             // type conversion
    Printf("%T %v\n", n, n) // int 4
}

type cat string
type dog string
func (c cat) Eat() { Println(c) }
func (d dog) Eat() { Println(d) }

ilà một biến của một giao diện trống có giá trị cat("Fish"). Việc tạo một giá trị phương thức từ một giá trị của loại giao diện là hợp pháp. Xem https://golang.org/ref/spec#Interface_types .

Một loại chuyển đổi xác nhận iloại giao diện là cat("Fish"). Xem https://golang.org/doc/effective_go.html#type_switch . isau đó được gán lại cho dog("Bone"). Một công tắc loại xác nhận rằng loại igiao diện đã thay đổi thành dog("Bone").

Bạn cũng có thể yêu cầu trình biên dịch kiểm tra xem kiểu này có Tthực hiện giao diện hay không Ibằng cách thử gán : var _ I = T{}. Xem https://golang.org/doc/faq#guarantee_satisfies_interfacehttps://stackoverflow.com/a/60663003/12817546 .

Tất cả các loại thực hiện giao diện trống interface{}. Xem https://talks.golang.org/2012/goforc.slide#44https://golang.org/ref/spec#Interface_types . Trong ví dụ này, iđược gán lại, lần này là một chuỗi "4.3". isau đó được gán cho một biến chuỗi mới svới i.(string)trước đó sđược chuyển đổi thành kiểu float64 fbằng cách sử dụng strconv. Cuối cùng fđược chuyển đổi thành nkiểu int bằng 4. Xem sự khác biệt giữa chuyển đổi loại và xác nhận kiểu là gì?

Các bản đồ và lát cắt tích hợp của Go, cộng với khả năng sử dụng giao diện trống để xây dựng các thùng chứa (với hộp thư rõ ràng) có nghĩa là trong nhiều trường hợp có thể viết mã thực hiện những gì tổng quát sẽ kích hoạt, nếu kém trơn tru. Xem https://golang.org/doc/faq#generics .


Mã tách rời với một giao diện. Xem stackoverflow.com/a/62297796/12817546 . Gọi một phương thức động lực học. Xem stackoverflow.com/a/62336440/12817546 . Truy cập gói Go. Xem stackoverflow.com/a/62278078/12817546 . Gán bất kỳ giá trị cho một biến. Xem stackoverflow.com/a/62337836/12817546 .
Tom J
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.