2012-01-07 67 views
6

Trong ngôn ngữ lập trình Go; làm thế nào có thể con trỏ để con trỏ trở nên hữu ích?Con trỏ cho con trỏ là gì?

(Tại sao họ không bất hợp pháp nếu họ không thực sự hữu ích?)

+6

Con trỏ tới bất kỳ dữ liệu nào đều hữu ích. Con trỏ là dữ liệu. Vì vậy, con trỏ trỏ đến con trỏ rất hữu ích, và do đó, con trỏ trỏ đến con trỏ, ... –

+0

Tôi đồng ý, nhưng vì không có số học con trỏ hay bất cứ thứ gì để làm với con trỏ hơn là con trỏ trong Go, chúng thực sự là vô dụng. – thwd

+0

Có số học con trỏ trong Go. Bạn có thể sử dụng gói 'unsafe' để nhận' uintptr' mà bạn có thể sử dụng một cách tự do. Trong một số trường hợp, phương pháp này có thể dẫn đến mã hiệu quả hơn, nhưng nói chung, nó nên tránh. – tux21b

Trả lời

16

Tính hữu ích của bất kỳ loại dữ liệu phụ thuộc vào vấn đề được giải quyết và vào phương pháp sử dụng để giải quyết vấn đề. Nếu một kiểu dữ liệu không phù hợp với vấn đề, nó chỉ đơn giản là không phù hợp với vấn đề - và không có gì thêm cho nó.

Ngôn ngữ lập trình Go (cũng như hầu hết các ngôn ngữ lập trình khác) dựa trên các quy tắc đơn giản mà người lập trình có thể sử dụng để tạo các loại dữ liệu mới. Một số trong những quy tắc là:

  • *T: tạo ra một loại mới dữ liệu đó là một con trỏ đến T
  • [10]T: một mảng của Ts
  • struct { t T; u U ... }: một cấu trúc, trong đó có một T như một thành phần
  • ...

Lập trình viên có thể tạo các loại dữ liệu phức tạp bằng cách soạn các quy tắc đơn giản này. Tổng số loại dữ liệu có thể vượt quá số loại dữ liệu hữu ích. Rõ ràng, tồn tại (và phải tồn tại) các kiểu dữ liệu không hữu ích chút nào. Đây chỉ là một hệ quả tự nhiên của thực tế là các quy tắc để xây dựng các kiểu dữ liệu mới rất đơn giản.

Loại **T rơi vào danh mục các loại ít có khả năng xuất hiện trong chương trình. Thực tế là có thể viết *****T không ngụ ý rằng loại như vậy phải vô cùng hữu ích.


Và cuối cùng, câu trả lời cho câu hỏi của bạn:

Loại **T thường xuất hiện trong những bối cảnh mà chúng tôi muốn chuyển hướng người dùng của một giá trị kiểu T khác giá trị của loại T, nhưng đối với một số lý do chúng tôi không có quyền truy cập vào tất cả người dùng giá trị hoặc việc tìm kiếm người dùng sẽ mất quá nhiều thời gian:

  1. Chúng tôi không muốn sao chép các giá trị loại T (đối với một số lý do)
  2. Chúng tôi muốn tất cả người dùng của một giá trị kiểu T để truy cập giá trị thông qua một con trỏ
  3. Chúng tôi muốn nhanh chóng redirect tất cả người dùng của một giá trị cụ thể của loại T để giá trị khác

trong một tình huống như vậy, sử dụng **T là tự nhiên vì nó cho phép chúng ta thực hiện các bước thứ 3 trong thời gian O (1):

type User_of_T struct { 
    Value **T 
} 

// Redirect all users of a particular value of type T 
// to another value of type T. 
func (u *User_of_T) Redirect(t *T) { 
    *(u.Value) = t 
} 
+0

Đó là một giải pháp thông minh. – Tarik

3

Trong C con trỏ đến con trỏ là khá phổ biến.Ví dụ: mảng

  • chiều hơn (ví dụ một mảng các chuỗi, char** argv có thể là ví dụ nổi bật nhất ở đây)
  • con trỏ như các thông số đầu ra

Trong Go Tuy nhiên, con trỏ để trỏ là khá hiếm. Thay vì truy cập các mảng bằng một con trỏ, có một kiểu slice (cũng lưu trữ một con trỏ bên trong). Vì vậy, bạn vẫn có thể nhận được cùng một loại indirection bằng cách sử dụng một lát lát trong Go, nhưng bạn thường sẽ không thấy một cái gì đó như **int ở đây.

Ví dụ thứ hai tuy nhiên vẫn có thể áp dụng cho các chương trình Go. Giả sử bạn có một hàm có thể thay đổi con trỏ được chuyển thành tham số. Trong trường hợp đó, bạn sẽ phải chuyển một con trỏ tới con trỏ đó, để bạn có thể thay đổi con trỏ ban đầu. Điều này cực kỳ phổ biến trong C, vì các hàm chỉ có thể trả về một giá trị (thường là một loại mã lỗi) và nếu bạn muốn trả về một con trỏ bổ sung, bạn sẽ phải sử dụng một con trỏ trỏ tới con trỏ đó làm tham số đầu ra. Tuy nhiên, một hàm trong Go có thể trả về nhiều giá trị, do đó, sự xuất hiện của con trỏ đến con trỏ cũng rất hiếm. Nhưng chúng vẫn có thể hữu ích và có thể dẫn đến một API đẹp hơn trong một số trường hợp.

Ví dụ: chức năng atomic.StorePointer có thể là một trong những trường hợp sử dụng hiếm và được ẩn cho con trỏ đến con trỏ trong thư viện chuẩn.

5

Khi bạn chuyển con trỏ đến hàm, hàm sẽ nhận được bản sao của nó. Vì vậy, gán giá trị mới cho con trỏ nhất định sẽ không dẫn đến gán cho nó với bản gốc một:

type Smartphone struct { 
    name string 
} 

type Geek struct { 
    smartphone *Smartphone 
} 

func replaceByNG(s **Smartphone) { 
    *s = &Smartphone{"Galaxy Nexus"} 
} 

func replaceByIPhone(s *Smartphone) { 
    s = &Smartphone{"IPhone 4S"} 
} 

func main() { 
    geek := Geek{&Smartphone{"Nexus S"}} 
    println(geek.smartphone.name) 

    replaceByIPhone(geek.smartphone) 
    println(geek.smartphone.name) 

    replaceByNG(&geek.smartphone) 
    println(geek.smartphone.name) 
} 

Đầu ra là:

Nexus S 
Nexus S 
Galaxy Nexus 
+0

câu trả lời thực sự hữu ích, đây là những gì tôi thấy cố gắng sửa đổi lát trong một chức năng khác –

2

Linus Torvalds vừa nêu cách con trỏ để trỏ dẫn đến mã với hương vị tốt (trong C). Xem (trong số những người khác) Brian Barto's blog post.

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