Função variável - Variadic function
Em matemática e em programação de computadores , uma função de aridade variável é uma função de indefinido arity , ou seja, aquele que aceita um número variável de argumentos . O suporte para funções variáveis difere amplamente entre as linguagens de programação .
O termo variadic é um neologismo , que remonta a 1936-1937. O termo não foi amplamente usado até a década de 1970.
Visão geral
Existem muitas operações matemáticas e lógicas que surgem naturalmente como funções variáveis. Por exemplo, a soma de números ou a concatenação de strings ou outras sequências são operações que podem ser consideradas aplicáveis a qualquer número de operandos (embora formalmente, nesses casos, a propriedade associativa seja aplicada).
Outra operação que foi implementada como uma função variável em muitos idiomas é a formatação de saída. O C função printf
e a Common Lisp função format
são dois exemplos. Ambos recebem um argumento que especifica a formatação da saída e qualquer número de argumentos que fornecem os valores a serem formatados.
As funções variáveis podem expor problemas de segurança de tipo em alguns idiomas. Por exemplo, Cs printf
, se usados incautamente, podem dar origem a uma classe de brechas de segurança conhecidas como ataques de string de formato . O ataque é possível porque o suporte de linguagem para funções variáveis não é seguro para o tipo: ele permite que a função tente retirar mais argumentos da pilha do que foram colocados lá, corrompendo a pilha e levando a um comportamento inesperado. Como consequência disso, o Centro de Coordenação CERT considera as funções variáveis em C um risco de segurança de alta gravidade.
Em linguagens funcionais, as variáveis podem ser consideradas complementares à função apply , que recebe uma função e uma lista / sequência / array como argumentos e chama a função com os argumentos fornecidos nessa lista, passando assim um número variável de argumentos para a função. Na linguagem funcional Haskell , funções variáveis podem ser implementadas retornando um valor de uma classe de tipo T
; se as instâncias de T
forem um valor de retorno final r
e uma função (T t) => x -> t
, isso permite qualquer número de argumentos adicionais x
.
Um assunto relacionado na pesquisa de reescrita de termos é chamado de hedges , ou variáveis de hedge . Ao contrário das variáveis, que são funções com argumentos, as coberturas são sequências de argumentos em si. Eles também podem ter restrições ('aceitar no máximo 4 argumentos', por exemplo) ao ponto em que não tenham comprimento variável (como 'aceitar exatamente 4 argumentos') - assim, chamá-los de variáveis pode ser enganoso. No entanto, eles se referem ao mesmo fenômeno, e às vezes o fraseado é misto, resultando em nomes como variável variável (sinônimo de hedge). Observe o duplo significado da palavra variável e a diferença entre argumentos e variáveis na programação funcional e na reescrita de termos. Por exemplo, um termo (função) pode ter três variáveis, uma delas uma cobertura, permitindo assim que o termo receba três ou mais argumentos (ou dois ou mais se a cobertura estiver vazia).
Exemplos
Em C
Para implementar portavelmente funções variáveis na linguagem de programação C , o stdarg.h
arquivo de cabeçalho padrão é usado. O varargs.h
cabeçalho mais antigo foi substituído por stdarg.h
. Em C ++, o arquivo de cabeçalho cstdarg
é usado.
#include <stdarg.h>
#include <stdio.h>
double average(int count, ...) {
va_list ap;
int j;
double sum = 0;
va_start(ap, count); /* Requires the last fixed parameter (to get the address) */
for (j = 0; j < count; j++) {
sum += va_arg(ap, int); /* Increments ap to the next argument. */
}
va_end(ap);
return sum / count;
}
int main(int argc, char const *argv[]) {
printf("%f\n", average(3, 1, 2, 3));
return 0;
}
Isso calculará a média de um número arbitrário de argumentos. Observe que a função não conhece o número de argumentos ou seus tipos. A função acima espera que os tipos sejam int
, e que o número de argumentos seja passado no primeiro argumento (este é um uso frequente, mas de forma alguma imposto pela linguagem ou compilador). Em alguns outros casos, por exemplo printf , o número e os tipos de argumentos são calculados a partir de uma string de formato. Em ambos os casos, isso depende do programador fornecer as informações corretas. (Alternativamente, um valor sentinela como NULL
pode ser usado para indicar o número.) Se menos argumentos forem passados do que a função acredita, ou os tipos de argumentos estão incorretos, isso pode fazer com que ela seja lida em áreas inválidas da memória e pode levar a vulnerabilidades como o ataque de formato de string .
stdarg.h
declara um tipo, va_list
e define quatro macros: va_start
, va_arg
, va_copy
, e va_end
. Cada invocação de va_start
e va_copy
deve ser correspondida por uma invocação correspondente de va_end
. Ao trabalhar com argumentos de variáveis, uma função normalmente declara uma variável do tipo va_list
( ap
no exemplo) que será manipulada pelas macros.
-
va_start
recebe dois argumentos, umva_list
objeto e uma referência ao último parâmetro da função (o que está antes das reticências; a macro usa isso para obter sua orientação). Ele inicializa ova_list
objeto para uso porva_arg
ouva_copy
. O compilador normalmente emitirá um aviso se a referência estiver incorreta (por exemplo, uma referência a um parâmetro diferente do último, ou uma referência a um objeto totalmente diferente), mas não impedirá que a compilação seja concluída normalmente. -
va_arg
recebe dois argumentos, umva_list
objeto (inicializado anteriormente) e um descritor de tipo. Ele se expande para o próximo argumento de variável e tem o tipo especificado. As sucessivas invocações deva_arg
permitem o processamento de cada um dos argumentos variáveis, por sua vez. O comportamento não especificado ocorre se o tipo estiver incorreto ou se não houver nenhum argumento de variável seguinte. -
va_end
leva um argumento, umva_list
objeto. Serve para limpar. Se alguém quisesse, por exemplo, examinar os argumentos das variáveis mais de uma vez, o programador reinicializaria seuva_list
objeto invocandova_end
eva_start
novamente nele. -
va_copy
recebe dois argumentos, ambosva_list
objetos. Ele clona o segundo (que deve ter sido inicializado) no primeiro. Voltando ao exemplo "varra os argumentos das variáveis mais de uma vez", isso pode ser conseguido invocandova_start
um primeiro eva_list
, em seguida, usandova_copy
para cloná-lo em um segundova_list
. Depois de escanear os argumentos das variáveis uma primeira vez comva_arg
e a primeirava_list
(descartando-os comva_end
), o programador poderia escanear os argumentos das variáveis uma segunda vez comva_arg
e na segundava_list
. Não se esqueçava_end
do clonadova_list
.
Em C #
C # descreve funções variáveis usando a params
palavra - chave. Um tipo deve ser fornecido para os argumentos, embora object[]
possa ser usado como abrangente.
using System;
class Program
{
static int Foo(int a, int b, params int[] args)
{
// Return the sum of the integers in args, ignoring a and b.
int sum = 0;
foreach (int i in args)
sum += i;
return sum;
}
static void Main(string[] args)
{
Console.WriteLine(Foo(1, 2)); // 0
Console.WriteLine(Foo(1, 2, 3, 10, 20)); // 33
}
}
Em C ++
A facilidade variável básica em C ++ é amplamente idêntica à de C. A única diferença está na sintaxe, onde a vírgula antes das reticências pode ser omitida.
#include <iostream>
#include <cstdarg>
void simple_printf(const char* fmt...) // C-style "const char* fmt, ..." is also valid
{
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') {
// note automatic conversion to integral type
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(args);
}
int main()
{
simple_printf("dcff", 3, 'a', 1.999, 42.5);
}
Os modelos variados (pacote de parâmetros) também podem ser usados em C ++ com expressões de dobra integradas na linguagem .
#include <iostream>
template <typename... 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', true, nullptr); // Foo b true nullptr
}
Os Padrões de Codificação CERT para C ++ preferem fortemente o uso de modelos variadic (pacote de parâmetros) em C ++ em vez da função variadic de estilo C devido a um menor risco de uso indevido.
Em Go
As funções variáveis em Go podem ser chamadas com qualquer número de argumentos finais. fmt.Println
é uma função variada comum; ele usa uma interface vazia como um tipo pega-tudo.
package main
import "fmt"
// This variadic function takes an arbitrary number of ints as arguments.
func sum(nums ...int) {
fmt.Print("The sum of ", nums) // Also a variadic function.
total := 0
for _, num := range nums {
total += num
}
fmt.Println(" is", total) // Also a variadic function.
}
func main() {
// Variadic functions can be called in the usual way with individual
// arguments.
sum(1, 2) // "The sum of [1 2] is 3"
sum(1, 2, 3) // "The sum of [1 2 3] is 6"
// If you already have multiple args in a slice, apply them to a variadic
// function using func(slice...) like this.
nums := []int{1, 2, 3, 4}
sum(nums...) // "The sum of [1 2 3 4] is 10"
}
Saída:
The sum of [1 2] is 3 The sum of [1 2 3] is 6 The sum of [1 2 3 4] is 10
Em Java
Tal como acontece com C #, o Object
tipo em Java está disponível como um pega-tudo.
public class Program {
// Variadic methods store any additional arguments they receive in an array.
// Consequentially, `printArgs` is actually a method with one parameter: a
// variable-length array of `String`s.
private static void printArgs(String... strings) {
for (String string : strings) {
System.out.println(string);
}
}
public static void main(String[] args) {
printArgs("hello"); // short for printArgs(["hello"])
printArgs("hello", "world"); // short for printArgs(["hello", "world"])
}
}
Em JavaScript
JavaScript não se preocupa com os tipos de argumentos variáveis.
function sum(...numbers) {
return numbers.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3)); // 6
console.log(sum(3, 2)); // 5
console.log(sum()); // 0
Em Pascal
Pascal tem quatro procedimentos embutidos que são definidos como variadic, que, devido a esta condição especial, são intrínsecos ao compilador. Esses são os procedimentos de leitura , leitura , gravação e gravação . No entanto, existem especificações alternativas que permitem argumentos padrão para procedimentos ou funções que os fazem trabalhar de forma variada, bem como polimorfismo que permite que um procedimento ou função tenha parâmetros diferentes.
Os procedimentos de leitura [ln] e gravação [ln] têm todos o mesmo formato:
- ler [ln] [([arquivo,] variável [, variável ...])];
- escrever [ln] [([arquivo] [, valor [, valor ...])];
Onde
- file é uma variável de arquivo opcional que, se omitida, assume o padrão de entrada para leitura e leitura ou é padronizada para saída para gravação e gravação ;
- variável é um escalar como um char (caractere), inteiro ou real (ou para alguns compiladores, certos tipos de registro ou tipos de matriz, como strings), e
- valor é uma variável ou constante.
Exemplo:
var
f: text;
ch: char;
n,a,I,B: Integer;
S: String;
begin
Write('Enter name of file to write results: ');
readln(s);
assign(f,S);
rewrite(f);
Write('What is your name? ');
readln(Input,S);
Write('Hello, ',S,'! Enter the number of calculations you want to do:');
writeln(output);
Write('? ');
readln(N);
Write('For each of the ',n,' formulas, enter ');
write('two integers separated by one or more spaces');
writeln;
for i := 1 to N do
begin
Write('Enter pair #',i,'? ');
read(a,b);
READLN;
WRITELN(Out,'A [',a,'] + B [',B,'] =',A+B);
end;
close(OUT);
end.
No exemplo acima, no que diz respeito ao compilador, as linhas 9 e 13 são idênticas, porque se a entrada for a variável do arquivo sendo lida por uma instrução read ou readln, a variável do arquivo pode ser omitida. Além disso, o compilador considera as linhas 15 e 20 idênticas, porque se a variável do arquivo que está sendo gravada for saída, ela pode ser omitida, o que significa (na linha 20), uma vez que não há argumentos sendo passados para o procedimento, os parênteses listam os argumentos pode ser omitida. A linha 26 mostra que a instrução writeln pode ter qualquer número de argumentos e eles podem ser uma string entre aspas, uma variável ou mesmo um resultado de fórmula.
Object Pascal suporta procedimentos e funções polimórficas , onde diferentes procedimentos ou funções podem ter o mesmo nome, mas são diferenciados pelos argumentos fornecidos a eles.
Pascal também suporta argumentos padrão , onde o valor de um argumento, se não fornecido, recebe um valor padrão.
Para o primeiro exemplo, polimorfismo, considere o seguinte:
function add(a1,a2:integer):Integer; begin add := a1+a2 end;
function add(r1,r2:real):real; begin add := a1+a2 end;
function add(a1:integer;r2:real):real; begin add := real(a1)+a2 end;
function add(r1:real,a2:integer):real; begin add := a1+real(a2) end;
No exemplo acima, se adicionar como chamado com dois valores inteiros, a função declarada na linha 1 seria chamada; se um dos argumentos for um inteiro e um for real, a função na linha 3 ou 4 é chamada dependendo de qual é o inteiro. Se ambos forem reais, a função na linha 2 é chamada.
Para parâmetros padrão, considere o seguinte:
const
Three = 3;
var
K: Integer;
function add(i1: integer = 0;
i2: integer = 0;
i3: integer = 0;
i4: integer = 0;
i5: integer = 0;
i6: integer = 0;
i7: integer = 0;
i8: integer = 0): integer;
begin
add := i1+i2+i3+I4+I5+i6+I7+I8;
end;
begin
K := add; { K is 0}
K := add(K,1); { K is 1}
K := add(1,2); { K is 3}
K := add(1,2,Three); { K is 6, etc.}
end.
Na linha 6 (e nas linhas abaixo), o parâmetro = 0 diz ao compilador, "se nenhum argumento for fornecido, presuma que o argumento seja zero." Na linha 19, nenhum argumento foi fornecido, então a função retorna 0. Na linha 20, um número ou uma variável pode ser fornecida para qualquer argumento e, conforme mostrado na linha 22, uma constante.
Em PHP
O PHP não se preocupa com os tipos de argumentos variáveis, a menos que o argumento seja digitado.
function sum(...$nums): int
{
return array_sum($nums);
}
echo sum(1, 2, 3); // 6
E argumentos variáveis digitados:
function sum(int ...$nums): int
{
return array_sum($nums);
}
echo sum(1, 'a', 3); // TypeError: Argument 2 passed to sum() must be of the type int (since PHP 7.3)
Em Python
Python não se preocupa com os tipos de argumentos variáveis.
def foo(a, b, *args):
print(args) # args is a tuple (immutable sequence).
foo(1, 2) # ()
foo(1, 2, 3) # (3,)
foo(1, 2, 3, "hello") # (3, "hello")
Os argumentos de palavra-chave podem ser armazenados em um dicionário, por exemplo def bar(*args, **kwargs)
.
Em Raku
No Raku , os tipos de parâmetros que criam funções variadas são conhecidos como parâmetros de array slurpy e são classificados em três grupos:
- Slurpy achatado
- Esses parâmetros são declarados com um único asterisco (
*
) e achatam os argumentos dissolvendo uma ou mais camadas de elementos que podem ser iterados (ou seja, Iterables ).sub foo($a, $b, *@args) { say @args.perl; } foo(1, 2) # [] foo(1, 2, 3) # [3] foo(1, 2, 3, "hello") # [3 "hello"] foo(1, 2, 3, [4, 5], [6]); # [3, 4, 5, 6]
- Slurpy não achatado
- Esses parâmetros são declarados com dois asteriscos () e não achatam nenhum argumento iterável na lista, mas mantêm os argumentos mais ou menos como estão:
sub bar($a, $b, **@args) { say @args.perl; } bar(1, 2); # [] bar(1, 2, 3); # [3] bar(1, 2, 3, "hello"); # [3 "hello"] bar(1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]]
- Bagunçado contextual
- Esses parâmetros são declarados com um
+
sinal de mais ( ) e aplicam a " regra de argumento único " , que decide como lidar com o argumento lento com base no contexto. Simplificando, se apenas um único argumento é passado e esse argumento é iterável, esse argumento é usado para preencher o array de parâmetros slurpy. Em qualquer outro caso,+@
funciona como**@
(ou seja, desleixado não achatado).sub zaz($a, $b, +@args) { say @args.perl; } zaz(1, 2); # [] zaz(1, 2, 3); # [3] zaz(1, 2, 3, "hello"); # [3 "hello"] zaz(1, 2, [4, 5]); # [4, 5], single argurment fills up array zaz(1, 2, 3, [4, 5]); # [3, [4, 5]], behaving as **@ zaz(1, 2, 3, [4, 5], [6]); # [3, [4, 5], [6]], behaving as **@
Em Ruby
Ruby não se preocupa com os tipos de argumentos variáveis.
def foo(*args)
print args
end
foo(1)
# prints `[1]=> nil`
foo(1, 2)
# prints `[1, 2]=> nil`
Em ferrugem
Rust não oferece suporte a argumentos variáveis em funções. Em vez disso, ele usa macros .
macro_rules! calculate {
// The pattern for a single `eval`
(eval $e:expr) => {{
{
let val: usize = $e; // Force types to be integers
println!("{} = {}", stringify!{$e}, val);
}
}};
// Decompose multiple `eval`s recursively
(eval $e:expr, $(eval $es:expr),+) => {{
calculate! { eval $e }
calculate! { $(eval $es),+ }
}};
}
fn main() {
calculate! { // Look ma! Variadic `calculate!`!
eval 1 + 2,
eval 3 + 4,
eval (2 * 3) + 1
}
}
Rust é capaz de interagir com o sistema variável de C por meio de um c_variadic
switch de recursos. Tal como acontece com outras interfaces C, o sistema é considerado unsafe
Rust.
Em Swift
O Swift se preocupa com o tipo de argumentos variáveis, mas o Any
tipo pega-tudo está disponível.
func greet(timeOfTheDay: String, names: String...) {
// here, names is [String]
print("Looks like we have \(names.count) people")
for name in names {
print("Hello \(name), good \(timeOfTheDay)")
}
}
greet(timeOfTheDay: "morning", names: "Joseph", "Clara", "William", "Maria")
// Output:
// Looks like we have 4 people
// Hello Joseph, good morning
// Hello Clara, good morning
// Hello William, good morning
// Hello Maria, good morning
Veja também
- Varargs na linguagem de programação Java
- Macro variável (linguagem de programação C)
- Template variadic
Referências
links externos
- Função variada . Tarefa Rosetta Code mostrando a implementação de funções variáveis em mais de cinquenta linguagens de programação.
- Funções de argumento variável - Um tutorial sobre funções de argumento variável para C ++
- Manual GNU libc