Argumentos padrão C


279

Existe uma maneira de especificar argumentos padrão para uma função em C?


6
só quero um C um pouco melhor, não C ++. pense em C +. com uma variedade de pequenas melhorias retiradas do C ++, mas não a grande bagunça. E, por favor, nenhum carregador de links diferente. deve ser apenas mais uma etapa semelhante ao pré-processador. padronizado. em todos os lugares ...
Ivo Welch

Pergunta relacionada que não vi listada na barra lateral.
Shelby Moore III

Eu diria para parar de ser um bárbaro e aprender a usar bem o C ++ (11, ...) - jk! / eu apaga chamas ... mas ... você vai adorar ... hahaha eu não posso me ajudar, desculpe.
Moodboom 17/03/19

Respostas:


147

Na verdade não. A única maneira seria escrever uma função varargs e preencher manualmente os valores padrão para argumentos que o chamador não passa.


22
Eu odeio a falta de verificação ao usar varargs.
dmckee --- gatinho ex-moderador

16
Você também deveria; Na verdade, eu não recomendo isso; Eu só queria transmitir que é possível.
Eli Courtwright

4
No entanto, como você deseja verificar se o chamador passa o argumento ou não? Eu acho que, para que isso funcione, você não tem o interlocutor para lhe dizer que ele não passou? Eu acho que isso torna toda a abordagem um pouco menos utilizável - o chamador também poderia chamar uma função com outro nome.
Johannes Schaub - litb 24/09/09

3
A open(2)chamada do sistema usa isso para um argumento opcional que pode estar presente, dependendo dos argumentos necessários, e printf(3)lê uma sequência de formato que especifica quantos argumentos haverá. Ambos usam varargs com bastante segurança e eficácia, e embora você possa estragar tudo, printf()especialmente parece ser bastante popular.
24711 Chris Lutz

4
@ Eli: Nem todos os compiladores C são gcc. Há alguma mágica avançada do compilador em andamento para avisar quando seus argumentos printf () não correspondem à sua string de formato. E não acho que seja possível receber avisos semelhantes para suas próprias funções variadas (a menos que elas usem o mesmo estilo de string de formato).
tomlogic

280

Uau, todo mundo é tão pessimista por aqui. A resposta é sim.

Não é trivial: no final, teremos a função principal, uma estrutura de suporte, uma função de wrapper e uma macro em torno da função de wrapper. No meu trabalho, tenho um conjunto de macros para automatizar tudo isso; depois de entender o fluxo, será fácil fazer o mesmo.

Eu escrevi isso em outro lugar, então aqui está um link externo detalhado para complementar o resumo aqui: http://modelingwithdata.org/arch/00000022.htm

Gostaríamos de transformar

double f(int i, double x)

em uma função que assume padrões (i = 8, x = 3,14). Defina uma estrutura complementar:

typedef struct {
    int i;
    double x;
} f_args;

Renomeie sua função f_basee defina uma função de wrapper que defina os padrões e chame a base:

double var_f(f_args in){
    int i_out = in.i ? in.i : 8;
    double x_out = in.x ? in.x : 3.14;
    return f_base(i_out, x_out);
}

Agora adicione uma macro, usando as macros variadas de C. Dessa forma, os usuários não precisam saber que estão preenchendo uma f_argsestrutura e acham que estão fazendo o habitual:

#define f(...) var_f((f_args){__VA_ARGS__});

OK, agora tudo o que se segue funcionaria:

f(3, 8);      //i=3, x=8
f(.i=1, 2.3); //i=1, x=2.3
f(2);         //i=2, x=3.14
f(.x=9.2);    //i=8, x=9.2

Verifique as regras sobre como os inicializadores compostos definem os padrões para as regras exatas.

Uma coisa que não funcionará: f(0)porque não podemos distinguir entre um valor ausente e zero. Na minha experiência, isso é algo a ser observado, mas pode ser resolvido conforme a necessidade - metade do tempo em que seu padrão é realmente zero.

Eu tive o trabalho de escrever isso porque acho que os argumentos e padrões nomeados realmente tornam a codificação em C mais fácil e ainda mais divertida. E C é incrível por ser tão simples e ainda ter o suficiente para tornar tudo isso possível.


16
+1 criativo! Ele tem suas limitações, mas também traz parâmetros nomeados para a tabela. Observe que, {}(inicializador vazio) é um erro C99.
U0b34a0f6ae 29/10

