En matemáticas y en programación de computadoras , 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 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.
Descripción general
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 formato de salida. El C función printf
y el Common Lisp función format
son dos 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 variables pueden exponer problemas de seguridad de tipos en algunos idiomas. Por ejemplo, las C printf
, si se usan de manera imprudente, 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 funciones variadas no es de tipo seguro: permite que la función intente sacar 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 del CERT considera que las funciones variadas en C son un riesgo de seguridad de alta severidad. [2]
En los lenguajes funcionales, las variantes pueden considerarse complementarias a la función de aplicación , 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. [ 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 de reescritura de términos se llama coberturas o variables de cobertura . [3] A diferencia de las variantes, que son funciones con argumentos, las coberturas son secuencias de argumentos en sí mismas. También pueden tener restricciones ('tomar no más de 4 argumentos', por ejemplo) hasta el punto en 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 es mixta, lo que da como resultado nombres como variable variadic (sinónimo de cobertura). Tenga en cuenta el doble significado de la palabra variable y la diferencia entre argumentos y variables en la programación funcional y la 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).
Ejemplos de
C ª
Para implementar de forma portátil funciones variadas en el lenguaje de programación C , stdarg.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]
#include #include doble promedio ( recuento int , ...) { va_list ap ; int j ; suma doble = 0 ; va_start ( ap , count ); / * 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 )); return 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 de que el programador proporcione la información correcta. (Alternativamente, se puede usar un valor centinela como NULL
para indicar el número). Si se pasan menos argumentos de los que cree la función, o los tipos de argumentos son incorrectos, esto podría hacer que se lea en áreas no válidas de la memoria y puede conducir 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 de variable, 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, unva_list
objeto y una referencia al último parámetro de la función (el que está antes de la elipsis; la macro usa esto para orientarse). Inicializa elva_list
objeto para que lo utiliceva_arg
ova_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 evitará que la compilación se complete normalmente.va_arg
toma dos argumentos, unva_list
objeto (previamente inicializado) y un descriptor de tipo. Se expande al siguiente argumento de variable y tiene el tipo especificado. Las sucesivas invocaciones deva_arg
permiten procesar cada uno de los argumentos de las variables 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, unva_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 suva_list
objeto invocandova_end
y luegova_start
nuevamente en él.va_copy
toma dos argumentos, ambosva_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 podría lograrse invocandova_start
en un primerova_list
y luego usandova_copy
para clonarlo en un segundova_list
. Después de escanear los argumentos de las variables una primera vez conva_arg
y la primerava_list
(deshaciéndose de ellosva_end
), el programador podría escanear los argumentos de las variables una segunda vez conva_arg
y la segundava_list
. No te olvides deva_end
los clonadosva_list
.
C ª#
C # describe funciones variadas usando la params
palabra clave. Se debe proporcionar un tipo para los argumentos, aunque object[]
se puede usar como un comodín.
usando el sistema ; Programa de clase { static int Foo ( int a , int b , params int [] args ) { // Devuelve la suma de los números enteros en argumentos, ignorando ay b. int suma = 0 ; foreach ( int i en args ) suma + = i ; devolución de suma ; } static void Main ( string [] args ) { Consola . WriteLine ( Foo ( 1 , 2 )); // 0 Consola . WriteLine ( Foo ( 1 , 2 , 3 , 10 , 20 )); // 33 } }
En C ++
La función básica de las variantes 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 la elipsis.
#include #include void simple_printf ( const char * fmt ...) ;int main () { simple_printf ( "dcff" , 3 , 'a' , 1.999 , 42.5 ); }void simple_printf ( const char * fmt ...) // 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' ) { // observe la conversión automática al tipo integral int c = va_arg ( args , int ); std :: cout << static_cast < char > ( c ) << '\ n' ; } Demás si ( * fmt == 'f' ) { doble d = va_arg ( args , doble ); std :: cout << d << '\ n' ; } ++ fmt ; } va_end ( args ); }
Las plantillas variables (paquete de parámetros) también se pueden utilizar en C ++ con expresiones de plegado integradas en el lenguaje .
#include plantilla < typename ... 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' , true , nullptr ); // Foo b true nullptr }
Los Estándares de codificación CERT para C ++ prefieren fuertemente el uso de plantillas variadic (paquete de parámetros) en C ++ sobre la función variadic estilo C debido a un menor riesgo de mal uso. [5]
En ir
Las funciones variables en Go se pueden llamar con cualquier número de argumentos finales. [6] fmt.Println
es una función variádica común; utiliza una interfaz vacía como un tipo general.
paquete principalimportar "fmt"// Esta función variadica toma un número arbitrario de entradas como argumentos. func sum ( nums ... int ) { fmt . Print ( "La suma de" , nums ) // También una función variada. total : = 0 para _ , num : = rango nums { total + = num } fmt . Println ( "es" , total ) // También una función variadic. }func main () { // Las funciones variables 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 un segmento, aplíquelos a una función // variable usando func (segmento ...) como este. 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 3 La suma de [1 2 3] es 6 La suma de [1 2 3 4] es 10
En Java
Al igual que con C #, el Object
tipo en Java está disponible como un comodín.
Programa de clase pública { private static void printArgs ( String ... strings ) { for ( String string : strings ) { System . fuera . println ( cadena ); } } public static void main ( String [] args ) { // el compilador envuelve los argumentos pasados a printArgs dentro de una matriz // lo que significa que printArgs es solo un método que toma un solo argumento que es una matriz de cadena de longitud variable printArgs ( "hola" ); // abreviatura de printArgs (["hola"]) printArgs ( "hola" , "mundo" ); // abreviatura de printArgs (["hola", "mundo"]) } }
En JavaScript
JavaScript no se preocupa por los tipos de argumentos variados.
función suma (... números ) { números de retorno . reducir (( a , b ) => a + b ); } suma ( 1 , 2 , 3 ) // 6 suma ( 3 , 2 ) // 5
En Pascal
Pascal tiene cuatro procedimientos incorporados que se definen como variadic, que, debido a esta condición especial, son intrínsecos al compilador. Estos son los de lectura , readln , escritura , y writeln procedimientos. Sin embargo, existen especificaciones alternativas que permiten argumentos predeterminados para procedimientos o funciones que los hacen funcionar de forma variable, así como polimorfismo que permite que un procedimiento o función tenga diferentes parámetros.
Los procedimientos de lectura [ln] y escritura [ln] tienen el mismo formato:
- leer [ln] [([archivo,] variable [, variable ...])];
- escriba [ln] [([archivo] [, valor [, valor ...])];
dónde
- archivo es una variable de archivo opcional, que si se omite, por defecto de entrada para la lectura y readln , o por defecto a la salida de escritura y writeln ;
- la variable es un escalar como char (carácter), entero o real (o para algunos compiladores, ciertos tipos de registros o tipos de matrices como cadenas), y
- el valor es una variable o una constante.
Ejemplo:
var f : texto ; ch : char ; n , a , I , B : entero ; S : Cuerda ;empezar Write ( 'Ingrese el nombre del archivo para escribir los resultados:' ) ; readln ( s ) ; asignar ( f , S ) ; reescribir ( f ) ; Escribe ( '¿Cuál es tu nombre?' ) ; readln ( Entrada , S ) ; Write ( 'Hola,' , S , '! Ingrese el número de cálculos que desea hacer:' ) ; Writeln ( salida ) ; Escriba ( '?' ) ; readln ( N ) ; Write ( 'Para cada una de las fórmulas ' , n , ', ingrese' ) ; write ( 'dos enteros separados por uno o más espacios' ) ; Writeln ; para i : = 1 a N hacer empezar Write ( 'Introduzca el número de par' , i , '?' ) ; leer ( a , b ) ; READLN ; ESCRIBIR ( Fuera , 'A [' , a , '] + B [' , B , '] =' , A + B ) ; terminar ; cerrar ( FUERA ) ;final .
En el ejemplo anterior, en lo que respecta al compilador, las líneas 9 y 13 son idénticas, porque si input es la variable de archivo en la que se lee una instrucción read o readln, la variable de archivo puede omitirse. Además, el compilador considera que las líneas 15 y 20 son idénticas, porque si la variable de archivo que se está escribiendo es la salida, se puede omitir, lo que significa que (en la línea 20) ya que no se pasan argumentos al procedimiento, los paréntesis enumeran los argumentos. puede ser omitido. La línea 26 muestra que la instrucción Writeln puede tener cualquier número de argumentos, y pueden ser una cadena entre comillas, una variable o incluso un resultado de fórmula.
Object Pascal admite funciones y procedimientos polimórficos , donde diferentes procedimientos o funciones pueden tener el mismo nombre pero se distinguen por los argumentos que se les proporcionan.
Pascal también admite argumentos predeterminados , donde el valor de un argumento, si no se proporciona, recibe un valor predeterminado.
Para el primer ejemplo, polimorfismo, considere lo siguiente:
función sumar ( a1 , a2 : integer ) : Integer ; comenzar agregar : = a1 + a2 final ; función sumar ( r1 , r2 : real ) : real ; comenzar agregar : = a1 + a2 final ; función sumar ( a1 : entero ; r2 : real ) : real ; comenzar suma : = real ( a1 ) + a2 final ; función sumar ( r1 : real , a2 : entero ) : real ; comenzar suma : = a1 + final real ( a2 ) ;
En el ejemplo anterior, si se suma como se llama con dos valores enteros, se llamaría a la función declarada en la línea 1; si uno de los argumentos es un número entero y el otro es real, se llama a la función en la línea 3 o 4 dependiendo de cuál sea el número entero. Si ambos son reales, se llama a la función de la línea 2.
Para los parámetros predeterminados, considere lo siguiente:
constante Tres = 3 ;var K : entero ; función sumar ( i1 : integer = 0 ; i2 : entero = 0 ; i3 : entero = 0 ; i4 : entero = 0 ; i5 : entero = 0 ; i6 : entero = 0 ; i7 : entero = 0 ; i8 : entero = 0 ) : entero ;empezar sumar : = i1 + i2 + i3 + I4 + I5 + i6 + I7 + I8 ;terminar ;empezar K : = agregar ; {K es 0} K : = sumar ( K , 1 ) ; {K es 1} K : = sumar ( 1 , 2 ) ; {K es 3} K : = sumar ( 1 , 2 , Tres ) ; {K es 6, etc.}final .
En la línea 6, (y las líneas siguientes) el parámetro = 0 le dice al compilador, "si no se proporciona ningún argumento, suponga que el argumento es cero". En la línea 19, no se proporcionaron argumentos, por lo que la función devuelve 0. En la línea 20, se puede proporcionar un número o una variable para cualquier argumento y, como se muestra en la línea 22, una constante.
En PHP
PHP no se preocupa por los tipos de argumentos variados a menos que se escriba el argumento.
function sum ( ... $ nums ) : int { return array_sum ( $ nums ); } suma de eco ( 1 , 2 , 3 ); // 6
Y escribí argumentos variadic:
function sum ( 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)
En Python
Python no se preocupa por 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
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:
- Slurpy aplanado
- 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 ) { digamos @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]
- Slurpy sin aplanar
- Estos parámetros se declaran con dos asteriscos () y no aplanan ningún argumento iterable dentro de la lista, pero mantienen los argumentos más o menos como están:
barra secundaria ( $ a , $ b , ** @args ) { digamos @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]]
- Slurpy contextual
- Estos parámetros se declaran con un
+
signo más ( ) y aplican la " regla de un solo argumento " , que decide cómo manejar el argumento slurpy según el contexto. En pocas palabras, si solo se pasa un único 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, slurpy sin aplanar).sub zaz ( $ a , $ b , + @args ) { digamos @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 ** @
En rubí
A Ruby no le importan los tipos de argumentos variados.
def foo ( * args ) print args endfoo ( 1 ) # imprime `[1] => nil`foo ( 1 , 2 ) # imprime `[1, 2] => nil`
En óxido
Rust no admite argumentos variados en funciones. En cambio, usa macros . [7]
macro_rules! calcular { // El patrón para una sola `eval` ( eval $ e : expr ) => {{ { deje val : usize = $ e ; // Forzar los tipos a ser enteros println! ( "{} = {}" , stringify! { $ e }, val ); } }}; // Descomponer múltiples `eval`s de forma recursiva ( eval $ e : expr , $ ( eval $ es : expr ), + ) => {{ calcular ! { eval $ e } calcular ! { $ ( eval $ es ), + } }};}fn main () { calcular ! { // ¡Mira mamá! Variadic `calcular!`! eval 1 + 2 , eval 3 + 4 , eval ( 2 * 3 ) + 1 }}
Rust puede interactuar con el sistema variadic 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. [8]
En Swift
Swift se preocupa por el tipo de argumentos variados, pero el tipo catch-all Any
está disponible.
func greet ( timeOfTheDay : String , names : String ...) { // aquí, los nombres son [String] print ( "Parece que tenemos \ ( nombres . contar ) personas" ) para el nombre en los nombres { print ( "Hola \ ( nombre ) , bueno \ ( timeOfTheDay ) " ) } }saludar ( timeOfTheDay : "mañana" , nombres : "José" , "Clara" , "William" , "María" )// Salida: // Parece que tenemos 4 personas // Hola José, buenos días // Hola Clara, buenos días // Hola William, buenos días // Hola María, buenos días
Ver también
- Varargs en lenguaje de programación Java
- Macro variadic (lenguaje de programación C)
- Plantilla variadic
Referencias
- ^ Henry S. Leonard y HN Goodman, Un cálculo de individuos . Resumen de una charla dada en la Segunda Reunión de la Asociación para la Lógica Simbólica, celebrada en Cambridge MA del 28 al 30 de diciembre de 1936, [1] , Journal of Symbolic Logic 2 (1) 1937, 63.
- ↑ Klemens, Ben (2014). Siglo XXI C: C Consejos de la nueva escuela . O'Reilly Media, Inc. pág. 224. ISBN 1491904445.
- ^ CLP (H): Programación de lógica de restricción para coberturas
- ^ " (stdarg.h) - Referencia de C ++" . www.cplusplus.com .
- ^ "DCL50-CPP. No defina una función variadic estilo C" .
- ^ https://gobyexample.com/variadic-functions
- ^ "Variadics" . Óxido por ejemplo .
- ^ "2137-variadic" . El libro de Rust RFC .
enlaces externos
- Función variádica . Tarea de Rosetta Code que muestra la implementación de funciones variadas en más de cincuenta lenguajes de programación.
- Funciones de argumento variable : un tutorial sobre funciones de argumento variable para C ++
- Manual de GNU libc