Disponible la nueva versión "donationware" 7.3 de OrganiZATOR
Descubre un nuevo concepto en el manejo de la información.
La mejor ayuda para sobrevivir en la moderna jungla de datos la tienes aquí.

Curso C++

[Home]  [Inicio]  [Índice]


4.4.1 Declaración de funciones

§1 Sinopsis

La declaración ( 4.1.2) da a conocer la función al compilador, de forma que a partir del punto de declaración, ya se pueden realizar invocaciones a la misma. A su vez, la definición estará en algún otro punto del programa, tal vez en una librería externa (en forma ya compilada) o en otro módulo de programa (como texto fuente).

Una función puede ser declarada varias veces en un mismo programa, y las declaraciones pueden aparecer en cualquier orden; en un fichero fuente o en varios, pero en cualquier caso antes de su uso, es decir: antes de cualquier invocación a la función [5]. Además de declarar el nombre de la función y el tipo devuelto (por defecto se supone int ) se declaran también el tipo de los parámetros.

§2 Sintaxis:

[extern] <tipo-devuelto> nombre-funcion ()                         // §2a
[extern] <tipo-devuelto> nombre-funcion (<tipo>, ...)              // §2b
[extern] <tipo-devuelto> nombre-funcion (<tipo> <parametro>, ... ) // §2c

§2.1 Ejemplos:

extern int funcion1 ();        // no acepta ningún argumento
extern int funcion1 (void);    // mejor que la anterior
funcion2 (char, int);          // por defecto supone que devuelve int
int funcion2 (char, int);      // mejor que la anterior
char funcion3 (char c, int i); // incluye nombres de parámetros

§2.2 Comentario

  El especificador <tipo-devuelto> es opcional. Por defecto se supone int, así que las declaraciones que siguen son equivalentes [4]:

int func (<tipo> <parámetro>, ...)
func (<tipo> <parámetro>, ...)

Nota: los compiladores MS Visual C++ y Borland C++ admiten que ciertos especificadores opcionales acompañen a la declaración de funciones y otros objetos. Tales especificadores son de utilidad en circunstancias específicas ( 4.4.1b).

§3 Declaraciones

Recuerde que las declaraciones deben realizarse antes que cualquier uso de la función. A su vez, las definiciones pueden estar en cualquier sitio, aunque en algunos casos puede haber excepciones (sustitución inline 4.4.6b).

Las declaraciones de funciones tienen un nombre específico: se denominan prototipo . El primero de los anteriores (§2a ) es válido, aunque desaconsejado (herencia del C); es el denominado estilo clásico Kernighan & Ritchie. El segundo (§2b ) y tercero (§2c ), son los aceptados en C++ [1].

Nota: es importante resaltar que en la declaración de parámetros no está permitido incluir funciones, es decir, las funciones no pueden pasar como argumentos a otras funciones [6]. Sin embargo, C++ dispone de recursos cuando esto es necesario; pasar un puntero ( 4.2.4) o una referencia ( 4.2.3) a la función.

El viejo estilo K&R tiene la desventaja de no permitir al compilador comprobar el número y tipo de los argumentos utilizados en las llamadas a la función. Este problema fue eliminado con la introducción de los prototipos que utilizan la forma completa [2], en la que se especifica el número y tipo de cada argumento aceptado por la función. El compilador usa estos datos para comprobar la validez de las llamadas a la función y como se ilustra en el ejemplo, es capaz en su caso, de realizar dentro de ciertos límites, un modelado de tipo ("Casting") de los argumentos para garantizar que coinciden con el tipo esperado.

Nota: el mecanismo anterior permite al compilador efectuar una comprobación de tipos de los argumentos que pasan y del valor devuelto. Los lenguajes en los que no se realizan estas comprobaciones, se denominan de débilmente tipados ("Weakly typed"), tienen la desventaja de que no conocen exactamente el tipo de código que ejecutarán.


Supongamos que se tiene el siguiente código:

extern long lmax(long v1, long v2);  // prototipo
funcion() {
  int limit = 32;
  char ch = 'A';
  long mval;
  mval = lmax(limit, ch);     // Llamada a la funcion
}

Puesto que se dispone de un prototipo para la función lmax, este programa convierte los parámetros limit y ch a long (utilizando las reglas estándar de asignación) antes de colocarlos en la pila para la llamada a lmax.  Si no hubiese existido el prototipo, limit y ch hubieran sido puestos en la pila como entero y carácter respectivamente, en cuyo caso, los valores pasados a limit no hubieran coincidido con el tamaño y/o contenido esperados por esta, originando problemas ( 4.4.6 Llamadas a funciones y conversión de argumentos).


§3.1 La inclusión del especificador opcional extern, sirve para indicar al compilador que la definición de la función se encuentra en otro fichero distinto del actual. En caso contrario dicha definición debe encontrarse en algún lugar del fichero (si es que existen llamadas a dicha función). Es decir, si aparece el siguiente trozo de código:

int alfa (int deg, int min, int sec); // declaración de alfa
...
int gr;
gr = alfa( x, y, z);                  // uso de alfa

la definición de alfa debe estar en algún sitio del fichero que contiene dichas instrucciones. En caso contrario, el compilador devolverá un error: Unresolved external 'alfa()' referenced from ....OBJ.

  Es importante recordar que las funciones tienen ámbito global, y que sus declaraciones (prototipos) aunque suelen estar al principio (inmediatamente después de las directivas de preprocesado), pueden aparecer en cualquier parte del fichero. Ponerlas al principio tiene la ventaja de que sus nombres sean conocidos en la totalidad del fichero, con lo que pueden ser invocadas desde cualquier punto -desde cualquier otra función, incluso main- sin tener que declarar un prototipo dentro de cada función que las invoque.

§4 Prototipos de funciones

Los prototipos juegan un rol importante en la programación C++ y sirven también para clarificar y documentar el código. Sobre todo si los nombres de las variables son significativos. Por ejemplo, la función strcpy tiene dos parámetros: una cadena fuente y una destino, la cuestión es: ¿Cual es cual?

char *strcpy(char* dest, const char* source);


Si se incluye un identificador en el parámetro de un prototipo, solo es utilizado para los posibles mensajes de error relativos a tal parámetro sin ningún otro efecto. De hecho, los identificadores (nombres) de los parámetros suelen ser largos y descriptivos en los prototipos, mientras que en las definiciones suelen ser abreviados, sin que, en este aspecto, tengan que haber coincidencia entre ambos (ver reglas de ámbito para los identificadores de parámetros en prototipos de funciones 4.1.3). Como puede deducirse de estas palabras, esto significa que en realidad los nombres de los argumentos no son imprescindibles en los prototipos; solo son necesarios los tipos de los parámetros. Es decir, el prototipo de la función anterior podría perfectamente ser sustituido por:

char *strcpy(char*, const char*);


Un declarador de función con la palabra void entre paréntesis: func(void);, indica que la función no acepta ningún parámetro. Es equivalente a la expresión como func();, que también declara una función sin parámetros.

§4.1 Prototipos y ficheros de cabecera:

Es costumbre que los prototipos de las funciones incluidas en las librerías del lenguaje se agrupen en ficheros específicos, los denominados ficheros de cabecera ("header files"), que son ficheros de texto (en realidad ficheros fuente 1.4) en los que se agrupan todas las declaraciones utilizadas en la librería.

Nota: en realidad, los ficheros de cabecera no solo incluyen los prototipos y declaraciones de las funciones, también las declaraciones de las estructuras, macros ( 4.9.10b) y clases ( 4.11.2a) utilizadas.


Por otra parte, también es frecuente que los programadores C++ construyan sus propias librerías que acompañan a las que vienen preconstruidas en el lenguaje. Para ello se agrupan en ciertos módulos aquellas funciones o clases más frecuentemente utilizadas. Estos módulos son compilados y enlazados de una forma especial de forma que no se obtiene un ejecutable, sino una librería de las que existen varios tipos ( 1.4.4a). En cualquier caso, sean librerías preconstruidas en el lenguaje o de fabricación propia, los prototipos de las funciones incluidas en ellas se agrupan en ficheros de cabecera. Las que vienen con el lenguaje se localizan en el directorio \Include; las de fabricación propia se deben mantener en otro directorio distinto del anterior.

Puesto que es imprescindible incluir en cada fichero fuente la declaración de cada función antes de que pueda ser utilizada , el hecho de tener agrupadas las declaraciones en un fichero de cabecera es de gran utilidad, porque solo es preciso incluir una directiva include ( 4.9.10g) al principio de cada fuente para tener la seguridad de que todos los prototipos estarán presentes. De otro modo tendría que escribirlos manualmente en cada fuente que utilizara funciones de la librería.

En la documentación que acompaña a toda librería se indica siempre, junto con un ejemplo de uso, el nombre del fichero de cabecera que contiene los prototipos de las funciones utilizadas en la librería. Sin embargo, en ocasiones, cuando no se tiene a mano un buen manual de uso, o existe la sospecha de una errata en el mismo, puede ser útil echar un vistazo al fichero de cabecera en que se incluye el prototipo de la función, ya que contiene bastante información sobre su uso: tipo y número de parámetros, valor devuelto, etc.

Nota: se ha llegado a afirmar que los ficheros de cabecera contienen toda la información necesaria para usar las librerías de cualquier tipo. Aunque desde luego pueden ser de gran ayuda, la afirmación es un poco exagerada. En lo que respecta a las funciones, los prototipos contienen en realidad toda la "gramática" de su invocación, pero poco o nada sobre la "funcionalidad".

§5 Número variable de argumentos