28
No entanto, aqui está algo excelente para você: O padrão permite especificar membros nomeados várias vezes, a substituição posterior. Portanto, apenas para parâmetros nomeados, você pode resolver o problema dos padrões e permitir uma chamada vazia. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
U0b34a0f6ae 29/10

3
Espero que os erros do compilador sejam legíveis, mas essa é uma ótima técnica! Quase se parece com python kwargs.
totowtwo

6
@RunHolt Embora certamente mais simples, não é objetivamente melhor; os parâmetros nomeados trazem benefícios como facilidade de legibilidade das chamadas (em detrimento da legibilidade do código-fonte). Um é melhor para os desenvolvedores da fonte, o outro é melhor para os usuários da função. É um pouco apressado jogar fora "este é melhor!"
Alice

3
@DawidPi: C11 6.7.9 (19), ao inicializar agregados: "todos os subobjetos que não foram inicializados explicitamente devem ser inicializados implicitamente da mesma forma que os objetos com duração de armazenamento estático". E como você sabe, os elementos de duração estática são inicializados com zero. | NULL | \ 0. [Isso também foi em c99.]
bk.

161

Sim. :-) Mas não da maneira que você esperaria.

int f1(int arg1, double arg2, char* name, char *opt);

int f2(int arg1, double arg2, char* name)
{
  return f1(arg1, arg2, name, "Some option");
}

Infelizmente, C não permite que você sobrecarregue métodos, então você terminaria com duas funções diferentes. Ainda assim, ao chamar f2, você realmente chamaria f1 com um valor padrão. Esta é uma solução "Não se repita", que ajuda a evitar copiar / colar o código existente.


24
FWIW, eu prefiro usar o número no final da função para indicar o número de argumentos necessários. Torna mais fácil do que simplesmente usar qualquer número arbitrário. :)
Macke

4
Essa é de longe a melhor resposta, pois demonstra uma maneira simples de atingir o mesmo objetivo. Eu tenho uma função que faz parte de uma API fixa que não quero alterar, mas preciso que ela adote um novo parâmetro. Claro, é tão óbvias que eu perdi (ficou preso no pensamento do param padrão!)
RunHolt

4
f2 também poderia ser uma macro pré-processador
osvein

41

Podemos criar funções que usam parâmetros nomeados (apenas) para valores padrão. Esta é uma continuação da resposta de bk.

#include <stdio.h>                                                               

struct range { int from; int to; int step; };
#define range(...) range((struct range){.from=1,.to=10,.step=1, __VA_ARGS__})   

/* use parentheses to avoid macro subst */             
void (range)(struct range r) {                                                     
    for (int i = r.from; i <= r.to; i += r.step)                                 
        printf("%d ", i);                                                        
    puts("");                                                                    
}                                                                                

int main() {                                                                     
    range();                                                                    
    range(.from=2, .to=4);                                                      
    range(.step=2);                                                             
}    

O padrão C99 define que nomes posteriores na inicialização substituam os itens anteriores. Também podemos ter alguns parâmetros posicionais padrão, basta alterar a macro e a assinatura da função de acordo. Os parâmetros de valor padrão podem ser usados ​​apenas no estilo de parâmetro nomeado.

Saída do programa:

1 2 3 4 5 6 7 8 9 10 
2 3 4 
1 3 5 7 9

2
Isso parece uma implementação mais fácil e direta do que a solução Wim ten Brink ou BK. Há alguma desvantagem nessa implementação que as outras também não possuam?
21414 sthenmm

24

O OpenCV usa algo como:

/* in the header file */

#ifdef __cplusplus
    /* in case the compiler is a C++ compiler */
    #define DEFAULT_VALUE(value) = value
#else
    /* otherwise, C compiler, do nothing */
    #define DEFAULT_VALUE(value)
#endif

void window_set_size(unsigned int width  DEFAULT_VALUE(640),
                     unsigned int height DEFAULT_VALUE(400));

Se o usuário não souber o que deve escrever, esse truque pode ser útil:

exemplo de uso


19

Não.

Nem mesmo o mais recente padrão C99 suporta isso.


um simples não teria sido ainda melhor;)
KevinDTimm

21
@ kevindtimm: Isso não é possível, o SO exige uma duração mínima nas respostas. Eu tentei. :)
descontraia

2
Por favor, consulte a minha resposta. :)
caos

18

Não, esse é um recurso da linguagem C ++.


