Funciones
Las funciones se declaran y se definen exactamente igual que en C, y, al igual que en éste, se
puede utilizar prototipo (prototype).
Un prototipo es un modelo limitado de una entidad más completa que aparecerá
después. En el caso de funciones, la función es la entidad completa que vendrá
después, y la declaración de dicha función es el prototipo. El prototipo da un
modelo de la interface a la función. Veamos un ejemplo:
# include <iostream.h>
void do_stuff (int wings, float feet, char eyes);
main()
{
int arm = 2;
float foot = 1000.0;
char lookers = 2;
do_stuff (3, 12.0, 4);
do_stuff (arm, foot, lookers);
}
void do_stuff (int wings, float feet, char eyes);
{
cout << "There are " << wings << "wings."
<< '\n';
cout << "There are " << feet << "feet. "
<< '\n';
cout << "There are " << int(eyes) << "eyes."
<< '\n';
}
La salida de este programa será:
There are 3 wings.
There are 12 feet.
There are 4 eyes.
There are 2 wings.
There are 1000 feet.
There are 2 eyes.
Cada llamada a la función do_stuff() debe verificar:
- El número de parámetros debe ser exactamente tres.
- Los tipos deben ser compatibles con los de la declaración (más adelante
explicaremos qué son tipos compatibles).
Nótese que cuando llamamos a la función, la comprobación de tipo la hace el
compilador basándose en el prototipo (en la declaración) puesto que la función
todavía no ha sido definida.
Los nombres de variables que aparecen en el prototipo son opcionales y actúan casi como
comentarios al lector del programa, ya que son completamente ignorados por el compilador.
Tipos compatibles
Son compatibles cualquiera de los tipos simples (definidos en C++) que pueden ser convertidos de
uno a otro de manera significativa. Por ejemplo, si llamamos con un entero a una función que
está esperando un número real como parámetro, el sistema lo convertirá
automáticamente, sin mencionarlo al usuario. Esto también es cierto de float a char,
o de char a int.
En cambio, si pasamos un puntero a un entero a una función que estaba esperando un entero,
no habrá conversión de tipo, ya que son dos variables completamente distintas. De la
misma forma, un tipo definido por el usuario (estructura o clase) no puede ser convertido
automáticamente a un long float, a un array o incluso a otra estructura o clase diferente,
porque son tipos incompatibles y no puede realizarse la conversión de manera
significativa.
Sin embargo, el tipo devuelto por la función, void en el ejemplo anterior, debe ser
compatible con el tipo que se espera que devuelva en la función de llamada, o el compilador
dará un warning.
Pequeños cambios
Volvamos al ejemplo anterior. Veamos qué sucede cuando hacemos pequeños cambios:
- Llamamos a la función con
do_stuff (12.2, 13, 12345). No se produce
ningún error durante la compilación, pues estamos trabajando con tipos compatibles.
La salida del programa es:
There are 12 wings.
There are 13 feet.
There are 57 eyes.
There are 2 wings.
There are 1000 feet.
There are 2 eyes.
- Llamamos a la función con sólo dos parámetros:
do_stuff (12.2, 13). El compilador lanzará un mensaje de error:
11:error: In this statement, "do_stuff(1.22e1, 13)" supplies 2 arguments, but 3 are
expected.
- En la segunda llamada a la función, ponemos un & delante del nombre de una de las
variables,
do_stuff (arm, &float, lookers). El mensaje de error es el siguiente:
13:error:In this statement, "&lookers" is of type "pointer to char", and
may not be converted to "char".
- Cambiemos, en el prototipo, void por int,
int do_stuff(int wings, float feet,
char eyes);. Entonces, al compilar:
16:error:In this declaration, the type of "do_stuff" is not compatible with the
type of previous declaration of "do_stuff".
Si ahora modificamos además el tipo devuelto en la declaración de la función,
int do_stuff (int wings, float feet, char eyes) { ...}, obtenemos:
16:warning:Non-void function "do_stuff" does not contain a return statement.
- Podemos cambiar la declaración de la función por:
void do_stuff (int, float, char);
Esto no dará ninguna diferencia respecto al programa original. Esto demuestra que los
nombres de las variables en el prototipo son tratados como comentarios por el compilador de C++.
- La función podría haber sido declarada de la siguiente forma:
void do_stuff (int wings, //Number of wings
float feet,
//Number of feet
char eyes,
//Number of eyes
Esto hace que la cabecera de la función sea autoexplicatoria. Sin embargo, debe recordarse
que los comentarios nunca deben usarse en lugar de nombres significativos para las variables.
El uso de prototipos no supone coste alguno en tiempo ni en velocidad de ejecución. El
prototipo se verifica durante la compilación. Ralentiza ésta, debido a que es una
comprobación extra que el compilador debe hacer, pero es despreciable el tiempo que necesita.
El prototipo sólo alarga el tamaño del código.
Void como argumento de una función
Una función sin lista de argumentos como
void func ();
significa en C que no se ha declarado el tipo de la lista de argumentos que recibe la
función, por lo que el compilador no producirá errores respecto al uso impropio de
los argumentos. Cuando en C se declara una función que no tiene argumentos se utiliza el
tipo void:
void func (void);
En C++, ambas expresiones son equivalentes.
Funciones a las que se pasan punteros
Cuando se llama a una función, todos los parámetros con los
que la llamamos son copiados y pasados a la función. Esto significa
que si la función cambia el valor de los parámetros, sólo
lo hace dentro de la función. Por ejemplo:
#include <iostream.h>
void change_values(int a,int b)
{
a=4;
b=5;
}
main()
{
int a;
int b;
a=1;
b=2;
change_values(a,b);
cout << "A is " << a <<",B is"
<< b <<'\n';
}
La salida de programa es:
A is 1, B is 2
La llamada a la función no ha cambiado el valor de las variables que
se le han pasado. La función cambia las copias de lo que se le ha
pasado. Para cambiar los valores no debemos pasar datos a la función,
sino punteros a los datos. Para hacer esto, utilizamos el operador &, que
da la dirección de una variable:
#include <iostream.h>
void change_values(int *a,int *b)
{
*a=4;
*b=5;
}
main()
{
int a;
int b;
a=1;
b=2;
change_values(&a,&b);
cout << "A is " << a <<",B is"
<< b <<'\n';
}
Ahora la salida del programa es:
A is 4, B is 5
La función main pasa la dirección de a y b, por lo que a la
función change_values se le pasa una copia de las direcciones.
Utilizando las direcciones de a y b, la función puede acceder a los
datos directamente.