En matemáticas y en programación informática , una función variada es una función de aridad indefinida , es decir, una que acepta un número variable de argumentos . El soporte para funciones variadas difiere ampliamente entre los lenguajes de programación .
El término variadic es un neologismo que se remonta a 1936-1937. [1] El término no se utilizó ampliamente hasta la década de 1970.
Hay muchas operaciones matemáticas y lógicas que se presentan naturalmente como funciones variadas. 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 variada en muchos idiomas es el formateo de salida. La función Cprintf
y la función Common Lispformat
son dos de esos ejemplos. Ambos toman un argumento que especifica el formato de la salida y cualquier número de argumentos que proporcionen los valores a formatear.
Las funciones variadas pueden exponer problemas de seguridad de tipos en algunos idiomas. Por ejemplo, las C printf
, si se usan sin precaución, pueden dar lugar a una clase de agujeros de seguridad conocidos como ataques de cadena de formato . El ataque es posible porque el soporte del lenguaje para funciones variadas no tiene seguridad de tipos: permite que la función intente extraer más argumentos de la pila de los que se colocaron allí, corrompiendo la pila y provocando un comportamiento inesperado. Como consecuencia de esto, el Centro de Coordinación CERT considera que las funciones variadas en C son un riesgo de seguridad de alta gravedad. [2]
En los lenguajes de programación funcionales , las variadics 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 proporcionados en esa lista, pasando así un número variable de argumentos a la función. función. [ cita requerida ] En el lenguaje funcional Haskell , las funciones variadas se pueden implementar devolviendo un valor de una clase de tipo 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 sobre reescritura de términos se llama coberturas o variables de cobertura . [3] A diferencia de las variadics, que son funciones con argumentos, las coberturas son secuencias de argumentos en sí mismas. También pueden tener restricciones ('no tomar más de 4 argumentos', por ejemplo) hasta el punto de que no sean de longitud variable (como 'tomar exactamente 4 argumentos'), por lo que llamarlos variadics puede ser engañoso. Sin embargo, se refieren al mismo fenómeno y, a veces, la redacción se mezcla, lo que da lugar a nombres como variable variable (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, permitiendo así 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 variadas 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> promedio doble ( int recuento , ...) { va_list ap ; intj ; doble suma = 0 ; va_start ( ap , contar ); /* 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 al 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 lo aplica 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 de que el programador proporcione la información correcta. (Como alternativa, se puede usar un valor centinela como NULL
para indicar el número). Si se pasan menos argumentos de los que la función cree, o los tipos de argumentos son incorrectos, esto podría hacer que se lea en áreas no válidas de la memoria y puede llevar a vulnerabilidades como el ataque de cadena de formato .
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 ir acompañada de 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 variadas ya no necesitarán un parámetro con nombre antes de los puntos suspensivos. [nota 1] [6] Inicializa el va_list
objeto para que lo utilice va_arg
o 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 (previamente inicializado) y un descriptor de tipo. Se expande al siguiente argumento de variable y tiene el tipo especificado. Las invocaciones sucesivas de va_arg
permiten procesar cada uno de los argumentos de la variable por turno. Se produce un comportamiento no especificado si el tipo es incorrecto o no hay un argumento de variable siguiente.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 invocándolo va_end
y luego va_start
nuevamente sobre él.va_copy
toma dos argumentos, ambos va_list
objetos. Clona el segundo (que debe haber sido 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
un first va_list
y luego usándolo va_copy
para clonarlo en un second va_list
. Después de escanear los argumentos de la variable por primera vez con va_arg
y la primera va_list
(eliminarlos con va_end
), el programador podría escanear los argumentos de la variable una segunda vez con va_arg
y la segunda va_list
. va_end
También debe llamarse en el clonado va_list
antes de que regrese la función contenedora.C# describe funciones variadas usando 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. Usar la forma variada es azúcar sintáctico para este último.
usando Sistema ; class 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. suma entera = 0 ; foreach ( int i en argumentos ) suma += i ; suma devuelta ; } static void Principal ( cadena [] args ) { Consola . WriteLine ( Foo ( 1 , 2 )); // 0 Consola . WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 int [] muchosValores = nuevo int [] { 13 , 14 , 15 }; Consola . WriteLine ( Foo ( 1 , 2 , muchosValores )); // 42 } }
La función variadic básica en C++ es en gran medida 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 variadas sin parámetros con nombre , pero no proporciona forma de acceder a esos argumentos ya que va_start
requiere el nombre del último argumento fijo de la función.
#incluir <iostream> #incluir <cstdarg> void simple_printf ( const char * fmt ...) // El estilo C "const char* fmt, ..." también es válido { va_list args ; va_start ( argumentos , fmt ); mientras ( * fmt != '\0' ) { si ( * fmt == 'd' ) { int i = va_arg ( args , int ); std :: cout << i << '\n' ; } else if ( * fmt == 'c' ) { // tenga en cuenta la conversión automática a tipo integral int c = va_arg ( args , int ); std :: cout << static_cast < char > ( c ) << '\n' ; } else if ( * fmt == 'f' ) { doble d = va_arg ( args , doble ); std :: cout << d << '\n' ; } ++ fmt ; } va_end ( argumentos ); } int main () { simple_printf ( "dcff" , 3 , 'a' , 1.999 , 42.5 ); }
Las plantillas variadas (paquete de parámetros) también se pueden usar en C++ con expresiones de plegado integradas en el lenguaje .
#incluir <iostream> plantilla < nombre de tipo ... Ts > void foo_print ( Ts ... args ) { (( std :: cout << args << ' ' ), ...); } int principal () { 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 firmemente el uso de plantillas variadas (paquete de parámetros) en C++ a la función variada de estilo C debido a un menor riesgo de uso indebido. [7]
Las funciones variadas en Go se pueden llamar con cualquier número de argumentos finales. [8] fmt.Println
es una función variada común; utiliza una interfaz vacía como un tipo general.
paquete principal importar "fmt" // Esta función variada toma un número arbitrario de entradas como argumentos. func suma ( nums ... int ) { fmt . Print ( "La suma de" , nums ) // También una función variada. total := 0 para _ , num := rango nums { total += num } fmt . Println ( "is" , total ) // También es una función variada. } func main () { // Las funciones variables se pueden llamar de la forma habitual con // argumentos individuales. suma ( 1 , 2 ) // "La suma de [1 2] es 3" suma ( 1 , 2 , 3 ) // "La suma de [1 2 3] es 6" // Si ya tiene varios argumentos en un segmento, aplíquelos a una función // variada usando func(slice...) como este. números := [] int { 1 , 2 , 3 , 4 } suma ( números ... ) // "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 con C#, el Object
tipo en Java está disponible como un todo.
public class Programa { // Los métodos variables 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 . afuera . println ( cadena ); } } 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 variados.
función suma (... números ) { devolver números . reducir (( a , b ) => a + b , 0 ); } consola . iniciar sesión ( suma ( 1 , 2 , 3 )); // 6 consola . iniciar sesión ( suma ( 3 , 2 )); // 5 consola . iniciar sesión ( suma ()); // 0
También es posible crear una función variada usando el objeto de argumentos, aunque solo se puede usar con funciones creadas con la function
palabra clave.
función suma () { retorno Matriz . prototipo . reducir . llamar ( argumentos , ( a , b ) => a + b , 0 ); } consola . iniciar sesión ( suma ( 1 , 2 , 3 )); // 6 consola . iniciar sesión ( suma ( 3 , 2 )); // 5 consola . iniciar sesión ( 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 variadas utilizando, en Lua versión 5.2 o superior [9] table.unpack
, o Lua 5.1 o inferior [10] unpack
. Varargs se puede utilizar como tabla construyendo una tabla con vararg como valor.
función suma (...) --... designa varargs suma local = 0 para _ , v en pares ({...}) do --crear una tabla con varargs es lo mismo que crear una con valores estándar suma = suma + v final devolver suma final valores = { 1 , 2 , 3 , 4 } suma ( 5 , table.unpack ( valores ) ) -- devuelve 15. table.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 (...) return ... + 5 : este es un uso incorrecto de varargs y solo devolverá el primer valor proporcionado .entradas = {} función proceso_entradas () local procesado = {} para i , v en pares ( entradas ) procesar [ i ] = v --código de procesamiento de marcador de posición final devolver table.unpack ( procesado ) --devuelve todas las entradas de una manera que se puede utilizar como un final vararg print ( process_entries ()) : la función de impresión toma todos los varargs y los escribe en la salida estándar separados por nuevas líneas
Pascal está estandarizado según las normas ISO 7185 (“Pascal estándar”) y 10206 (“Pascal extendido”). Ninguna forma estandarizada de Pascal admite rutinas variadas, 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 rutinas variadas. Delphi define un tipo de datos que puede estar asociado con el último parámetro formal . Dentro de la definición de rutina hay 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 adecuado. El compilador Free Pascal también admite las rutinas variadas de Delphi. [12]array of const
array of const
array of TVarRec
VType
record
Esta implementación, sin embargo, técnicamente requiere un único argumento, que es un archivo array
. Pascal impone la restricción de que las matrices deben ser homogéneas. Este requisito se evita utilizando un registro variante. GNU Pascal define una especificación de parámetro formal variable 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 funciones declaradas externamente utilicen una especificación de parámetro formal variable utilizando puntos suspensivos ( ...
).
A PHP no le importan los tipos de argumentos variables a menos que el argumento esté escrito.
función suma ( ... $nums ) : int { return array_sum ( $nums ); } suma de eco ( 1 , 2 , 3 ); // 6
Y argumentos variados escritos:
función suma ( int ... $nums ) : int { return array_sum ( $nums ); } suma de eco ( 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 variados.
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 variadas 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 al disolver una o más capas de elementos sobre los que se puede iterar (es decir, Iterables).
sub foo ( $a , $b , * @args ) { dice @args . Perla ;}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:
subbarra ( $a , $ b , ** @args ) { digamos @args . Perla ; }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 de argumento único" , que decide cómo manejar el argumento slurpy según el contexto. En pocas palabras, si solo se pasa un argumento y ese argumento es iterable, ese argumento se usa para llenar la matriz de parámetros slurpy. En cualquier otro caso, +@
funciona como **@
(es decir, viscoso sin aplanar).
sub zaz ( $a , $b , + @args ) { dice @args . Perla ;}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 variados.
def foo ( * args ) imprimir argumentos fin foo ( 1 ) # imprime `[1]=> nulo`foo ( 1 , 2 ) # imprime `[1, 2]=> nulo`
Rust no admite argumentos variados en funciones. En su lugar, utiliza macros . [14]
macro_reglas! calcular { // El patrón para una sola `eval` ( eval $e : expr ) => {{ { let val : usize = $e ; // ¡Forzar que los tipos sean números enteros println! ( "{} = {}" , ¡stringificar! { $e }, val ); } }}; // Descomponer múltiples `eval`s recursivamente ( eval $e : expr , $( eval $es : expr ), + ) => {{ calcular ! { eval $e } calcular ! { $( eval $es ), + } }}; } fn principal () { calcular ! { // ¡Mira mamá! Variádico `¡calcular!`! evaluación 1 + 2 , evaluación 3 + 4 , evaluación ( 2 * 3 ) + 1 } }
Rust puede interactuar con el sistema variado de C a través de un c_variadic
interruptor de función. Al igual que con otras interfaces C, el sistema se considera unsafe
Rust. [15]
object Programa { // Los métodos variados 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. def privada printArgs ( cadenas : Cadena * ): Unidad = { cadenas . foreach ( imprimirln ) } def principal ( argumentos : Matriz [ Cadena ]): Unidad = { printArgs ( "hola" ); // abreviatura de printArgs(["hola"]) printArgs ( "hola" , "mundo" ); // abreviatura de printArgs(["hola", "mundo"]) } }
A Swift le importa el tipo de argumentos variados, pero el Any
tipo general está disponible.
func greet ( timeOfTheDay : String , nombres : String ...) { // aquí, los nombres son [String] print ( "Parece que tenemos \( nombres . recuento ) personas" ) para nombre en nombres { print ( "Hola \( nombre ) , bien \( horadeldía ) " ) } }saludar ( timeOfTheDay : "mañana" , nombres : "Joseph" , "Clara" , "William" , "Maria" )// Salida: // Parece que somos 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 variado cuando su último argumento es args
: 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 greet { timeOfTheDay args } { puts "Parece que tenemos [llength $args] personas" foreach nombre $args { pone "Hola $nombre, buenas $timeOfTheDay" } } saludar a "mañana" "Joseph" "Clara" "William" "Maria" # Salida: # Parece que somos 4 personas # Hola Joseph, buenos días # Hola Clara, buenos días # Hola William, buenos días # Hola María, buenos días