14

Resposta curta: Não.

Resposta um pouco mais longa: Há uma solução antiga e antiga em que você passa uma sequência que analisa para argumentos opcionais:

int f(int arg1, double arg2, char* name, char *opt);

onde opt pode incluir o par "nome = valor" ou algo assim, e que você chamaria como

n = f(2,3.0,"foo","plot=yes save=no");

Obviamente, isso só é útil ocasionalmente. Geralmente quando você deseja uma única interface para uma família de funcionalidades.


Você ainda encontra essa abordagem em códigos de física de partículas escritos por programas profissionais em c ++ (como por exemplo, ROOT ). Sua principal vantagem é que ele pode ser estendido quase indefinidamente, mantendo a compatibilidade com versões anteriores.


2
Combine isso com varargs e você terá todo o tipo de diversão!
David Thornley

5
Eu usaria um costume structe faria com que o chamador fizesse um, preencha os campos para opções diferentes e depois o passe pelo endereço ou NULLpelas opções padrão.
24711 Chris Lutz

Copiar padrões de código do ROOT é uma péssima ideia!
innisfree

13

Provavelmente, a melhor maneira de fazer isso (que pode ou não ser possível no seu caso, dependendo da sua situação) é mudar para C ++ e usá-lo como 'um C melhor'. Você pode usar C ++ sem usar classes, modelos, sobrecarga de operador ou outros recursos avançados.

Isso fornecerá uma variante de C com sobrecarga de função e parâmetros padrão (e quaisquer outros recursos que você escolher usar). Você só precisa ser um pouco disciplinado se quiser realmente usar apenas um subconjunto restrito de C ++.

Muitas pessoas dirão que é uma péssima idéia usar C ++ dessa maneira, e elas podem ter razão. Mas é apenas uma opinião; Eu acho que é válido usar recursos do C ++ com os quais você se sinta confortável sem precisar comprar tudo. Eu acho que uma parte significativa da razão do sucesso do C ++ é que ele foi usado por muitos programadores nos primeiros dias exatamente dessa maneira.


13

Ainda outra opção usa structs:

struct func_opts {
  int    arg1;
  char * arg2;
  int    arg3;
};

void func(int arg, struct func_opts *opts)
{
    int arg1 = 0, arg3 = 0;
    char *arg2 = "Default";
    if(opts)
      {
        if(opts->arg1)
            arg1 = opts->arg1;
        if(opts->arg2)
            arg2 = opts->arg2;
        if(opts->arg3)
            arg3 = opts->arg3;
      }
    // do stuff
}

// call with defaults
func(3, NULL);

// also call with defaults
struct func_opts opts = {0};
func(3, &opts);

// set some arguments
opts.arg3 = 3;
opts.arg2 = "Yes";
func(3, &opts);

9

Não.


2
qual é a solução alternativa? Percebo que está 20202020em hexadecimal, mas como digito?
Lazer

5

Outro truque usando macros:

#include <stdio.h>

#define func(...) FUNC(__VA_ARGS__, 15, 0)
#define FUNC(a, b, ...) func(a, b)

int (func)(int a, int b)
{
    return a + b;
}

int main(void)
{
    printf("%d\n", func(1));
    printf("%d\n", func(1, 2));
    return 0;
}

Se apenas um argumento for passado, brecebe o valor padrão (neste caso 15)


4

Não, mas você pode considerar usar um conjunto de funções (ou macros) para aproximar usando argumentos padrão:

// No default args
int foo3(int a, int b, int c)
{
    return ...;
}

// Default 3rd arg
int foo2(int a, int b)
{
    return foo3(a, b, 0);  // default c
}

// Default 2nd and 3rd args
int foo1(int a)
{
    return foo3(a, 1, 0);  // default b and c
}


3

Geralmente não, mas em gcc Você pode tornar o último parâmetro de funcA () opcional com uma macro.

Em funcB () eu uso um valor especial (-1) para sinalizar que eu preciso do valor padrão para o parâmetro 'b'.

#include <stdio.h> 

int funcA( int a, int b, ... ){ return a+b; }
#define funcA( a, ... ) funcA( a, ##__VA_ARGS__, 8 ) 


int funcB( int a, int b ){
  if( b == -1 ) b = 8;
  return a+b;
}

int main(void){
  printf("funcA(1,2): %i\n", funcA(1,2) );
  printf("funcA(1):   %i\n", funcA(1)   );

  printf("funcB(1, 2): %i\n", funcB(1, 2) );
  printf("funcB(1,-1): %i\n", funcB(1,-1) );
}

