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 acceso 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; 
}

Comentarios de los usuarios (10)

José
2013-01-30 10:26:04

Tengo que admitir que no lo entiendo. Tal vez con otros ejemplos?

Steven R. Davidson
2013-01-30 19:07:59

Hola José,

No te preocupes si te resulta lioso el uso de estas macros. La idea es tratar la lista de parámetros como un array, pero de diferentes tipos de datos. Para lograr esto, obtenemos la dirección de memoria del último parámetro antes de esa lista desconocida de parámetros; o sea, antes de los puntos suspensivos. Con esta dirección de memoria podremos avanzar al primer parámetro desconocido, tratando el puntero a bytes. Obviamente, necesitamos realizar cástings al tipo correcto de cada parámetro (o elemento en esta lista). Básicamente, todo esto se realiza con estas macros.

Te pongo el primer ejemplo, sin usar las macros estándares:

void funcion( int a, ... )
{
  unsigned char *p = (unsigned char *)&a;  // Apuntamos a 'a'
  p += sizeof(a);  // Ahora apuntamos al primer byte del siguiente parámetro

  char **pp = (char **)p;  // Esa dirección de memoria en 'p' realmente es para un valor de tipo 'char *'

  while( *pp )
    cout << *pp++ << ' ';

  cout << endl; 
}

Como puedes ver, aquí sabemos los tipos de los parámetros posteriores (en memoria) al parámetro 'a', que son todos 'char *'. Por lo tanto, sólo tenemos que apuntar al segundo parámetro y continuar incrementando el puntero a 'char *'.

Para casos generales, usaríamos las macros estándares, porque si no sería demasiado engorroso, como puedes ver en el ejemplo anterior.

Espero que esto te ayude.

Steven

Adri
2015-01-02 04:40:25

¿Es cosa mia, o te sobran unos parentesis en el primer ejemplo donde pone...

while ((arg = va_arg(p, char*)))

Porque si hubieras puesto...

while (arg = va_arg(p, char*))

Habria sido igual, pero esos parentesis no se si sobran o no; el caso es que confunde bastante.

Steven R. Davidson
2015-01-02 14:29:15

Hola Adri,

Sí, tenemos una pareja de paréntesis de más. Creo que está porque alguien pudiese pensar incorrectamente que se nos olvidase el otro = y por tanto una comparación. Con otro par de paréntesis, parece que agrupamos esa asignación en una subexpresión.

Steven

Adri
2015-01-02 23:39:37

Entonces va_start(p,a) hace que va_arg(p,char*) se refiera al primer argumento de la

funcion y va_arg, cada vez que se usa, se refiere al siguiente argumento, ¿Es asi?.

Por otro lado, supongamos que me quiero referir al enésimo argumento de la funcion. ¿Tendria que usar va_arg(p,tipo) n veces?

Por cierto, cuando borro va_end(p) de tu ejemplo, no me da ninguna clase de error ni al compilar ni al ejecutar.

¿Por que?

Steven R. Davidson
2015-01-03 00:40:04

Hola Adri,

'va_start()' sirve para comenzar la lista, asignando el primer parámetro a ella. En la práctica, asignamos el primer parámetro al puntero que representa la lista de parámetros; o sea, algo así,

p = a;

'va_arg()' actualiza la lista para que apunte al siguiente parámetro, según su tipo de dato; o sea, hace algo así,

p += sizeof(char *);

Para el enésimo parámetro, tendrías que invocar 'va_arg()' (n-1) veces; con 'va_start()' ya empezamos por el primer parámetro. Eso sí, en cada invocación de 'va_arg()', tendrías que pasar el tipo de dato correcto.

No habrá un error o uno aparente, por la implementación de 'va_end()'. En algunas bibliotecas, esto puede suponer un problema del funcionamiento correcto interno de la lista de parámetros, mientras que en otras, no pasa nada "malo", porque 'va_end()' no hace nada importante. En general, siempre deberías invocar 'va_end()' debidamente, para asegurar que tu código fuente pueda funcionar correctamente bajo cualesquier compiladores existentes.

Puedes consultar la referencia al fichero de cabecera <cstdarg> yendo a nuestra página: http://c.conclase.net/librerias/?ansilib=stdarg#inicio

Espero haber aclarado las dudas.

Steven

Diego
2015-01-10 03:00:35

Hola, llegado a este punto me preguntaba como puedo crear funciones con descripción, o sea que tenga mas información y detalles de esta misma y de sus argumentos/parámetros. Se necesita crear una biblioteca para esto, o hay alguna forma de hacerlo?, gracias, me serviría muchísimo.

Diego
2015-01-10 03:00:38

Hola, llegado a este punto me preguntaba como puedo crear funciones con descripción, o sea que tenga mas información y detalles de esta misma y de sus argumentos/parámetros. Se necesita crear una biblioteca para esto, o hay alguna forma de hacerlo?, gracias, me serviría muchísimo.

Jesus Leyva
2015-03-08 06:13:10

Hola, gracias por el aporte al conocimiento.!. Tengo una inquietud con respecto al tipo de una variable, utilizando la cabecera <typeinfo> , como puedo saber el tipo de una variable.!?. Gracias.!

Steven R. Davidson
2015-03-18 17:07:33

Hola Jesús,

Con la definición estándar de 'typeinfo', puedes comparar tipos de datos. Por ejemplo,

tipo_de_dato var;
...
if( typeid(var) == typeid(int) )
  cout << "var es de tipo 'int'" << endl;
else if( typeid(var) == typeid(float) )
  cout << "var es de tipo 'float'" << endl;
else if( typeid(var) == typeid(void *) )
  cout << "var es de tipo 'void *'" << endl;
...

Consulta el capítulo 42 ( http://c.conclase.net/curso/?cap=042b#CAS_typeid ) acerca de este tema.

Espero haber aclarado la duda.

Steven