valores retornan referencia que por parametros funciones ejemplos con como arreglos pointers go

pointers - retornan - Punteros vs. valores en parámetros y valores de retorno



funciones que retornan valores en c++ (2)

Tres razones principales cuando querría usar receptores de método como punteros:

  1. "Primero, y lo más importante, ¿el método necesita modificar el receptor? Si lo hace, el receptor debe ser un indicador".

  2. "La segunda es la consideración de la eficiencia. Si el receptor es grande, por ejemplo, una estructura grande, será mucho más barato usar un receptor de puntero".

  3. "Lo siguiente es la consistencia. Si algunos de los métodos del tipo deben tener receptores de puntero, el resto también debería hacerlo, por lo que el conjunto de métodos es consistente independientemente de cómo se use el tipo"

Referencia: https://golang.org/doc/faq#methods_on_values_or_pointers

Edición: Otra cosa importante es saber el "tipo" real que está enviando a la función. El tipo puede ser un ''tipo de valor'' o ''tipo de referencia''. Vea la figura a continuación:

Incluso como los cortes y mapas actúan como referencias, es posible que desee pasarlos como indicadores en escenarios como cambiar la longitud del corte en la función.

En Go hay varias formas de devolver un valor de struct o una porción del mismo. Para los individuales que he visto:

type MyStruct struct { Val int } func myfunc() MyStruct { return MyStruct{Val: 1} } func myfunc() *MyStruct { return &MyStruct{} } func myfunc(s *MyStruct) { s.Val = 1 }

Entiendo las diferencias entre estos. El primero devuelve una copia de la estructura, el segundo un puntero al valor de estructura creado dentro de la función, el tercero espera que se pase una estructura existente y reemplaza el valor.

He visto que todos estos patrones se utilizan en diversos contextos, me pregunto cuáles son las mejores prácticas con respecto a estos. ¿Cuándo usarías cual? Por ejemplo, el primero podría estar bien para estructuras pequeñas (porque la sobrecarga es mínima), y el segundo para estructuras más grandes. Y el tercero, si quiere ser extremadamente eficiente en memoria, porque puede reutilizar fácilmente una instancia de estructura única entre llamadas. ¿Hay alguna práctica recomendada sobre cuándo usar cuál?

Del mismo modo, la misma pregunta con respecto a las rebanadas:

func myfunc() []MyStruct { return []MyStruct{ MyStruct{Val: 1} } } func myfunc() []*MyStruct { return []MyStruct{ &MyStruct{Val: 1} } } func myfunc(s *[]MyStruct) { *s = []MyStruct{ MyStruct{Val: 1} } } func myfunc(s *[]*MyStruct) { *s = []MyStruct{ &MyStruct{Val: 1} } }

Una vez más: cuáles son las mejores prácticas aquí. Sé que los segmentos siempre son punteros, por lo que devolver un puntero a un sector no es útil. Sin embargo, ¿debo devolver una porción de valores de estructura, una porción de punteros a estructuras, debo pasar un puntero a una porción como argumento (un patrón utilizado en la API de Go App Engine )?


tl; dr :

  • Los métodos que usan punteros del receptor son comunes; la regla de oro para los receptores es : "En caso de duda, use un puntero".
  • Los segmentos, mapas, canales, cadenas, valores de función y valores de interfaz se implementan con punteros internamente, y un puntero a ellos es a menudo redundante.
  • En otros lugares, use punteros para estructuras grandes o estructuras que tendrá que cambiar, y de lo contrario pase valores , porque hacer que las cosas cambien por sorpresa a través de un puntero es confuso.

Un caso en el que a menudo deberías usar un puntero:

  • Los receptores son punteros más a menudo que otros argumentos. No es inusual que los métodos modifiquen lo que se llama, o que los tipos nombrados sean estructuras grandes, por lo que la guía es omitir los punteros, excepto en casos excepcionales.
    • La herramienta copyfighter Jeff Hodges busca automáticamente receptores no pequeños pasados ​​por valor.

