En matemáticas y en programación informática , una función variádica es una función de aridad indefinida , es decir, una que acepta un número variable de argumentos . El soporte para funciones variádicas difiere ampliamente entre los lenguajes de programación .
El término variádico es un neologismo que data de 1936-1937. [1] El término no se usó ampliamente hasta la década de 1970.
Existen muchas operaciones matemáticas y lógicas que se presentan de manera natural como funciones variádicas. Por ejemplo, la suma de números o la concatenación de cadenas u otras secuencias son operaciones que pueden considerarse aplicables a cualquier número de operandos (aunque formalmente en estos casos se aplica la propiedad asociativa ).
Otra operación que se ha implementado como función variádica en muchos lenguajes es el formato de salida. La función Cprintf
y la función Common Lispformat
son dos ejemplos de ello. Ambas toman un argumento que especifica el formato de la salida y cualquier número de argumentos que proporcionan los valores que se van a formatear.
Las funciones variádicas pueden exponer problemas de seguridad de tipos en algunos lenguajes. Por ejemplo, las funciones variádicas de C printf
, si se usan sin precaución, pueden dar lugar a una clase de agujeros de seguridad conocidos como ataques de cadenas de formato . El ataque es posible porque el soporte del lenguaje para las funciones variádicas no es seguro en cuanto a tipos: permite que la función intente sacar de la pila más argumentos de los que se colocaron allí, corrompiendo la pila y dando lugar a un comportamiento inesperado. Como consecuencia de esto, el Centro de Coordinación CERT considera que las funciones variádicas en C son un riesgo de seguridad de alta gravedad. [2]
En los lenguajes de programación funcional , las funciones variádicas pueden considerarse complementarias a la función apply , que toma una función y una lista/secuencia/matriz como argumentos, y llama a la función con los argumentos suministrados en esa lista, pasando así un número variable de argumentos a la función. [ cita requerida ] En el lenguaje funcional Haskell , las funciones variádicas pueden implementarse devolviendo un valor de un tipo class T
; si las instancias de T
son un valor de retorno final r
y una función (T t) => x -> t
, esto permite cualquier número de argumentos adicionales x
. [ se necesita más explicación ]
Un tema relacionado en la investigación de reescritura de términos se llama coberturas o variables de cobertura . [3] A diferencia de los variádicos, que son funciones con argumentos, las coberturas son secuencias de argumentos en sí mismas. También pueden tener restricciones ('no tome más de 4 argumentos', por ejemplo) hasta el punto en que no son de longitud variable (como 'tome exactamente 4 argumentos'), por lo que llamarlos variádicos puede ser engañoso. Sin embargo, se refieren al mismo fenómeno y, a veces, la redacción es mixta, lo que resulta en nombres como variable variádica (sinónimo de cobertura). Tenga en cuenta el doble significado de la palabra variable y la diferencia entre argumentos y variables en programación funcional y reescritura de términos. Por ejemplo, un término (función) puede tener tres variables, una de ellas una cobertura, lo que permite que el término tome tres o más argumentos (o dos o más si se permite que la cobertura esté vacía).
Para implementar de forma portátil funciones variádicas en el lenguaje Cstdarg.h
, se utiliza el archivo de encabezado estándar . El varargs.h
encabezado anterior ha quedado obsoleto en favor de stdarg.h
. En C++, cstdarg
se utiliza el archivo de encabezado. [4]
#incluir <stdarg.h> #incluir <stdio.h> doble promedio ( int recuento , ...) { va_list ap ; int j ; doble suma = 0 ; va_start ( ap , count ); /* Antes de C23: Requiere el último parámetro fijo (para obtener la dirección) */ for ( j = 0 ; j < count ; j ++ ) { sum += va_arg ( ap , int ); /* Incrementa ap hasta el siguiente argumento. */ } va_end ( ap ); devolver suma / recuento ; } int main ( int argc , char const * argv []) { printf ( "%f \n " , promedio ( 3 , 1 , 2 , 3 )); devolver 0 ; }
Esto calculará el promedio de un número arbitrario de argumentos. Tenga en cuenta que la función no conoce el número de argumentos ni sus tipos. La función anterior espera que los tipos sean int
, y que el número de argumentos se pase en el primer argumento (este es un uso frecuente pero de ninguna manera impuesto por el lenguaje o el compilador). En algunos otros casos, por ejemplo printf , el número y los tipos de argumentos se calculan a partir de una cadena de formato. En ambos casos, esto depende del programador para proporcionar la información correcta. (Alternativamente, se puede usar un valor centinela como NULL
o nullptr
para indicar el final de la lista de parámetros). Si se pasan menos argumentos de los que cree la función, o los tipos de argumentos son incorrectos, esto podría hacer que lea en áreas no válidas de la memoria y puede conducir a vulnerabilidades como el ataque de cadena de formato . Dependiendo del sistema, incluso el uso NULL
como centinela puede encontrar tales problemas; nullptr
o se puede usar un puntero nulo dedicado del tipo de destino correcto para evitarlos.
stdarg.h
declara un tipo, va_list
, y define cuatro macros: va_start
, va_arg
, va_copy
, y va_end
. Cada invocación de va_start
y va_copy
debe coincidir con una invocación correspondiente de va_end
. Cuando se trabaja con argumentos variables, una función normalmente declara una variable de tipo va_list
( ap
en el ejemplo) que será manipulada por las macros.
va_start
toma dos argumentos, un va_list
objeto y una referencia al último parámetro de la función (el que está antes de los puntos suspensivos; la macro usa esto para orientarse). En C23 , el segundo argumento ya no será necesario y las funciones variádicas ya no necesitarán un parámetro nombrado antes de los puntos suspensivos. [nota 1] [6] Inicializa el va_list
objeto para que lo use va_arg
or va_copy
. El compilador normalmente emitirá una advertencia si la referencia es incorrecta (por ejemplo, una referencia a un parámetro diferente al último, o una referencia a un objeto completamente diferente), pero no impedirá que la compilación se complete normalmente.va_arg
toma dos argumentos, un va_list
objeto (inicializado previamente) y un descriptor de tipo. Se expande al siguiente argumento variable y tiene el tipo especificado. Las invocaciones sucesivas de va_arg
permiten procesar cada uno de los argumentos variables por turno. Se produce un comportamiento no especificado si el tipo es incorrecto o no hay un siguiente argumento variable.va_end
toma un argumento, un va_list
objeto. Sirve para limpiar. Si uno quisiera, por ejemplo, escanear los argumentos de la variable más de una vez, el programador reinicializaría su va_list
objeto invocando va_end
y luego va_start
nuevamente en él.va_copy
toma dos argumentos, ambos va_list
objetos. Clona el segundo (que debe haberse inicializado) en el primero. Volviendo al ejemplo de "escanear los argumentos de la variable más de una vez", esto se podría lograr invocando va_start
en un primero va_list
y luego usando va_copy
para clonarlo en un segundo va_list
. Después de escanear los argumentos de la variable una primera vez con va_arg
y el primero va_list
(descartándolo con va_end
), el programador podría escanear los argumentos de la variable una segunda vez con va_arg
y el segundo va_list
. va_end
también debe llamarse en el clonado va_list
antes de que la función que lo contiene regrese.C# describe las funciones variádicas mediante la params
palabra clave. Se debe proporcionar un tipo para los argumentos, aunque object[]
se puede utilizar como un comodín. En el sitio de llamada, puede enumerar los argumentos uno por uno o entregar una matriz preexistente que tenga el tipo de elemento requerido. El uso de la forma variádica es una sintaxis simplificada para esto último.
usando Sistema ; clase Program { static int Foo ( int a , int b , params int [ ] args ) { // Devuelve la suma de los números enteros en args , ignorando a y b . int suma = 0 ; foreach ( int i in args ) suma + = i ; return suma ; } static void Main ( string [ ] args ) { Console.WriteLine ( Foo ( 1,2 ) ) ; // 0 Console.WriteLine ( Foo ( 1,2,3,10,20 ) ) ; // 33 int [ ] manyValues = new int [ ] { 13,14,15 } ; Console.WriteLine ( Foo ( 1,2 , manyValues ) ) ; // 42 } }
La función variádica básica de C++ es prácticamente idéntica a la de C. La única diferencia está en la sintaxis, donde se puede omitir la coma antes de los puntos suspensivos. C++ permite funciones variádicas sin parámetros nombrados , pero no ofrece ninguna forma de acceder a esos argumentos, ya que va_start
requiere el nombre del último argumento fijo de la función.
#include <iostream> #include <cstdarg> void simple_printf ( const char * fmt ...) // El estilo C "const char* fmt, ..." también es válido { va_list args ; va_start ( args , fmt ); while ( * fmt != '\0' ) { if ( * fmt == 'd' ) { int i = va_arg ( args , int ); std :: cout << i << '\n' ; } else if ( * fmt == 'c' ) { // tenga en cuenta la conversión automática al tipo integral int c = va_arg ( args , int ); std :: cout << static_cast < char > ( c ) << '\n' ; } else if ( * fmt == 'f' ) { double d = va_arg ( args , double ); std :: cout << d << '\n' ; } ++ fmt ; } va_end ( argumentos ); } int principal () { simple_printf ( "dcff" , 3 , 'a' , 1.999 , 42.5 ); }
Las plantillas variádicas (paquete de parámetros) también se pueden usar en C++ con expresiones de pliegue integradas en el lenguaje .
#include <flujo de datos> plantilla < nombre_tipo ... Ts > void foo_print ( Ts ... args ) { (( std :: cout << args << ' ' ), ...); } int main () { std :: cout << std :: boolalpha ; foo_print ( 1 , 3.14f ); // 1 3.14 foo_print ( "Foo" , 'b' , verdadero , nullptr ); // Foo b verdadero nullptr }
Los estándares de codificación CERT para C++ prefieren fuertemente el uso de plantillas variádicas (paquete de parámetros) en C++ sobre la función variádica de estilo C debido a un menor riesgo de mal uso. [7]
Las funciones variádicas en Go se pueden llamar con cualquier número de argumentos finales. [8] fmt.Println
es una función variádica común; utiliza una interfaz vacía como un tipo general.
paquete principal importar "fmt" // Esta función variádica toma una cantidad arbitraria de enteros como argumentos. func sum ( nums ... int ) { fmt.Print ( "La suma de " , nums ) // También es una función variádica. total : = 0 for _ , num := range nums { total += num } fmt.Println ( "is" , total ) // También es una función variádica. } func main () { // Las funciones variádicas se pueden llamar de la forma habitual con argumentos individuales. sum ( 1 , 2 ) // "La suma de [1 2] es 3" sum ( 1 , 2 , 3 ) // "La suma de [1 2 3] es 6" // Si ya tiene varios argumentos en una porción, aplíquelos a una función variádica usando func(slice...) de esta manera. nums := [] int { 1 , 2 , 3 , 4 } sum ( nums ... ) // "La suma de [1 2 3 4] es 10" }
Producción:
La suma de [1 2] es 3La suma de [1 2 3] es 6La suma de [1 2 3 4] es 10
Al igual que en C#, el Object
tipo en Java está disponible como un comodín.
public class Program { // Los métodos variádicos almacenan cualquier argumento adicional que reciben en una matriz. // En consecuencia, `printArgs` es en realidad un método con un parámetro: una matriz de longitud variable de `String`s. private static void printArgs ( String ... strings ) { for ( String string : strings ) { System . out . println ( string ); } } public static void main ( String [] args ) { printArgs ( "hola" ); // abreviatura de printArgs(["hola"]) printArgs ( "hola" , "mundo" ); // abreviatura de printArgs(["hola", "mundo"]) } }
A JavaScript no le importan los tipos de argumentos variádicos.
función suma (... números ) { devolver números . reducir (( a , b ) => a + b , 0 ); } consola.log ( suma ( 1,2,3 ) ) ; // 6 consola.log ( suma ( 3,2 ) ) ; // 5 consola.log ( suma ( ) ) ; // 0
También es posible crear una función variádica utilizando el objeto de argumentos, aunque solo se puede utilizar con funciones creadas con la function
palabra clave.
función suma ( ) { return Array.prototype.reduce.call ( argumentos , ( a , b ) = > a + b , 0 ) ; } consola.log ( suma ( 1,2,3 ) ) ; // 6 consola.log ( suma ( 3,2 ) ) ; // 5 consola.log ( suma ( ) ) ; // 0
Las funciones de Lua pueden pasar varargs a otras funciones de la misma manera que otros valores usando la return
palabra clave. Las tablas se pueden pasar a funciones variádicas usando, en la versión Lua 5.2 o superior [9] table.unpack
, o Lua 5.1 o inferior [10] unpack
. Los varargs se pueden usar como una tabla construyendo una tabla con el vararg como valor.
función suma (...) --... designa varargs local suma = 0 para _ , v en pares ({...}) hacer --crear una tabla con varargs es lo mismo que crear una con valores estándar suma = suma + v fin devolver suma finvalues = { 1 , 2 , 3 , 4 } suma ( 5 , tabla.unpack ( valores )) --devuelve 15. tabla.unpack debe ir después de cualquier otro argumento, de lo contrario no todos los valores se pasarán a la función.función add5 (...) retorna ... + 5 - este es un uso incorrecto de varargs y solo retornará el primer valor proporcionadoentradas = {} función process_entries () local procesado = {} para i , v en pares ( entradas ) hacer procesado [ i ] = v --placeholder código de procesamiento fin retorno tabla.unpack ( procesado ) --devuelve todas las entradas de una manera que se pueda usar como un vararg finprint ( process_entries ()) - la función de impresión toma todas las variables y las escribe en stdout separadas por nuevas líneas
Pascal está estandarizado por las normas ISO 7185 (“Pascal estándar”) y 10206 (“Pascal extendido”). Ninguna de las formas estandarizadas de Pascal admite rutinas variádicas, excepto ciertas rutinas integradas ( read
/ readLn
y write
/ writeLn
, y adicionalmente en EP readStr
/ writeStr
).
No obstante, los dialectos de Pascal implementan mecanismos que se asemejan a las rutinas variádicas. Delphi define un tipo de datos que puede asociarse con el último parámetro formal . Dentro de la definición de rutina, el es un , una matriz de registros variantes . [11]
El miembro del tipo de datos antes mencionado permite la inspección del tipo de datos del argumento y su posterior manejo apropiado. El compilador Free Pascal también admite las rutinas variádicas de Delphi. [12]array of const
array of const
array of TVarRec
VType
record
Sin embargo, esta implementación requiere técnicamente un único argumento, es decir, un array
. Pascal impone la restricción de que las matrices deben ser homogéneas. Este requisito se evita utilizando un registro de variantes. GNU Pascal define una especificación de parámetro formal variádico real utilizando puntos suspensivos ( ...
), pero a partir de 2022 no se ha definido ningún mecanismo portátil para utilizarlo. [13]
Tanto GNU Pascal como FreePascal permiten que las funciones declaradas externamente utilicen una especificación de parámetros formales variádicos mediante puntos suspensivos ( ...
).
A PHP no le importan los tipos de argumentos variádicos a menos que el argumento esté tipificado.
función suma ( ... $nums ) : int { return array_sum ( $nums ); }eco suma ( 1 , 2 , 3 ); // 6
Y escribió argumentos variádicos:
función suma ( int ... $nums ) : int { return array_sum ( $nums ); }echo sum ( 1 , 'a' , 3 ); // TypeError: El argumento 2 pasado a sum() debe ser del tipo int (desde PHP 7.3)
A Python no le importan los tipos de argumentos variádicos.
def foo ( a , b , * args ): print ( args ) # args es una tupla (secuencia inmutable).foo ( 1 , 2 ) # () foo ( 1 , 2 , 3 ) # (3,) foo ( 1 , 2 , 3 , "hola" ) # (3, "hola")
Los argumentos de palabras clave se pueden almacenar en un diccionario, por ejemplo def bar(*args, **kwargs)
.
En Raku , el tipo de parámetros que crean funciones variádicas se conocen como parámetros de matriz slurpy y se clasifican en tres grupos:
Estos parámetros se declaran con un solo asterisco ( *
) y aplanan los argumentos disolviendo una o más capas de elementos que se pueden iterar (es decir, Iterables).
sub foo ( $a , $b , * @args ) { decir @args . perl ;}foo ( 1 , 2 ) # [] foo ( 1 , 2 , 3 ) # [3] foo ( 1 , 2 , 3 , "hola" ) # [3 "hola"] foo ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, 4, 5, 6]
Estos parámetros se declaran con dos asteriscos ( **
) y no aplanan ningún argumento iterable dentro de la lista, sino que mantienen los argumentos más o menos como están:
sub barra ( $a , $b , ** @args ) { decir @args . perl ;}barra ( 1 , 2 ); # [] barra ( 1 , 2 , 3 ); # [3] barra ( 1 , 2 , 3 , "hola" ); # [3 "hola"] barra ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]]
Estos parámetros se declaran con un +
signo más ( ) y aplican la "regla del argumento único" , que decide cómo manejar el argumento slurpy en función del contexto. En pocas palabras, si solo se pasa un argumento único y ese argumento es iterable, ese argumento se usa para completar la matriz de parámetros slurpy. En cualquier otro caso, +@
funciona como **@
(es decir, slurpy sin aplanar).
sub zaz ( $a , $b , + @args ) { decir @args . perl ;}zaz ( 1 , 2 ); # [] zaz ( 1 , 2 , 3 ); # [3] zaz ( 1 , 2 , 3 , "hola" ); # [3 "hola"] zaz ( 1 , 2 , [ 4 , 5 ]); # [4, 5], un solo argumento llena la matriz zaz ( 1 , 2 , 3 , [ 4 , 5 ]); # [3, [4, 5]], comportándose como **@ zaz ( 1 , 2 , 3 , [ 4 , 5 ], [ 6 ]); # [3, [4, 5], [6]], comportándose como **@
A Ruby no le importan los tipos de argumentos variádicos.
def foo ( * args ) imprimir args fin foo ( 1 ) # imprime `[1]=> nil`foo ( 1 , 2 ) # imprime `[1, 2]=> nil`
Rust no admite argumentos variádicos en funciones. En su lugar, utiliza macros . [14]
macro_rules! calculate { // El patrón para un solo `eval` ( eval $e : expr ) => {{ { let val : usize = $e ; // Fuerza a que los tipos sean enteros println! ( "{} = {}" , stringify! { $e }, val ); } }}; // Descomponer múltiples `eval`s recursivamente ( eval $e : expr , $( eval $es : expr ), + ) => {{ calculate ! { eval $e } calculate ! { $( eval $es ), + } }}; } fn main () { calcular ! { // Mira ma! Variadic `calculate!`! eval 1 + 2 , eval 3 + 4 , eval ( 2 * 3 ) + 1 } }
Rust puede interactuar con el sistema variádico de C a través de un c_variadic
conmutador de características. Al igual que con otras interfaces de C, el sistema se considera unsafe
parte de Rust. [15]
objeto Programa { // Los métodos variádicos almacenan cualquier argumento adicional que reciben en una matriz. // En consecuencia, `printArgs` es en realidad un método con un parámetro: una matriz de longitud variable de `String`s. private def printArgs ( strings : String * ) : Unit = { strings.foreach ( println ) } def main ( args : Array [ String ]): Unidad = { printArgs ( "hola" ); // abreviatura de printArgs(["hola"]) printArgs ( "hola" , "mundo" ); // abreviatura de printArgs(["hola", "mundo"]) } }
A Swift le importa el tipo de argumentos variádicos, pero el tipo general Any
está disponible.
func saludo ( timeOfTheDay : String , names : String ...) { // aquí, names es [String] print ( " Parece que tenemos \( nombres.contar ) personas" ) para nombre en nombres { print ( "Hola \( nombre ) , buenas \( horaDelDía ) " ) } }Saludo ( horaDelDía : "mañana" , nombres : "José" , "Clara" , "William" , "María" )// Salida: // Parece que tenemos 4 personas // Hola Joseph, buenos días // Hola Clara, buenos días // Hola William, buenos días // Hola María, buenos días
Un procedimiento Tcl o lambda es variádico cuando su último argumento es args
: esto contendrá una lista (posiblemente vacía) de todos los argumentos restantes. Este patrón es común en muchos otros métodos similares a procedimientos. [16] [17]
proc greeting { timeOfTheDay args } { puts "Parece que tenemos [llength $args] personas" foreach nombre $args { puts "Hola $nombre, buen $tiempoDelDía" } } Saludos "Buenos días" "José" "Clara" "William" "María" # Salida: # Parece que tenemos 4 personas # Hola Joseph, buenos días # Hola Clara, buenos días # Hola William, buenos días # Hola María, buenos días