20 Funciones III: más cosas

Aún quedan algunas cosas interesantes por explicar sobre las funciones en C++.

Parámetros con valores por defecto

En algunas funciones suele suceder que para ciertos parámetros se repiten frecuentemente los mismos valores cada vez que necesitamos invocarlas.

Por ejemplo, podemos tener una función que calcule la velocidad final de un cuerpo en caída libre. Para hacer el cálculo necesitaremos algunos parámetros: velocidad inicial, altura...

h = vo·t + 1/2·g·t2
vf = vo + g·t
   donde:
   h es altura, o espacio recorrido
   g es la fuerza de la gravedad, o aceleración
   v0 es la velocidad inicial
   vf es la velocidad final y
   t es el tiempo

La fuerza de la gravedad también es un parámetro que hay que tener en cuenta, aunque podemos considerar que casi siempre nuestros cálculos se referirán a caídas que tendrán lugar en el planeta Tierra, por lo que podemos estar tentados de considerar la gravedad como una constante. Sin embargo, C++ nos permite tomar una solución intermedia. Podemos hacer que la fuerza de la gravedad sea uno de los parámetros de la función, y darle como valor por defecto el que tiene en la Tierra. De este modo, cuando no proporcionemos un valor para ese parámetro en una llamada, se tomará el valor por defecto, y en caso contrario, se usará el valor proporcionado.

Durante el programa, cuando se llama a la función incluyendo valores para esos parámetros opcionales, funcionará como cualquiera de las funciones que hemos usado hasta ahora. Pero si se omiten todos o algunos de estos parámetros la función trabajará con los valores por defecto para esos parámetros que hemos definido.

Por ejemplo:

#include <iostream>
#include <cmath>

using namespace std;
 
double VelocidadFinal(double h, double v0=0.0, double g=9.8);
 
int main() { 
   cout << "Velocidad final en caida libre desde 100 metros,\n" <<
      "partiendo del reposo en la Tierra" <<
      VelocidadFinal(100) << "m/s" << endl; 
   cout << "Velocidad final en caida libre desde 100 metros,\n" <<
      "con una velocidad inicial de 10m/s en la Tierra" <<
      VelocidadFinal(100, 10) << "m/s" << endl; 
   cout << "Velocidad final en caida libre desde 100 metros,\n" <<
      "partiendo del reposo en la Luna" <<
      VelocidadFinal(100, 0, 1.6) << "m/s" << endl; 
   cout << "Velocidad final en caida libre desde 100 metros,\n" <<
      "con una velocidad inical de 10m/s en la Luna" <<
      VelocidadFinal(100, 10, 1.6) << "m/s" << endl; 
   return 0;
}
 
double VelocidadFinal(double h, double v0, double g) {
   double t = (-v0+sqrt(v0*v0 + 2*g*h))/g;
   return v0 + g*t;
}

Nota: En este programa hemos usado la función sqrt. Se trata de una función ANSI C, que está declarada en el fichero de cabecera math, y que, evidentemente, sirve para calcular raíces cuadradas.

La salida de este programa será:

Velocidad final en caida libre desde 100 metros,
partiendo del reposo en la Tierra 44.2719 m/s
Velocidad final en caida libre desde 100 metros,
con una velocidad inicial de 10m/s en la Tierra 45.3872 m/s
Velocidad final en caida libre desde 100 metros,
partiendo del reposo en la Luna 17.8885 m/s
Velocidad final en caida libre desde 100 metros,
con una velocidad inical de 10m/s en la Luna 20.4939 m/s

La primera llamada a la función "VelocidadFinal" dará como salida la velocidad final para una caída libre desde 100 metros de altura. Como no hemos indicado ningún valor para la velocidad inicial, se tomará el valor por defecto de 0 m/s. Y como tampoco hemos indicado un valor para la gravedad, se tomará el valor por defecto, correspondiente a la fuerza de la gravedad en la Tierra.

En la segunda llamada hemos indicado explícitamente un valor para el segundo parámetro, dejando sin especificar el tercero. Como en el caso anterior, se tomará el valor por defecto para la fuerza de la gravedad, que es 9.8.

Este método tiene algunas limitaciones, por otra parte, muy lógicas:

  • Sólo los últimos argumentos de las funciones pueden tener valores por defecto.
  • De estos, sólo los últimos argumentos pueden ser omitidos en una llamada.
  • Cada uno de los valores por defecto debe especificarse bien en los prototipos, o bien en las declaraciones, pero no en ambos.

En la tercera y cuarta llamadas hemos tenido que especificar los tres parámetros, a pesar de que en la tercera el valor de la velocidad inicial es cero. Si sólo especificamos dos parámetros, el programa interpretará que el que falta es el último, y no el segundo. El compilador no puede adivinar qué parámetro queremos omitir, por eso es necesario aplicar reglas extrictas.

Cuando se declaren valores de parámetros por defecto en prototipos, no es necesario indicar el nombre de los parámetros.

Por ejemplo:

void funcion(int = 1); // Legal
void funcion1(int a, int b=0, int c=1); // Legal 
void funcion2(int a=1, int b, int c); // Ilegal
void funcion3(int, int, int=1); // Legal 
...
void funcion3(int a, int b=3, int c) // Legal
{
}

Los argumentos por defecto empiezan a asignarse empezando por el último.

int funcion1(int a, int b=0, int c=1); 
...
funcion1(12, 10); // Legal, el valor para "c" es 1 
funcion1(12); // Legal, los valores para "b" y "c" son 0 y 1 
funcion1(); // Ilegal, el valor para "a" es obligatorio

Comentarios de los usuarios (6)

Sara
2013-03-30 16:58:07

