Ir al paso de parámetros: ¿diferencia entre valor, referencia y puntero?

Cambiar parámetros


Suponga que define una función y modifica los parámetros en la función, para que la persona que llama pueda obtener su último valor modificado a través de los parámetros. Todavía uso la estructura de persona utilizada en el curso anterior como ejemplo, de la siguiente manera:

func main() {

   p:=person{name: "张三",age: 18}

   modifyPerson(p)

   fmt.Println("person name:",p.name,",age:",p.age)

}

func modifyPerson(p person)  {

   p.name = "李四"

   p.age = 20

}

type person struct {

   name string

   age int

}

En este ejemplo, espero cambiar el nombre en el parámetro p a Li Si y la edad a 20 a través de la función modificarPersona. El código no tiene errores, pero ejecútelo y verá la siguiente impresión:

person name: 张三 ,age: 18

¿Por qué sigue siendo Zhang San y 18? Intento reemplazarlo con un parámetro de puntero. Puedo modificar los datos del objeto puntiagudo a través del puntero, como se muestra a continuación:

modifyPerson(&p)

func modifyPerson(p *person)  {

   p.name = "李四"

   p.age = 20

}

Estos códigos se utilizan para satisfacer la modificación de los parámetros del puntero, para cambiar los parámetros recibidos a parámetros del puntero y para pasar un puntero a través del carácter de dirección & al llamar a la función modificarPersona. Ahora ejecute el programa nuevamente y debería ver el resultado esperado, como este:

person name: 李四 ,age: 20

tipo de valor


En la subsección anterior, definí una variable normal p de tipo persona. En el lenguaje Go, person es un tipo de valor y el puntero obtenido por &p es de tipo *person , que es un tipo de puntero. Entonces, ¿por qué no se pueden modificar los tipos de valor en el paso de parámetros? Esto también comienza con la memoria.

Ya sabemos que el valor de una variable se almacena en la memoria, y la memoria tiene un número llamado dirección de memoria . Entonces, si desea modificar los datos en la memoria, debe encontrar esta dirección de memoria . Ahora, permítanme comparar las direcciones de memoria de las variables de tipo de valor dentro y fuera de la función de la siguiente manera:

func main() {

   p:=person{name: "张三",age: 18}

   fmt.Printf("main函数:p的内存地址为%p\n",&p)

   modifyPerson(p)

   fmt.Println("person name:",p.name,",age:",p.age)

}

func modifyPerson(p person)  {

   fmt.Printf("modifyPerson函数:p的内存地址为%p\n",&p)

   p.name = "李四"

   p.age = 20

}

Entre ellos, cambié el código de muestra original para imprimir la dirección de memoria de la variable p en la función principal y la dirección de memoria del parámetro p en la función modificarPersona. Al ejecutar el programa anterior, puede ver los siguientes resultados:

main函数:p的内存地址为0xc0000a6020

modifyPerson函数:p的内存地址为0xc0000a6040

person name: 张三 ,age: 18

Encontrará que sus direcciones de memoria son diferentes, lo que significa que el parámetro p modificado en la función modificarPersona no es lo mismo que la variable p en la función principal, que también es el parámetro p modificado en la función modificarPersona, pero en la principal función Después de la impresión, se encontró que no había razón para la modificación.

El motivo de este resultado es  que los parámetros de función en el lenguaje Go se pasan por valor .  Pasar por valor se refiere a pasar una copia de los datos originales, no los datos originales en sí.

                              (La función principal llama a la función modificarPersona para pasar el diagrama de memoria de parámetros)

Tomando la función modificarPersona como ejemplo, al llamar a la función modificarPersona para pasar la variable p, el lenguaje Go copiará una p y la pondrá en una nueva memoria, de modo que la dirección de memoria de la nueva p sea diferente de la original , pero la nombre y edad dentro Es lo mismo, o Zhang San y 18. Este es el significado de copy , los datos en la variable son los mismos, pero la dirección de memoria almacenada es diferente.

Además de la estructura, hay flotantes, enteros, cadenas, booleanos, matrices, que son todos tipos de valores.

tipo de puntero


El valor almacenado por la variable de tipo puntero es la dirección de memoria correspondiente a los datos , por lo que bajo el principio de que la transferencia de parámetros de función es por valor, el valor copiado también es la dirección de memoria. Ahora modifique ligeramente el ejemplo anterior, el código modificado es el siguiente:

func main() {

   p:=person{name: "张三",age: 18}

   fmt.Printf("main函数:p的内存地址为%p\n",&p)

   modifyPerson(&p)

   fmt.Println("person name:",p.name,",age:",p.age)

}

func modifyPerson(p *person)  {

   fmt.Printf("modifyPerson函数:p的内存地址为%p\n",p)

   p.name = "李四"

   p.age = 20

}

Ejecute este ejemplo, encontrará que la dirección de memoria impresa es consistente y los datos se han modificado correctamente, como se muestra a continuación:

main函数:p的内存地址为0xc0000a6020

modifyPerson函数:p的内存地址为0xc0000a6020

person name: 李四 ,age: 20

Por lo tanto, los parámetros de tipo puntero siempre pueden modificar los datos originales, porque cuando se pasan los parámetros, se pasa la dirección de memoria.

