2012-06-11 16 views
7

Estoy aprendiendo Go, y estoy un poco confundido sobre cuándo usar punteros. Específicamente, al devolver un struct desde una función, ¿cuándo es apropiado devolver la instancia de la estructura en sí, y cuándo es apropiado devolver un puntero a la estructura?¿Cuándo es una buena idea devolver un puntero a una estructura?

código Ejemplo:

type Car struct { 
    make string 
    model string 
} 

func Whatever() { 
    var car Car 

    car := Car{"honda", "civic"} 

    // ... 

    return car 
} 

¿Cuáles son las situaciones en las que me gustaría para devolver un puntero, y en el que se no quieren? ¿Hay una buena regla general?

+4

Esto no es C ... –

+0

¿No se aplicarían las mismas reglas? – Carson

+2

no, diferentes reglas para diferentes idiomas. Todos los idiomas tienen sus advertencias, y yo personalmente no sé Ir, así que no puedo hablar por ello, pero sí sé que en C, devolver un puntero a un objeto asignado en la pila es un no-no gigante. –

Respuesta

12

Hay dos cosas que desea tener en cuenta, rendimiento y API.

¿Cómo se utiliza un automóvil? ¿Es un objeto que tiene estado? ¿Es una gran estructura? Desafortunadamente, es imposible responder cuando no tengo idea de qué es un Auto. A decir verdad, la mejor manera es ver lo que otros hacen y copiarlos. Eventualmente, tienes una sensación para este tipo de cosas. Ahora describiré tres ejemplos de la biblioteca estándar y explicaré por qué creo que usaron lo que hicieron.

  1. hash/crc32: La función de crc32.NewIEEE() devuelve un tipo de puntero (en realidad, una interfaz, pero el tipo subyacente es un puntero). Una instancia de una función hash tiene estado. A medida que escribe información en un hash, resume los datos para que cuando llame al método Sum(), le proporcione el estado de esa instancia.

  2. time: La función time.Date devuelve Time struct. ¿Por qué? Un tiempo es un tiempo. No tiene estado. Es como un número entero donde puede compararlos, realizar cálculos matemáticos en ellos, etc. El diseñador de la API decidió que una modificación de una hora no cambiaría la actual, sino que crearía una nueva. Como usuario de la biblioteca, si quiero el tiempo de un mes a partir de ahora, me gustaría un nuevo objeto de tiempo, no para cambiar el actual que tengo. Un tiempo también tiene solo 3 palabras de longitud. En otras palabras, es pequeño y no habría ganancia de rendimiento en el uso de un puntero.

  3. math/big: big.NewInt() es interesante. Podemos aceptar que cuando modifique un big.Int, a menudo querrá uno nuevo. Un big.Int no tiene un estado interno, entonces ¿por qué es un puntero? La respuesta es simplemente el rendimiento. Los programadores se dieron cuenta de que los grandes ingresos son ... grandes. Asignar constantemente cada vez que realiza una operación matemática puede no ser práctico. Entonces, decidieron usar punteros y permitir que el programador decidiera cuándo asignar espacio nuevo.

¿He respondido a su pregunta? Probablemente no. Es una decisión de diseño y debe resolverla caso por caso. Utilizo la biblioteca estándar como guía cuando estoy diseñando mis propias bibliotecas. Realmente todo se reduce a juicio y cómo esperas que el código del cliente use tus tipos.

+1

tuve que leer esto unas cuantas veces para darme cuenta de que esta es una muy buena respuesta. gracias. – Carson

+0

Excelente respuesta. Soy nuevo en Ir, y Stephen lo refirió aquí de # go-nuts en IRC. –

1

Muy losely, las excepciones son propensos a aparecer en circunstancias específicas:

  • devolver un valor cuando es muy pequeño (no más de unas pocas palabras).
  • Devuelve un puntero cuando la sobrecarga de copiado dañaría sustancialmente el rendimiento (el tamaño es un montón de palabras).
2

A menudo, cuando quiere imitar un estilo orientado a objetos, donde tiene un "objeto" que almacena estado y "métodos" que pueden alterar el objeto, entonces tendría una función de "constructor" que devuelve un puntero a una estructura (piénselo como la "referencia de objeto" como en otros lenguajes OO). Los métodos de mutación tendrían que ser métodos del tipo puntero a estructura en lugar del tipo de estructura en sí, para cambiar los campos del "objeto", por lo que es conveniente tener un puntero a la estructura en lugar de una estructura valor en sí mismo, de modo que todos los "métodos" estarán en su conjunto de métodos.

Por ejemplo, para imitar algo como esto en Java:

class Car { 
    String make; 
    String model; 
    public Car(String myMake) { make = myMake; } 
    public setMake(String newMake) { make = myMake; } 
} 

Usted solía ver algo como esto en Ir:

type Car struct { 
    make string 
    model string 
} 
func NewCar(myMake string) *Car { 
    return &Car{myMake, ""} 
} 
func (self *Car) setMake(newMake string) { 
    self.make = newMake 
} 
+0

entonces, con go, ¿no debería estar usando la palabra clave 'new'? debería devolver una referencia como lo has ilustrado? estoy confundido por tu ejemplo. – Carson

+0

@Carson: bueno, puedes usar 'new', pero' new' inicializa el valor al valor cero del tipo, que para las estructuras es todos los campos inicializados al valor cero. Es posible que no sea un valor válido inicializado para todos los tipos. Si el valor cero está bien para inicializar su tipo, entonces puede simplemente usar 'new'; pero si su tipo necesita un "constructor" personalizado, entonces debe definir su propia función como mostré – newacct

Cuestiones relacionadas