Páginas

24 jun. 2011

Paso por valor y por referencia en C

No tengo intención de escribir tantas entradas en un mismo día, pero bueno acabo de empezar y las ganas de escribir me corroen.


En este caso más que escribir voy a pegar, ya dije que hablaría de más cosas y no solo de Debian o Linux en general. Esta vez voy a hablar del lenguaje de programación C. En un foro resolví una duda que tenía un compañero sobre la diferencia entre el paso por valor y el paso por referencia de los argumentos a una función así que voy a aprovechar (ya que para algo lo he escrito yo) y voy a hacer un copy&paste aquí.

Cuando una función contiene unos parámetros, estos, pueden pasarse a la función de dos formas: por valor y/o por referencia.

Cuando nosotros llamamos a una función, esta guarda sus variables en la RAM y al terminar de ejecutarse la función, todos los datos y variables que se cargaron en memoria se borran ya que no van a volver a ser utilizadas. 

Voy a poner un ejemplo fácil de una función que realiza la suma de dos números y devuelve el resultado de dicha suma:

CÓDIGO: 
int suma (int a, int b)
{
    int resultado;
    
    resultado = a + b;
    
    return resultado;
}

En el main para llamar a la función simplemente la llamaríamos pasándole a la función las variables a y b:


CÓDIGO:
int main ()
{
    int a=10, b=15, var;
    
    var = suma(a, b);
    
    return 0;
}

Ahí la variable "var" va a valer lo que devuelva la función suma (como le pasamos 10 y 15, var valdría 25).

Como he dicho la función suma reserva espacio en memoria para sus variables locales. En el ejemplo serían a, b y resultado. 

Como veis en la función main he llamado a la función suma pasándole a y b. Pero estas variables a y b de main NO son las mismas variables a y b de la función suma. Son una copia que están almacenadas en otra dirección de memoria. Es decir cuando llamamos a la función tendríamos dos variables "a" y otras dos variables "b" con el mismo valor en diferentes posiciones de memoria.

Si modificamos el valor de 'a' en la función suma, no cambia el valor de 'a' en la función principal puesto que son copias, no son la misma.

Esto es lo que sería el paso por valor. Ahora voy a intentar explicar el paso por referencia.

Ya os imaginaréis cual es la gracia del paso por referencia, que es ni más ni menos, que al llamar a la función no le pasemos una copia, si no que le pasemos la variable original en sí. Para ello lo que hacemos es pasarle la dirección de memoria de dicha variable, al pasarle su dirección estamos trabajando directamente sobre ella, por lo que cualquier cambio que hagamos en la función a esa variable también se verá cambiado en el main.

Voy a utilizar el mismo ejemplo de la suma:

CÓDIGO: 
void suma (int a, int b, int *resultado)
{
    *resultado = a + b;
}

La función suma la he cambiado a tipo void, antes la puse tipo int para que devolviera el valor de un entero con el resultado de la suma. Ahora no hace falta, simplemente le paso las variables que necesito. 

Las variables "a" y "b" las he vuelto a pasar como antes, por valor, ya que no las voy a modificar para nada. En cambio ahora he creado una variable de tipo int, que es un puntero, que es donde voy a guardar el resultado. Como he dicho antes, el paso por referencia consiste en pasarle la dirección de memoria de la variable a la función y sabemos que lo que trabaja con direcciones son los punteros.

La función main quedaría así:


CÓDIGO: 
int main ()
{
    int a=10, b=15, resultado=0;
    
    suma(a, b, &resultado);
    
    printf("%d", resultado);
    
    return 0;
}

Creamos nuestras variables igual que siempre, llamamos a la función y le pasamos la variable "a" y "b" por valor. La variable "resultado" es la que va a ser modificada en la función ya que almacenará el valor de la suma, para pasarlo por referencia necesitamos su dirección de memoria, por eso he puesto el "&" delante (recuerdo que el & delante del nombre de una variable nos da la dirección de memoria en la que se encuentra dicha variable).