Hola, soy nueva programando en C++ y quisiera saber como hacer para que reciba la hora del día en formato HHMM e imprima en pantalla “Buenos días” si es antes de las 1200, “Buenas Tardes” desde las 1200 hasta las 1800 y “Buenas Noches” si es mayor a 1800. tengo dudas en que como hacer para que retorne una cadena de caracteres

Steven R. Davidson
2013-03-30 17:56:55

Hola Sara,

Para el tema de la hora, sugiero que uses las funciones estándares declaradas en el fichero de cabecera estándar, <ctime> ( http://c.conclase.net/librerias/?ansilib=time#inicio ). Por ejemplo,

time_t tiempo = time( NULL );
tm *pTiempoLocal = localtime( &tiempo );

char szHora[5]="";
strftime( szHora, 5, "%H%M", pTiempoLocal );

En cuanto a retornar cadenas de caracteres, tendrías que retornar el puntero, que es el array. Por ejemplo,

char * func()
{
  ...
  return szResultado;
}

El problema es que si 'szResultado' es local, entonces al terminar la función se destruirá y el puntero retornado es "basura". Podrías adjudicar memoria dinámicamente para crear un array dinámico y así puedes retornarlo. La desventaja es que fuera de la función tienes que liberarla cuando termines de usar la cadena.

La solución que recomiendo es que pases un array como parámetro a la función en el que coloques el resultado; también puedes retornarlo. Por ejemplo,

char * func( char *pszResultado )
{
  ...
  return pszResultado;
}

Con esto, nos aseguramos que el array existe fuera de la función, y si hace falta, podemos hacer algo con la cadena retornada.

Si sólo quieres retornar "Buenos días", "Buenas tardes", o "Buenas noches", entonces sugiero otra solución más simple. Como puedes ver son cadenas literales, por lo que tu programa realmente se basa en elegir cuál usar. La solución se basa en hacer una base de datos muy simple. Por ejemplo,

const char szSaludo[][14] = { "Buenos días", "Buenas tardes", "Buenas noches" };
...
int func()
{
  ...
  return i;
}

Reducimos el problema a uno de selección. Como la información - las cadenas - no se va a modificar, simplemente retornamos el índice a la cadena que nos interesa. Claro está, nada nos impide retornar un puntero, porque en este caso, la información existe externamente. Por ejemplo,

const char * func()

{

...

return szSaludo[i];

}

Espero que todo esto te oriente.

Steven

Adri
2015-01-02 04:22:16

Por lo que he entendido, entonces seria absurdo declarar una funcion como la que sigue:

int funcion(int = 1,int);

Porque si escribimos

funcion(3);

el compilador dará error esperando que indiques el segundo parametro, porque sobreentiende que el que has puesto se corresponde con el primero, por lo que seria una tonteria darle un valor por defecto al primero. ¿Me equivoco?

Adri
2015-01-02 04:25:29

He supuesto (como me imagino que habra hecho la mayoria) que para el caso que expuse antes podria contemplarse esta solucion:

funcion(,4);

Pero aunque no funcione, ¿Existe alguna manera de asignarle valores por defecto al primer parametro de una funcion cuando hay varios parametros?

Adri
2015-01-02 04:26:52

Perdon, creo que llamé parametros a lo que queria llamar argumentos.

Steven R. Davidson
2015-01-02 14:16:42

Hola Adri,

Aparte de absurdo es ilegal en C++. La razón, como bien describes, es por la ambigüedad. El compilador, como programa, no le gusta la ambigüedad y "prefiere" decisiones determinantes.

Una solución al problema que planteas es sobrecargando la función, cuyo tema se verá en el próximo capítulo 21. Te doy la solución para completar la respuesta:

int funcion( int );
int funcion( int, int );

Ahora tienes dos "versiones" de la función con el mismo nombre, pero con diferentes cantidades de parámetros. Ahora podemos ser más explícitos con el comportamiento de cada caso de la función.

Otra solución, es crear un parámetro inicial, que indique cuál parámetro de los dos que queremos aplicamos el valor por defecto. Por ejemplo,

int funcion( bool bPrimeroPorDefecto, int n  );  // true => el primero = 1; false => el segundo = 3
int funcion( int n1, int n2  );  // parámetros explícitos
...
funcion( true, 10 );  // primero = 1 (por defecto), segundo = 10
funcion( false, -4 );  // primero = -4, segundo = 3 (por defecto)
funcion( -4, 10 );

Aplicamos manual y explícitamente el comportamiento de valores por defecto, según el valor del booleano. Si no, entonces usamos la segunda versión de 'funcion()' pasando ambos parámetros.

También podríamos hacer algo parecido creando un tipo de dato personalizado con los datos que nos interesan; es decir, crea una 'struct' para representar los parámetros y unas marcaciones, usando 'bool' por ejemplo. Es más complicado, pero se puede hacer; por ejemplo, algo así,

struct parametros
{
  int n1, n2;
  bool b1, b2;  // true => aplicar valor por defecto;  false => usar valor en 'n1' o en 'n2', según la asociación con 'b1' o 'b2', respectivamente.
};

int funcion( parametros params );
...
parametros params = { 0,10, true, false };
funcion( params );  // n1 = 1 (por defecto);  n2 = 10  (explícito)

Típicamente, se busca otro diseño si hace falta solucionar este tipo de problemas.

En cuanto a esta terminología, no te preocupes mucho. Últimamente, ya no se hace mucho caso al tema de "parámetro" y "argumento". Hoy día se usan indistintamente, y se entiende por el contexto. Puedes decir "parámetro" tanto para un parámetro formal (definido en el prototipo) como para un parámetro verdadero (dado durante la invocación) o "argumento". Si es en un ambiente académico, entonces usa los términos "correctos".

Espero que esto te ayude.

Steven