2013-06-07 25 views
31

Giả sử tôi có đoạn mã sau:Trong Go, tại sao phương thức giao diện Stringer của tôi không được gọi? Khi sử dụng fmt.Println

package main 

import "fmt" 

type Car struct{ 
    year int 
    make string 
} 

func (c *Car)String() string{ 
    return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year) 
} 

func main() { 
    myCar := Car{year:1996, make:"Toyota"} 
    fmt.Println(myCar) 
} 

Khi tôi gọi fmt.Println (myCar) và các đối tượng trong câu hỏi là một con trỏ, phương pháp chuỗi của tôi() được gọi là đúng. Nếu, tuy nhiên đối tượng là một giá trị, đầu ra của tôi được định dạng bằng cách sử dụng định dạng mặc định được tích hợp vào Go và mã của tôi để định dạng đối tượng đã nói không được gọi.

Điều thú vị là trong cả hai trường hợp nếu tôi gọi myCar.String() theo cách thủ công, nó hoạt động đúng cho dù đối tượng của tôi là con trỏ hay giá trị.

Làm cách nào để có được đối tượng được định dạng theo cách tôi muốn cho dù đối tượng có dựa trên giá trị hay dựa trên con trỏ khi được sử dụng với Println?

Tôi không muốn sử dụng một phương pháp giá trị cho chuỗi bởi vì sau đó điều đó có nghĩa là mỗi lần nó được gọi đối tượng được sao chép mà đường nối không hợp lý. Và tôi không muốn phải luôn luôn được gọi bằng tay .String() hoặc bởi vì tôi đang cố gắng để cho hệ thống gõ vịt làm việc đó.

Cảm ơn trước!

-Ralph

Trả lời

50

Khi gọi fmt.Println, myCar được ngầm chuyển đổi sang một giá trị kiểu interface{} như bạn có thể nhìn thấy từ chữ ký chức năng. Mã từ gói fmt sau đó thực hiện một type switch để tìm ra cách để in giá trị này, tìm một cái gì đó như thế này:

switch v := v.(type) { 
case string: 
    os.Stdout.WriteString(v) 
case fmt.Stringer: 
    os.Stdout.WriteString(v.String()) 
// ... 
} 

Tuy nhiên, trường hợp không thành công vì fmt.StringerCar không thực hiện String (vì nó được định nghĩa trên *Car). Gọi String hoạt động thủ công vì trình biên dịch thấy rằng String cần một số *Car và do đó sẽ tự động chuyển đổi myCar.String() thành (&myCar).String(). Đối với bất cứ điều gì liên quan đến giao diện, bạn phải làm điều đó bằng tay. Vì vậy, bạn có thể sở để thực hiện String trên Car hoặc luôn luôn vượt qua một con trỏ đến fmt.Println:

fmt.Println(&myCar) 
+0

Tôi cho rằng nó cũng là một phần vì ngôn ngữ sẽ không cung cấp cho bạn một con trỏ với những gì được lưu trữ bên trong một giá trị giao diện (vì địa chỉ đó có thể kết thúc đại diện cho một kiểu khác nếu một cái gì đó khác được gán cho giao diện). –

+0

@ JamesHenstridge, tại sao không để lại giá trị cũ trong một con trỏ trong trường hợp đó? – starius

16

Phương pháp

Pointers vs. Values

Nguyên tắc về con trỏ vs giá trị cho người nhận là phương pháp giá trị có thể được gọi trên con trỏ và các giá trị, nhưng các phương thức con trỏ chỉ có thể được gọi là được gọi trên con trỏ. Điều này là do các phương thức con trỏ có thể sửa đổi bộ thu ; gọi chúng trên một bản sao của giá trị sẽ làm cho những sửa đổi bị loại bỏ.

Do đó, để phương thức String hoạt động khi được gọi trên cả hai con trỏ và giá trị, hãy sử dụng trình nhận giá trị. Ví dụ,

package main 

import "fmt" 

type Car struct { 
    year int 
    make string 
} 

func (c Car) String() string { 
    return fmt.Sprintf("{make:%s, year:%d}", c.make, c.year) 
} 

func main() { 
    myCar := Car{year: 1996, make: "Toyota"} 
    fmt.Println(myCar) 
    fmt.Println(&myCar) 
} 

Output:

{make:Toyota, year:1996} 
{make:Toyota, year:1996} 
+14

Điều này về cơ bản có nghĩa là nếu tôi có một cấu trúc lớn, rằng mỗi lần nó đi qua Println Nó sẽ được sao chép? Như tôi đã nói trong bài đăng của tôi, những đường nối này không hợp lý. –

7

Xác định fmt của bạn.Stringer trên một máy thu trỏ:

package main 

import "fmt" 

type Car struct { 
     year int 
     make string 
} 

func (c *Car) String() string { 
     return fmt.Sprintf("{maker:%s, produced:%d}", c.make, c.year) 
} 

func main() { 
     myCar := Car{year: 1996, make: "Toyota"} 
     myOtherCar := &Car{year: 2013, make: "Honda"} 
     fmt.Println(&myCar) 
     fmt.Println(myOtherCar) 
} 

Playground


Output:

{maker:Toyota, produced:1996} 
{maker:Honda, produced:2013}  

Sau đó, luôn luôn vượt qua một con trỏ đến trường hợp của xe để fmt.Println. Bằng cách này, bạn có thể tránh được một bản sao có giá trị tiềm ẩn trong tầm kiểm soát của mình.

-3

Nói chung, nó là tốt nhất để tránh gán giá trị cho biến qua initializers tĩnh, tức là

f := Foo{bar:1,baz:"2"} 

này là bởi vì nó có thể tạo ra một cách chính xác các khiếu nại bạn đang nói về, nếu bạn quên để vượt qua foo như một con trỏ qua &foohoặc bạn quyết định sử dụng bộ thu giá trị mà bạn sẽ tạo ra nhiều bản sao của các giá trị của bạn.

Thay vào đó, hãy cố gắng gán con trỏ để initializers tĩnh theo mặc định, tức là

f := &Foo{bar:1,baz:"2"} 

Bằng cách này f sẽ luôn luôn là một con trỏ và thời gian duy nhất bạn sẽ nhận được một bản sao giá trị nếu bạn sử dụng một cách rõ ràng người nhận giá trị.

(Có lần Tất nhiên khi bạn muốn để lưu trữ các giá trị từ một initializer tĩnh, nhưng những người cần được các trường hợp cạnh)

Các vấn đề liên quan