Normalmente los prototipos de funciones declaran un número fijo de parámetros (que puede ser ninguno). Para las funciones que pueden aceptar un número variable de parámetros (tales como printf), el prototipo puede terminar en puntos suspensivos (...). Esta elipsis indica que la función puede ser invocada con diferentes tipos de argumentos en diferentes ocasiones. Los puntos pueden colocarse al final de una sublista de parámetros conocidos. Por ejemplo:

func(int *count, long total, ...);

Por supuesto esta forma de prototipo reduce la comprobación que puede efectuar el compilador; los parámetros fijos son comprobados en tiempo de compilación, y los variables son pasados sin comprobación [3].

§6 Ejemplos

A continuación algunos ejemplos adicionales de prototipos y declaraciones de funciones. Obsérvese que, para mayor concisión, en todas ellas se han omitido los nombres de los parámetros formales (que como hemos indicado, son opcionales en los prototipos):

f();                  /* En C, sin datos sobre los parametros, el estilo clásico K&R. Devuelve int */
f();                  // En C++, f no recibe argumentos. Devuelve int.
int f(void);          // f devuelve int, no recibe argumentos.
int p(int,long);      /* p devuelve int, acepta dos parámetros: El primero un int, el segundo un long */
int __pascal q(void); /* q función Pascal, devuelve int, no recibe parámetro */
int printf(char *format,...); /* Devuelve int; un parámetro fijo, puntero a carácter, después cualquier número de argumentos adicionales de tipo desconocido */
char* f(int)         // función que devuelve puntero a carácter, acepta un int.
int* f(int)          // función que devuelve puntero a int, acepta int.
struct str f(int)    // función que devuelve estructura str acepta un int.
int (*f (int))(int); /* función que acepta un entero y devuelve un puntero a función que acepta un int y devuelve un entero */
int (*(*f())[10])(); /* función que no recibe argumentos, devuelve un puntero a un array de 10 punteros a función que devuelven enteros */
int f(struct S* Sptr);  /* función que recibe como argumento un puntero a una estructura y devuelve int */
int (B::* getptr())();  /* función que no recibe argumentos, devuelve un puntero a función miembro de la clase B que no recibe argumentos y devuelve un int */

§7 Polimorfismo

Aparte de estas tareas de comprobación y modelado de tipos, en realidad el objetivo principal de incluir en la declaración de funciones una descripción detallada del valor devuelto y de los parámetros aceptados, es permitir lo que se llama sobrecarga (de funciones). Esto significa que dentro del mismo ámbito puedan definirse varias funciones con nombres idénticos, pero distintos parámetros y por supuesto distintas definiciones. Más tarde, el compilador será capaz de saber a cual de ellas nos estamos refiriendo, precisamente analizando los parámetros que pasamos a la función. Por ejemplo, en C++ está permitido el siguiente trozo de código:

int alfa (int deg, int min, int sec); // declaracion-1 de alfa
void alfa (int deg);                  // declaracion-2 de alfa
int alfa (char n);                    // declaracion-3 de alfa
  ...
n = alfa('c');                        // invocación de alfa-3

el compilador conoce que, en este caso, la invocación se refiere a la tercera declaración de la función alfa, precisamente por el argumento utilizado.

Nota: las funciones main, WinMain y LibMain no pueden ser sobrecargadas. Ejemplo:

extern "C" void WinMain(int, char*, char*);
void WinMain(int, short, char*, char*);       // Error!!

Como veremos inmediatamente ( 4.4.1a), para saber que definición ajusta mejor con los argumentos actuales, el compilador sigue una serie de reglas denominadas de congruencia estándar de argumentos.

§7.1 Temas relacionados:
  • Sobrecarga de funciones ( 4.4.1a)
  • Polimorfismo de funciones-miembro ( 4.11.2a2).
  • Sobrecarga de funciones genéricas (plantillas) 4.12.1-2

  Inicio.


[1]  En C++ deben usarse siempre prototipos completos y se recomienda hacerlo también en C. Es posible instruir al compilador para que, en caso necesario, incluya un mensaje de advertencia: Function called without a prototype.

[2]  Esta fue una de las mayores novedades del estándar ANSI C respecto a las primitivas versiones de K&R. Aunque el viejo estilo todavía es permitido en el estándar, es desaconsejado por razones de compatibilidad.

[3]  Las cabeceras stdarg.h y varargs.h contienen macros que permiten utilizar funciones con número variable de argumentos.

[4] Este tipo de asunciones (suponer un int implícito cuando hace falta una declaración de tipo y se omite), es una larga tradición C. Por su parte C++ las permite, pero como pretende ser más estricto en la cuestión de los tipos, tiende a eliminarlas, pues se trata de malas prácticas que conviene erradicar. Por esta razón, tal permisividad puede no ser válida en determinadas circunstancias y con determinados compiladores.

[5] Recuerde que pueden existir múltiples declaraciones, pero solo una definición de cada función.

[6] Existe un comentario más amplio sobre este punto en la nota del capítulo "Declaración y definición de punteros a función" ( 4.2.4a)