1

Melhorei a resposta de Jens Gustedt para que:

  1. funções inline não são empregadas
  2. os padrões são calculados durante o pré-processamento
  3. macros reutilizáveis ​​modulares
  4. possível definir erro do compilador que corresponda significativamente ao caso de argumentos insuficientes para os padrões permitidos
  5. os padrões não são necessários para formar a cauda da lista de parâmetros se os tipos de argumento permanecerem inequívocos
  6. interage com C11 _Generic
  7. varie o nome da função pelo número de argumentos!

variadic.h:

#ifndef VARIADIC

#define _NARG2(_0, _1, _2, ...) _2
#define NUMARG2(...) _NARG2(__VA_ARGS__, 2, 1, 0)
#define _NARG3(_0, _1, _2, _3, ...) _3
#define NUMARG3(...) _NARG3(__VA_ARGS__, 3, 2, 1, 0)
#define _NARG4(_0, _1, _2, _3, _4, ...) _4
#define NUMARG4(...) _NARG4(__VA_ARGS__, 4, 3, 2, 1, 0)
#define _NARG5(_0, _1, _2, _3, _4, _5, ...) _5
#define NUMARG5(...) _NARG5(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define _NARG6(_0, _1, _2, _3, _4, _5, _6, ...) _6
#define NUMARG6(...) _NARG6(__VA_ARGS__, 6, 5, 4, 3, 2, 1, 0)
#define _NARG7(_0, _1, _2, _3, _4, _5, _6, _7, ...) _7
#define NUMARG7(...) _NARG7(__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG8(_0, _1, _2, _3, _4, _5, _6, _7, _8, ...) _8
#define NUMARG8(...) _NARG8(__VA_ARGS__, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define _NARG9(_0, _1, _2, _3, _4, _5, _6, _7, _8, _9, ...) _9
#define NUMARG9(...) _NARG9(__VA_ARGS__, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0)
#define __VARIADIC(name, num_args, ...) name ## _ ## num_args (__VA_ARGS__)
#define _VARIADIC(name, num_args, ...) name (__VARIADIC(name, num_args, __VA_ARGS__))
#define VARIADIC(name, num_args, ...) _VARIADIC(name, num_args, __VA_ARGS__)
#define VARIADIC2(name, num_args, ...) __VARIADIC(name, num_args, __VA_ARGS__)

// Vary function name by number of arguments supplied
#define VARIADIC_NAME(name, num_args) name ## _ ## num_args ## _name ()
#define NVARIADIC(name, num_args, ...) _VARIADIC(VARIADIC_NAME(name, num_args), num_args, __VA_ARGS__)

#endif

Cenário de uso simplificado:

const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);

/*
The output buffer defaults to NULL if not provided.
*/

#include "variadic.h"

#define uint32_frombytes_2(   b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c)    a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

E com _Generic:

const uint8*
uint16_tobytes(const uint16* in, uint8* out, size_t bytes);

const uint16*
uint16_frombytes(uint16* out, const uint8* in, size_t bytes);

const uint8*
uint32_tobytes(const uint32* in, uint8* out, size_t bytes);

const uint32*
uint32_frombytes(uint32* out, const uint8* in, size_t bytes);

/*
The output buffer defaults to NULL if not provided.
Generic function name supported on the non-uint8 type, except where said type
is unavailable because the argument for output buffer was not provided.
*/

#include "variadic.h"

#define   uint16_tobytes_2(a,    c) a, NULL, c
#define   uint16_tobytes_3(a, b, c) a,    b, c
#define   uint16_tobytes(...) VARIADIC(  uint16_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define uint16_frombytes_2(   b, c) NULL, b, c
#define uint16_frombytes_3(a, b, c)    a, b, c
#define uint16_frombytes(...) VARIADIC(uint16_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define   uint32_tobytes_2(a,    c) a, NULL, c
#define   uint32_tobytes_3(a, b, c) a,    b, c
#define   uint32_tobytes(...) VARIADIC(  uint32_tobytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define uint32_frombytes_2(   b, c) NULL, b, c
#define uint32_frombytes_3(a, b, c)    a, b, c
#define uint32_frombytes(...) VARIADIC(uint32_frombytes, NUMARG3(__VA_ARGS__), __VA_ARGS__)

