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 printfe a Common Lisp função formatsã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 Tforem um valor de retorno final re 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.harquivo de cabeçalho padrão é usado. O varargs.hcabeç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 NULLpode 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.hdeclara um tipo, va_liste define quatro macros: va_start, va_arg, va_copy, e va_end. Cada invocação de va_starte va_copydeve 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( apno exemplo) que será manipulada pelas macros.

  1. va_startrecebe dois argumentos, um va_listobjeto 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 o va_listobjeto para uso por va_argou va_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.
  2. va_argrecebe dois argumentos, um va_listobjeto (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 de va_argpermitem 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.
  3. va_endleva um argumento, um va_listobjeto. Serve para limpar. Se alguém quisesse, por exemplo, examinar os argumentos das variáveis ​​mais de uma vez, o programador reinicializaria seu va_listobjeto invocando va_ende va_startnovamente nele.
  4. va_copyrecebe dois argumentos, ambos va_listobjetos. 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 invocando va_startum primeiro e va_list, em seguida, usando va_copypara cloná-lo em um segundo va_list. Depois de escanear os argumentos das variáveis ​​uma primeira vez com va_arge a primeira va_list(descartando-os com va_end), o programador poderia escanear os argumentos das variáveis ​​uma segunda vez com va_arge na segunda va_list. Não se esqueça va_enddo clonado va_list.

Em C #

C # descreve funções variáveis ​​usando a paramspalavra - 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 Objecttipo 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_variadicswitch de recursos. Tal como acontece com outras interfaces C, o sistema é considerado unsafeRust.

Em Swift

O Swift se preocupa com o tipo de argumentos variáveis, mas o Anytipo 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

Referências

links externos