domingo, 23 de enero de 2011

Cómo se usa el paso por valor y por referencia en C

Retomando el post anterior, os voy a explicar como se usan funciones en C.
Como ya dije en el post anterior, una función se compone de dos partes (cabecera y cuerpo) que más o menos expliqué en el post anterior, pero queda aclarar un "pequeño" detalle bastante importante.

  • Parámetros formales
Como ya explique, las funciones puedes recibir parámetros en el momento de la llamada. Estos parámetros son variables de algún tipo definido en C.
Hay dos formas de pasar parámetros a una función, bien pasando el valor de la variable o bien pasando la dirección de memoria de la misma. A estas formas de pasar parametros se las conoce como paso por valor y paso por referencia respectivamente.

  • Paso por valor
En el paso por valor se pasa directamente el valor con el que queremos trabajar, en este caso solo se tendrá acceso a dicho valor y en el caso de cambiarlo dentro de la función, al terminar esta y volver al punto del programa desde donde se invocó, la variable que se pasó como parámetro no se habrá modificado.

int suma (int a, int b){
a=4;
return a+b;
}

La función suma recibe dos parámetros de tipo int y devuelve la suma de ambos, que también es un dato de tipo int (se especifica al principio de la cabecera).

void main(){
int a=2;
int b=3;
printf("%d",a); -> ¿Cuánto vale a?
printf("%d", suma(a,b)); ->¿Cuál es el resultado devuelto por suma?
printf("%d",a); -> ¿Cuánto vale a?
}

En la función main se declaran dos variables enteras a y b con valores 2 y 3 respectivamente.
En el primer printf se imprime el valor de a que, como no ha sido modificada desde su inicialización, será 2.
En la siguiente linea se imprime el valor retornado por la función suma definida antes. Si analizamos la función suma veremos que el valor de a se ha cambiado, por lo que la función devolverá 4+3 (o lo que es lo mismo, 7).
En la última linea se muestra de nuevo el valor de a pero, ¿cuál es? Dentro de la función suma se modificó el valor y esto afectó a la operación pero, una vez que la función termina y volvemos al lugar de la llamada ese cambio deja de tener efecto sobre a ya que se le pasó a suma por valor, por lo que el valor de a sigue siendo el mismo que cuando se inicializó: 2.

  • Paso por referencia
En el paso por referencia lo que recibe la función ya no es un valor, sino una dirección de memoria.
Veamos el ejemplo anterior.

int suma (int *a, int *b){
*a=4;
return *a+*b;
}

Si os fijáis, hay una pequeña variación con respecto a la función suma anterior. Ahora los parámetros tienen un *. Esto significa que son punteros (se verán con mas detalle).
Al pasar punteros las modificaciones que se realicen dentro de la función permanecerán cuando esta termine y retorne al lugar de la llamada, porque ahora lo que se está modificando no es el valor, sino el contenido de la dirección de memoria donde apunta el puntero. Pero ¿qué pasa si se quita el *?

int suma (int *a, int *b){
a=4;
return a+b;
}

Si no pusiésemos el * lo que estamos haciendo es modificar las direcciones de memoria de las variables, lo que producirá resultados inesperados. El * nos indica que accedemos al contenido de la dirección de memoria, es decir, al valor de la variable. Hay que tener mucho cuidado con esto.

void main(){
int a=2;
int b=3;
printf("%d",a); -> ¿Cuánto vale a?
printf("%d", suma(&a,&b)); ->¿Cuál es el resultado devuelto por suma?
printf("%d",a); -> ¿Cuánto vale a?
}

En la llamada a la función también hay una pequeña variación. Hay que incluir un & antes de los parámetros pasados por referencia. Al poner el & lo que estamos haciendo es pasar la dirección de memoria de la variable. De este modo la salida por pantalla será la siguiente:
En el primer printf se mostrará un 2, que es el valor de inicialización de a.
En el segundo printf se mostrará el valor retornado por la función suma: 7
El ultimo printf mostrará de nuevo el valor de a pero, en este caso, el valor habrá cambiado puesto que en la función se modificó, y al pasar a por referencia, el cambio se hace permanente, por lo tanto esa linea imprimirá un 4.

NOTA: Los arrays siempre se pasan por referencia por lo que no hay que especificarlo poniendo * y & (más adelante se explicará con más detalle).