15 Funciones II: Parámetros por valor y por referencia

Dediquemos algo más de tiempo a las funciones.

Hasta ahora siempre hemos declarado los parámetros de nuestras funciones del mismo modo. Sin embargo, éste no es el único modo que existe para pasar parámetros.

La forma en que hemos declarado y pasado los parámetros de las funciones hasta ahora es la que normalmente se conoce como "por valor". Esto quiere decir que cuando el control pasa a la función, los valores de los parámetros en la llamada se copian a "objetos" locales de la función, estos "objetos" son de hecho los propios parámetros.

Lo veremos mucho mejor con un ejemplo:

#include <iostream> 
using namespace std;
 
int funcion(int n, int m);
 
int main() { 
   int a, b; 
   a = 10; 
   b = 20;
 
   cout << "a,b ->" << a << ", " << b << endl; 
   cout << "funcion(a,b) ->" 
        << funcion(a, b) << endl;
   cout << "a,b ->" << a << ", " << b << endl; 
   cout << "funcion(10,20) ->" 
        << funcion(10, 20) << endl; 

   return 0; 
}
 
int funcion(int n, int m) { 
   n = n + 2; 
   m = m - 5; 
   return n+m; 
}

Bien, ¿qué es lo que pasa en este ejemplo?

Empezamos haciendo a = 10 y b = 20, después llamamos a la función "funcion" con las objetos a y b como parámetros. Dentro de "funcion" esos parámetros se llaman n y m, y sus valores son modificados. Sin embargo al retornar a main, a y b conservan sus valores originales. ¿Por qué?

La respuesta es que lo que pasamos no son los objetos a y b, sino que copiamos sus valores a los objetos n y m.

Piensa, por ejemplo, en lo que pasa cuando llamamos a la función con parámetros constantes, es lo que pasa en la segunda llamada a "funcion". Los valores de los parámetros no pueden cambiar al retornar de "funcion", ya que esos valores son constantes.

Si los parámetros por valor no funcionasen así, no sería posible llamar a una función con valores constantes o literales.

Referencias a objetos

Las referencias sirven para definir "alias" o nombres alternativos para un mismo objeto. Para ello se usa el operador de referencia (&).

Sintaxis:

<tipo> &<alias> = <objeto de referencia>
<tipo> &<alias>

La primera forma es la que se usa para declarar objetos que son referencias, la asignación es obligatoria ya que no pueden definirse referencias indeterminadas.

La segunda forma es la que se usa para definir parámetros por referencia en funciones, en estos casos, las asignaciones son implícitas.

Ejemplo:

#include <iostream>
using namespace std;
 
int main() { 
   int a; 
   int &r = a;
 
   a = 10; 
   cout << r << endl; 
   
   return 0; 
}

En este ejemplo los identificadores a y r se refieren al mismo objeto, cualquier cambio en una de ellos se produce en el otro, ya que son, de hecho, el mismo objeto.

El compilador mantiene una tabla en la que se hace corresponder una dirección de memoria para cada identificador de objeto. A cada nuevo objeto declarado se le reserva un espacio de memoria y se almacena su dirección. En el caso de las referencias, se omite ese paso, y se asigna la dirección de otro objeto que ya existía previamente.

De ese modo, podemos tener varios identificadores que hacen referencia al mismo objeto, pero sin usar punteros.

Pasando parámetros por referencia

Si queremos que los cambios realizados en los parámetros dentro de la función se conserven al retornar de la llamada, deberemos pasarlos por referencia. Esto se hace declarando los parámetros de la función como referencias a objetos. Por ejemplo:

#include <iostream>
using namespace std;
 
int funcion(int &n, int &m);
 
int main() { 
   int a, b;
 
   a = 10; b = 20; 
   cout << "a,b ->" << a << ", " << b << endl;
   cout << "funcion(a,b) ->" << funcion(a, b) << endl; 
   cout << "a,b ->" << a << ", " << b << endl; 
   /* cout << "funcion(10,20) ->" 
           << funcion(10, 20) << endl; // (1)
   es ilegal pasar constantes como parámetros cuando 
   estos son referencias */ 
   
   return 0; 
}
 
int funcion(int &n, int &m) {
   n = n + 2; 
   m = m - 5; 
   return n+m; 
}

En este caso, los objetos "a" y "b" tendrán valores distintos después de llamar a la función. Cualquier cambio de valor que realicemos en los parámetros dentro de la función, se hará también en los objetos referenciadas.

Esto quiere decir que no podremos llamar a la función con parámetros constantes, como se indica en (1), ya que aunque es posible definir referencias a constantes, en este ejemplo, la función tiene como parámetros referencias a objetos variables.

Y si bien es posible hacer un casting implícito de un objeto variable a uno constante, no es posible hacerlo en el sentido inverso. Un objeto constante no puede tratarse como objeto variable.

Comentarios de los usuarios (2)

Luis Fernandez
2014-12-03 13:55:38

Gracias por el curso, es el mejor

Tengo serios problemas con el paso de apuntadores a funciones, no en los simples sino en los complicados, y no encuentro la respuesta en el curso completo.

Tengo claro que cuando son variables simples se pasan por valor, si las quieres pasar por referencia haces lo de arriba, pero en cuento es un array de caracteres o dimensión todo cambia, pasa su dirección, pero no tengo claro que se usa en los parámetros de la función y que se usa para llamarla.

void funcion(int &a, char &b){
...
}
int main(){
   int c[100];
   char d[100];
   funcion (c,d);
}

Si estoy pasando la dirección en funcion (c,d); debería estar pasando por referencia, no se usan los [], ademas si son 2 dimensiones, si hay que usarlos?, ademas con valor solo en la segunda dimensión?, algo así función (c[][M],d[][G]);(si son de 2 dimeensiones

Luis Fernandez
2014-12-03 14:04:20

También tengo dudas cuando en la función usas el * para los parámetros en vez de &.

void funcion(int *a, char *b){
...
}
int main(){
   int c[100];
   char d[100];
   funcion (&c,&d);
}

acá estoy mandando la dirección de c y d

y la función recibe apuntadores, es equivalente?.

Otra pregunta, las estructuras se pasan por valor?, si es una dimensión de estructuras, pasa por referencia? varias dimensiones?, como se usan, es necesario ampliar el tema de las llamadas a funciones (como se las llama según coloques los parámetros) y sus equivalencias, y para los tipos de datos, estructuras, arrays de caracteres, dimensiones, y según lo que quieras hacer/pasar