Algunas situaciones donde no necesitas punteros:

  • Las pautas de revisión de código sugieren pasar pequeñas estructuras como el type Point struct { latitude, longitude float64 } , y quizás incluso cosas un poco más grandes, como valores, a menos que la función a la que está llamando necesite poder modificarlas en su lugar.

    • La semántica del valor evita las situaciones de alias en las que una asignación aquí cambia un valor allá por sorpresa.
    • No es Go-y sacrificar la semántica limpia por una pequeña velocidad, y algunas veces pasar estructuras pequeñas por valor es en realidad más eficiente, ya que evita las fallas de caché o las asignaciones de pila.
    • Entonces, la página de comentarios de revisión de código de Go Wiki sugiere pasar por valor cuando las estructuras son pequeñas y es probable que sigan siendo así.
    • Si el corte "grande" parece vago, es; Podría decirse que muchas estructuras están en un rango donde un puntero o un valor está bien. Como límite inferior, los comentarios de revisión de código sugieren que los segmentos (tres palabras de máquina) son razonables de usar como receptores de valor. Como algo más cercano a un límite superior, bytes.Replace . bytes.Replace toma el valor de 10 palabras de args (tres cortes y un int ).
  • Para los segmentos , no es necesario pasar un puntero para cambiar los elementos de la matriz. io.Reader.Read(p []byte) cambia los bytes de p , por ejemplo. Podría decirse que es un caso especial de "tratar pequeñas estructuras como valores", ya que internamente estás pasando una pequeña estructura llamada encabezado de un segmento (ver la explicación de Russ Cox (rsc) ). Del mismo modo, no necesita un puntero para modificar un mapa o comunicarse en un canal .

  • Para los segmentos que se volverá a cortar (cambiar el inicio / longitud / capacidad de), las funciones incorporadas, como el append aceptan un valor de sector y devuelven uno nuevo. Yo imitaría eso; evita el alias, devolver una nueva porción ayuda a llamar la atención sobre el hecho de que se puede asignar una nueva matriz, y es familiar para las personas que llaman.

    • No siempre es práctico seguir ese patrón. Algunas herramientas como las interfaces de base de datos o los serializers deben adjuntarse a un segmento cuyo tipo no se conoce en el momento de la compilación. A veces aceptan un puntero a un segmento en un parámetro de interface{} .
  • Los mapas, los canales, las cadenas y los valores de la función y la interfaz , como los segmentos, son referencias internas o estructuras que ya contienen referencias, por lo que si está intentando evitar que se copien los datos subyacentes, no necesita pasarles punteros. . (rsc escribió una publicación separada sobre cómo se almacenan los valores de la interfaz ).

    • Es posible que aún deba pasar punteros en el caso más raro en el que desee modificar la estructura de la persona que llama: flag.StringVar toma una *string por ese motivo, por ejemplo.

Donde usas punteros:

  • Considere si su función debe ser un método en cualquier estructura para la que necesite un puntero. La gente espera que muchos métodos en x modifiquen x , por lo que al hacer la estructura modificada, el receptor puede ayudar a minimizar la sorpresa. Hay guidelines sobre cuándo los receptores deberían ser punteros.

  • Las funciones que tienen efectos en sus parámetros de no receptor deberían dejarlo claro en el dios cd, o mejor aún, el cúpula y el nombre (como reader.WriteTo(writer) ).

  • Usted menciona aceptar un puntero para evitar asignaciones permitiendo la reutilización; cambiar las API por el bien de la reutilización de la memoria es una optimización que demoraría hasta que quede claro que las asignaciones tienen un costo no trivial, y luego buscaría una forma que no obligue a la API más complicada para todos los usuarios:

    1. Para evitar asignaciones, el análisis de escape de Go es tu amigo. A veces puede ayudarlo a evitar las asignaciones de almacenamiento dinámico haciendo tipos que se pueden inicializar con un constructor trivial, un literal llano o un valor cero útil como bytes.Buffer .
    2. Considere un método Reset() para volver a poner un objeto en un estado en blanco, como lo ofrecen algunos tipos de stdlib. Los usuarios a los que no les importa o no pueden guardar una asignación no tienen que llamarlo.
    3. Considere la posibilidad de escribir métodos de modificación en el lugar y funciones de creación desde cero como pares coincidentes, por comodidad: el existingUser.LoadFromJSON(json []byte) error podría incluirse en NewUserFromJSON(json []byte) (*User, error) . De nuevo, impulsa la elección entre la pereza y las asignaciones de pellizco para el llamante individual.
    4. Quienes llaman para reciclar la memoria pueden permitir que sync.Pool maneje algunos detalles. Si una asignación en particular genera mucha presión en la memoria, confía en saber cuándo la asignación ya no se usa y no tiene una mejor optimización disponible, sync.Pool puede ayudarlo. (CloudFlare publicó una sync.Pool blog útil (pre- sync.Pool ) sobre reciclaje).
    5. Curiosamente, para constructores complicados, new(Foo).Reset() veces puede evitar una asignación cuando NewFoo() no lo haría. No es idiomático; Cuidado con eso en casa.

Finalmente, en cuanto a si los segmentos deben ser de punteros: los segmentos de valores pueden ser útiles y le ahorrarán asignaciones y fallas de caché. Puede haber bloqueadores:

  • La API para crear tus elementos puede forzar los punteros sobre ti, por ejemplo, debes llamar a NewFoo() *Foo lugar de dejar que Go se inicialice con el valor cero .
  • Los tiempos de vida deseados de los artículos pueden no ser todos iguales. La rebanada entera se libera a la vez; Si el 99% de los elementos ya no son útiles pero tiene punteros al otro 1%, toda la matriz permanece asignada.
  • Mover objetos alrededor puede causarle problemas. Cabe destacar que append elementos cuando crece la matriz subyacente . Los punteros que recibió antes del punto de append en el lugar equivocado después, la copia puede ser más lenta para estructuras enormes y, por ejemplo, para la copia sync.Mutex no está permitida. Insertar / eliminar en el medio y ordenar de forma similar mover elementos alrededor.

En términos generales, los segmentos de valor pueden tener sentido si consigues todos los elementos en su lugar por adelantado y no los mueves (por ejemplo, no hay más append después de la configuración inicial), o si continúas moviéndolos, pero estás seguro eso está bien (no / uso cuidadoso de los punteros a los elementos, los elementos son lo suficientemente pequeños para copiar de manera eficiente, etc.). A veces tienes que pensar o medir los aspectos específicos de tu situación, pero eso es una guía aproximada.