Funciones con número de argumentos variable
También es posible crear funciones con un número indeterminado de argumentos. Para ello declararemos los parámetros conocidos del modo normal, debe existir al menos un parámetro de este tipo. Los parámetros desconocidos se sustituyen por tres puntos (...), del siguiente modo:
<tipo_valor_retorno> <identificador>(<lista_parámetros_conocidos>, ...); |
Los parámetros se pasan usando la pila, (esto es siempre así con todos los parámetros, pero normalmente no tendremos que prestar atención a este hecho). Además es el programador el responsable de decidir el tipo de cada argumento, lo cual limita algo el uso de esta forma de pasar parámetros.
Para hacer más fácil la vida de los programadores, se incluyen algunas macros en el fichero de cabecera "cstdarg", estas macros permiten manejar más fácilmente las listas de argumentos desconocidos.
Tipos
En el fichero de cabecera "cstdarg" de define el tipo va_list:
va_list |
Será necesario declarar una variable de este tipo para tener a la lista de parámetros.
Macros
También se definen tres macros: va_start, va_arg y va_end.
void va_start(va_list ap, <ultimo>); |
Ajusta el valor de "ap" para que apunte al primer parámetro de la lista. <ultimo> es el identificador del último parámetro fijo antes de comenzar la lista de parámetros desconocidos.
<tipo> va_arg(va_list ap, <tipo>); |
Devuelve el siguiente valor de la lista de parámetros, "ap" debe ser la misma variable que se actualizó previamente con va_start, <tipo> es el tipo del parámetro que se tomará de la lista.
void va_end(va_list va); |
Permite a la función retornar normalmente, restaurando el estado de la pila, esto es necesario porque algunas de las macros anteriores pueden modificarla, haciendo que el programa termine anormalmente.
Leer la lista de parámetros
<tipo> funcion(<tipo> <id1> [, <tipo> <id2>...], ...)
{
va_list ar; // Declarar una variable para manejar la lista
va_start(ar, <idn>); // <idn> debe ser el nombre del último
// parámetro antes de ...
<tipo> <arg>; // <arg> es una variable para recoger
// un parámetro
while((<arg> = va_arg(ar, <tipo>)) != 0) {
// <tipo> debe ser el mismo que es de <arg>
// Manejar <arg>
}
va_end(ar); // Normalizar la pila
} |
Es necesario diseñar un sistema que permita determinar cuál es el último valor de la lista de parámetros, de modo que no queden parámetros por procesar o que no se procesen más de la cuenta.
Una forma es hacer que el último valor de la lista de parámetros en la llamada a la función sea un 0, (o de forma más general, un valor conocido).
También puede usarse uno de los parámetros conocidos para pasar a la función la cuenta de los parámetros desconocidos.
Además de esto, es necesario que el programador conozca el tipo de cada parámetro, para así poder leerlos adecuadamente. Una forma es que todos los parámetros sean del mismo tipo. Otra, que se use un mecanismo como el de la función "printf", donde analizando el primer parámetro se pueden deducir el tipo de todos los demás. Este último sistema tiene la ventaja de que también sirve para saber el número de parámetros.
Ejemplos:
#include <iostream>
#include <cstdarg>
using namespace std;
void funcion(int a, ...);
int main() {
funcion(1, "cadena 1", 0);
funcion(1, "cadena 1", "cadena 2", "cadena 3", 0);
funcion(1, 0);
return 0;
}
void funcion(int a, ...) {
va_list p;
va_start(p, a);
char *arg;
while ((arg = va_arg(p, char*))) {
cout << arg << " ";
}
va_end(p);
cout << endl;
} |
Otro Ejemplo, este usando un sistema análogo al de "printf":
#include <iostream>
#include <cstring>
#include <cstdarg>
using namespace std;
void funcion(char *formato, ...);
int main() {
funcion("ciic", "Hola", 12, 34, "Adios");
funcion("ccci", "Uno", "Dos", "Tres", 4);
funcion("i", 1);
return 0;
}
void funcion(char *formato, ...) {
va_list p;
char *szarg;
int iarg;
int i;
va_start(p, formato);
/* analizamos la cadena de formato para saber el número y
tipo de cada parámetro */
for(i = 0; i < strlen(formato); i++) {
switch(formato[i]) {
case 'c': /* Cadena de caracteres */
szarg = va_arg(p, char*);
cout << szarg << " ";
break;
case 'i': /* Entero */
iarg = va_arg(p, int);
cout << iarg << " ";
break;
}
}
va_end(p);
cout << endl;
} |
Argumentos de main
Muy a menudo necesitamos especificar valores u opciones a nuestros programas cuando los ejecutamos desde la línea de comandos.
Por ejemplo, si hacemos un programa que copie ficheros, del tipo del "copy" de MS-DOS, necesitaremos especificar el nombre del archivo de origen y el de destino.
Hasta ahora siempre hemos usado la función main sin parámetros, sin embargo, como veremos ahora, se pueden pasar argumentos a nuestros programas a través de los parámetros de la función main.
Para tener a los argumentos de la línea de comandos hay que declararlos en la función main, la manera de hacerlo puede ser una de las siguientes:
int main(int argc, char *argv[]);
int main(int argc, char **argv); |
Como sabemos que son equivalentes.
El primer parámetro, "argc" (argument counter), es el número de argumentos que se han especificado en la línea de comandos. El segundo, "argv", (argument values) es un array de cadenas que contiene los argumentos especificados en la línea de comandos.
Por ejemplo, si nuestro programa se llama "programa", y lo ejecutamos con la siguiente línea de comandos:
programa arg1 arg2 arg3 arg4 |
argc valdrá 5, ya que el nombre del programa también se cuenta como un argumento.
argv[] contendrá la siguiente lista: "C:programasrograma", "arg1", "arg2", "arg3" y "arg4".
Ejemplo:
#include <iostream>
using namespace std;
int main(int argc, char **argv) {
for(int i = 0; i < argc; i++)
cout << argv[i] << " ";
cout << endl;
return 0;
} |
Funciones inline
Cuando usamos el nombre de una función, indicando valores para sus argumentos, dentro de un programa, decimos que llamamos o invocamos a esa función. Esto quiere decir que el procesador guarda la dirección actual, "salta" a la dirección donde comienza el código de la función, la ejecuta, recupera la dirección guardada previamente, y retorna al punto desde el que fue llamada.
Esto es cierto para las funciones que hemos usado hasta ahora, pero hay un tipo especial de funciones que trabajan de otro modo. En lugar de existir una única copia de la función dentro del código, si se declara una función como inline, lo que se hace es insertar el código de la función, en el lugar (y cada vez) que sea llamada.
Sintaxis:
inline <tipo> <nombre_de_funcion>(<lista_de_parámetros>); |
Esto tiene la ventaja de que la ejecución es más rápida, pero por contra, el programa generado es más grande. Se debe evitar el uso de funciones inline cuando éstas son de gran tamaño, aunque con funciones pequeñas puede ser recomendable, ya que pueden producir programas más rápidos. Su uso es frecuente cuando las funciones tienen código en ensamblador, ya que en estos casos la optimización es mucho mayor.
En algunos casos, si la función es demasiado larga, el compilador puede decidir no insertar la función, sino simplemente llamarla. El uso de inline no es por lo tanto una obligación para el compilador, sino simplemente una recomendación.
Aparentemente, una función inline se comportará como cualquier otra función. De hecho, es incluso posible obtener un puntero a una función declarada inline.
Nota: inline es exclusivo de C++, y no está disponible en C. |
Ejemplos:
#include <iostream>
using namespace std;
inline int mayor(int a, int b) {
if(a > b) return a;
return b;
}
int main() {
cout << "El mayor de 12,32 es " << mayor(12,32) << endl;
cout << "El mayor de 6,21 es " << mayor(6,21) << endl;
cout << "El mayor de 14,34 es " << mayor(14,34) << endl;
return 0;
} |
Comentarios