#define   tobytes(a, ...) _Generic((a),                                                                                                 \
                                   const uint16*: uint16_tobytes,                                                                       \
                                   const uint32*: uint32_tobytes)  (VARIADIC2(  uint32_tobytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))

#define frombytes(a, ...) _Generic((a),                                                                                                 \
                                         uint16*: uint16_frombytes,                                                                     \
                                         uint32*: uint32_frombytes)(VARIADIC2(uint32_frombytes, NUMARG3(a, __VA_ARGS__), a, __VA_ARGS__))

E com a seleção variável de nome de função, que não pode ser combinada com _Generic:

// winternitz() with 5 arguments is replaced with merkle_lamport() on those 5 arguments.

#define   merkle_lamport_5(a, b, c, d, e) a, b, c, d, e
#define   winternitz_7(a, b, c, d, e, f, g) a, b, c, d, e, f, g
#define   winternitz_5_name() merkle_lamport
#define   winternitz_7_name() winternitz
#define   winternitz(...) NVARIADIC(winternitz, NUMARG7(__VA_ARGS__), __VA_ARGS__)

1

SIM

Através de macros

3 parâmetros:

#define my_func2(...) my_func3(__VA_ARGS__, 0.5)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func3(char a, int b, float c) // b=10, c=0.5
{
    printf("a=%c; b=%d; c=%f\n", a, b, c);
}

Se você quiser o 4º argumento, será necessário adicionar um my_func3 extra. Observe as alterações em VAR_FUNC, my_func2 e my_func

4 parâmetros:

#define my_func3(...) my_func4(__VA_ARGS__, "default") // <== New function added
#define my_func2(...) my_func3(__VA_ARGS__, (float)1/2)
#define my_func1(...) my_func2(__VA_ARGS__, 10)
#define VAR_FUNC(_1, _2, _3, _4, NAME, ...) NAME
#define my_func(...) VAR_FUNC(__VA_ARGS__, my_func4, my_func3, my_func2, my_func1)(__VA_ARGS__)

void my_func4(char a, int b, float c, const char* d) // b=10, c=0.5, d="default"
{
    printf("a=%c; b=%d; c=%f; d=%s\n", a, b, c, d);
}

Única exceção, que variáveis flutuantes não podem receber valores padrão (a menos que seja o último argumento, como no caso dos 3 parâmetros ), porque precisam do ponto ('.'), Que não é aceito nos argumentos da macro. Mas pode descobrir uma solução alternativa, como pode ser visto na macro my_func2 ( de 4 parâmetros )

Programa

int main(void)
{
    my_func('a');
    my_func('b', 20);
    my_func('c', 200, 10.5);
    my_func('d', 2000, 100.5, "hello");

    return 0;
}

Resultado:

a=a; b=10; c=0.500000; d=default                                                                                                                                                  
a=b; b=20; c=0.500000; d=default                                                                                                                                                  
a=c; b=200; c=10.500000; d=default                                                                                                                                                
a=d; b=2000; c=100.500000; d=hello  

0

Sim, você pode fazer alguma simulação, aqui você precisa conhecer as diferentes listas de argumentos que pode obter, mas tem a mesma função para lidar com todas.

typedef enum { my_input_set1 = 0, my_input_set2, my_input_set3} INPUT_SET;

typedef struct{
    INPUT_SET type;
    char* text;
} input_set1;

typedef struct{
    INPUT_SET type;
    char* text;
    int var;
} input_set2;

typedef struct{
    INPUT_SET type;
    int text;
} input_set3;

typedef union
{
    INPUT_SET type;
    input_set1 set1;
    input_set2 set2;
    input_set3 set3;
} MY_INPUT;

void my_func(MY_INPUT input)
{
    switch(input.type)
    {
        case my_input_set1:
        break;
        case my_input_set2:
        break;
        case my_input_set3:
        break;
        default:
        // unknown input
        break;
    }
}

-6

Por que não podemos fazer isso?

Atribua um valor padrão ao argumento opcional. Dessa forma, o chamador da função não precisa necessariamente passar o valor do argumento. O argumento assume o valor padrão. E facilmente esse argumento se torna opcional para o cliente.

Por exemplo

void foo (int a, int b = 0);

Aqui b é um argumento opcional.


10
Visão impressionante, o problema é que C não suporta argumentos opcionais ou funções sobrecarregadas, portanto a solução direta não é compilada.
Thomas
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.