Sugerencia: el valor pasado es un puntero, que también es una dirección de memoria. La memoria de los datos originales se puede encontrar a través de la dirección de memoria, por lo que modificarla equivale a modificar los datos originales.

tipo de referencia


Los siguientes son tipos de referencia, incluidos map y chan.

mapa

Para el ejemplo anterior, si no uso una estructura de persona personalizada y un puntero, ¿puedo usar el mapa para lograr el propósito de la modificación?

Déjame probarlo, de la siguiente manera:

func main() {

   m:=make(map[string]int)

   m["飞雪无情"] = 18

   fmt.Println("飞雪无情的年龄为",m["飞雪无情"])

   modifyMap(m)

   fmt.Println("飞雪无情的年龄为",m["飞雪无情"])

}

func modifyMap(p map[string]int)  {

   p["飞雪无情"] =20

}

Defino una variable m de tipo map[string]int, almaceno un par clave-valor cuya Clave es Feixueruheng y Valor es 18, y luego paso esta variable m a la función modificarMapa. Lo que hace la función modifyMap es modificar el valor correspondiente a 20. Ahora ejecute este código y vea si la modificación es exitosa imprimiendo la salida.El resultado es el siguiente:

飞雪无情的年龄为 18

飞雪无情的年龄为 20

La modificación fue efectivamente exitosa. ¿Tienes muchas dudas? No se utilizan punteros, pero se utilizan parámetros de tipo mapa. De acuerdo con el principio de transferencia de valores en el lenguaje Go, el mapa en la función modificarMapa es una copia. ¿Cómo puede tener éxito la modificación?

Para responder a esta pregunta, comencemos con make, una función integrada del lenguaje Go. En Go, cualquier código que crea un mapa (ya sea un literal o una función de creación) finalmente llama a la función runtime.makemap.

Sugerencia: Cree un mapa en forma de función literal o make y conviértalo en una llamada a la función makemap. Esta conversión la realiza automáticamente el compilador del lenguaje Go.

Como puede ver en el siguiente código, la función makemap devuelve un tipo *hmap, lo que significa que devuelve un puntero, por lo que el mapa que creamos es en realidad un *hmap.

// makemap implements Go map creation for make(map[k]v, hint).

func makemap(t *maptype, hint int, h *hmap) *hmap{

  //省略无关代码

}

Debido a que el tipo de mapa del lenguaje Go es esencialmente *hmap , de acuerdo con el principio de reemplazo, la función modificarMap(p mapa) que acabo de definir es en realidad modificarMap(p *hmap). ¿Es esto lo mismo que la llamada de parámetro de tipo puntero mencionada en la sección anterior? Esta es también la razón por la cual los datos originales se pueden modificar a través de un parámetro de tipo mapa, porque es esencialmente un puntero.

Para verificar aún más que el mapa creado es un puntero, modifiqué el ejemplo anterior para imprimir las direcciones de memoria correspondientes a las variables y parámetros del tipo de mapa, como se muestra en el siguiente código:

func main(){

  //省略其他没有修改的代码

  fmt.Printf("main函数:m的内存地址为%p\n",m)

}

func modifyMap(p map[string]int)  {

   fmt.Printf("modifyMap函数:p的内存地址为%p\n",p)

   //省略其他没有修改的代码

}

Las dos líneas de código de impresión en el ejemplo se agregaron recientemente y los otros códigos no se modificaron, por lo que no se publicarán aquí. Al ejecutar el programa modificado, puede ver el siguiente resultado:

飞雪无情的年龄为 18

main函数:m的内存地址为0xc000060180

modifyMap函数:p的内存地址为0xc000060180

飞雪无情的年龄为 20

Como puede ver en la salida, sus direcciones de memoria son exactamente las mismas, por lo que los datos originales se pueden modificar para obtener el resultado de que la edad es 20. Y cuando imprimo el puntero, uso las variables m y p directamente, y no uso el carácter de dirección &, porque son punteros, por lo que no es necesario usar & para tomar la dirección.

Así que aquí, el lenguaje Go nos salva de la manipulación del puntero al envolver la función make o el literal, permitiéndonos usar el mapa más fácilmente. De hecho, es azúcar sintáctico, que es una vieja tradición en el mundo de la programación.

Nota: El mapa aquí puede entenderse como un tipo de referencia, pero es esencialmente un puntero, simplemente puede llamarse tipo de referencia. Cuando se pasan parámetros, sigue siendo pass-by-value, no el llamado pass-by-reference en otros lenguajes de programación.

chan


¿Recuerdas el canal del que aprendimos en el módulo de concurrencia de Go? También se puede entender como un tipo de referencia, y es esencialmente un puntero.

Como puede ver en el código fuente a continuación, el chan creado es en realidad un *hchan, por lo que es lo mismo que el mapa en el paso de parámetros.

func makechan(t *chantype, size int64) *hchan {

    //省略无关代码

}

Estrictamente hablando, el lenguaje Go no tiene tipos de referencia , pero podemos llamar tipos de referencia map y chan, lo cual es fácil de entender. Además de map y chan, las funciones, interfaces y sectores en el lenguaje Go pueden llamarse tipos de referencia.

Sugerencia: el tipo de puntero también se puede entender como un tipo de referencia.

Supongo que te gusta

Origin blog.csdn.net/qq_34556414/article/details/123395653
Recomendado
Clasificación