Como podréis comprobar la función suma ahora es de tipo void y no devuelve nada, el cambio se queda reflejado en la variable que hemos pasado por referencia, ya que al no ser una copia sino que directamente trabajamos sobre dicha variable, vemos cuanto vale el valor de la suma en la función principal.
Y eso es todo, espero que os sirva a alguno de ayuda y si tenéis alguna duda o corrección de lo que escribo no dudéis en usar los comentarios para ello.

7 comentarios:

  1. Muchísimas gracias. Lo has explicado de una forma muy sencilla. Me está siendo muy útil. Gracias, gracias, gracias.

    ResponderEliminar
  2. buenisimo brother .... mas adelante tendre algunas preguntillas de programas en C amigo...

    ResponderEliminar
  3. Buenos días, tengo un problema con el pasaje de valor por referencia, o código es el siguiente:

    #include
    void incrementarHasta3(int *a);

    void main(){
    int x;
    int limit = 0;

    for(x=0; x<15; x++){
    incrementarHasta3(&limit);
    printf("\nfor -> limit: %d", limit);
    }

    getch();
    }

    void incrementarHasta3(int *a){

    printf("\nMenuPrincipal init -> limit: %d", *a);
    if (*a >= 3){
    printf("limit exceeded");
    }
    else{
    *a++;

    }

    printf("\nMenuPrincipal end-> limit: %d", *a);
    }

    el código da la siguiente salida:
    MenuPrincipal init -> limit: 0
    MenuPrincipal end-> limit: 0
    for -> limit: 0
    MenuPrincipal init -> limit: 0
    MenuPrincipal end-> limit: 1
    for -> limit: 0
    MenuPrincipal init -> limit: 0
    MenuPrincipal end-> limit: 2
    ...

    ...
    MenuPrincipal init -> limit: 0
    MenuPrincipal end-> limit: 13
    for -> limit: 0
    MenuPrincipal init -> limit: 0
    MenuPrincipal end-> limit: 14
    for -> limit: 0

    no debería listar hasta el 3?
    y porque na salida antes del if, no me muestra el valor que foi asignado?

    Gracias, ojala puedas explicar esta duda !!!

    ResponderEliminar
    Respuestas
    1. Hola,

      el fallo está en la línea del *a++. Tal como está, lo que se ejecuta es *(a++), es decir, estás incrementando el puntero. De esta manera, el valor de limit sigue siendo 0, lo que cuadra con lo que imprime en los prints de init y de for.
      Lo que imprime el print del end, en cambio, es el valor apuntado por el puntero a tras haber sido incrementado, que, de hecho corresponde a la variable x del main (por eso se va incrementando el valor imprimido).

      La solución es poner un paréntesis para forzar que primero se haga la desreferencia y luego el incremento, así, la linea del else quedaría como (*a)++;

      Eliminar
  4. una pregunta porque la función por referencia no se puede poner dentro del printf

    ResponderEliminar
  5. hola tengo una dudilla, si tenemos una 1ra acción llamada introduir dades que le paso una variable del tipo estructura por referencia jugadors_tipus *jugadors y tengo que pasar esta misma variable jugadors_tipus *jugadors en una acción anidada a introduir_dades nombrada taulell esto quedaria asi
    void introduir_dades (jugador_tipus *jugadors)
    {
    ...

    taulell (&jugadors)
    ....
    }
    el problema que me dice codebloks es que en taulell cuando paso jugadors por referencia no es correccto es decir supuestamente seria asi: jugador_tipus *jugadors pero me dice que lo interpreta como: jugador_tipus **jugadors.
    esto a que es debido, como se puede solucionar? me han comentado de cambiar en cada acción anidada el nombre al cual le llamo a la estructura jugador_tipus, pero quiero saber si hay alguna otra manera mas eficiente de hacerlo para que funcione.
    muchas gracias de antemano.

    ResponderEliminar