Argomenti predefiniti C.


279

C'è un modo per specificare argomenti predefiniti per una funzione in C?


6
voglio solo un C leggermente migliore, non C ++. pensa C +. con una varietà di piccoli miglioramenti sollevati dal C ++, ma non il grande casino. E, per favore, nessun altro link-loader. dovrebbe essere solo un altro passo simile al preprocessore. standardizzato. ovunque ...
ivo Welch,

Domanda correlata che non ho visto elencato nella barra laterale.
Shelby Moore III,

Direi di smettere di essere un barbaro e di imparare a usare il C ++ (11, ...) bene - jk! / io spengo fiamme ... ma ... ti innamorerai ... ahahah non posso fare a meno, scusa.
moodboom

Risposte:


147

Non proprio. L'unico modo sarebbe scrivere una funzione varargs e compilare manualmente i valori predefiniti per gli argomenti che il chiamante non passa.


22
Odio la mancanza di controllo quando si usa Varargs.
dmckee --- ex-moderatore gattino

16
Anche tu dovresti; In realtà non lo consiglio; Volevo solo comunicare che è possibile.
Eli Courtwright,

4
Tuttavia, come si desidera verificare se il chiamante passa l'argomento o no? Penso che funzioni, non hai il chiamante per dirti che non l'ha superato? Penso che questo renda l'intero approccio un po 'meno utilizzabile: il chiamante potrebbe anche chiamare una funzione con un altro nome.
Johannes Schaub - litb,

3
La open(2)chiamata di sistema lo utilizza per un argomento facoltativo che può essere presente a seconda degli argomenti richiesti e printf(3)legge una stringa di formato che specifica quanti argomenti ci saranno. Entrambi usano i vararg in modo abbastanza sicuro ed efficace, e sebbene tu possa certamente rovinarli, in printf()particolare sembra essere abbastanza popolare.
Chris Lutz,

4
@Eli: non tutti i compilatori C sono gcc. C'è qualche magia avanzata del compilatore in corso per avvisare quando i tuoi argomenti printf () non corrispondono alla tua stringa di formato. E non penso che sia possibile ottenere avvisi simili per le proprie funzioni variadiche (a meno che non utilizzino lo stesso stile di stringa di formato).
tomlogic,

280

Caspita, tutti sono così pessimisti da queste parti. La risposta è si.

Non è banale: alla fine avremo la funzione core, una struttura di supporto, una funzione wrapper e una macro attorno alla funzione wrapper. Nel mio lavoro ho una serie di macro per automatizzare tutto ciò; una volta compreso il flusso, sarà facile per te fare lo stesso.

L'ho scritto altrove, quindi ecco un link esterno dettagliato per integrare il riepilogo qui: http://modelingwithdata.org/arch/00000022.htm

Vorremmo girare

double f(int i, double x)

in una funzione che accetta i valori predefiniti (i = 8, x = 3.14). Definisci una struttura di accompagnamento:

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

Rinomina la tua funzione f_basee definisci una funzione wrapper che imposta i valori predefiniti e chiama la 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);
}

Ora aggiungi una macro, usando le macro variadiche di C. In questo modo gli utenti non devono sapere che stanno effettivamente popolando una f_argsstruttura e pensano di fare il solito:

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

OK, ora tutte le seguenti funzioni:

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

Controlla le regole su come gli inizializzatori composti impostano i valori predefiniti per le regole esatte.

Una cosa che non funzionerà: f(0)perché non possiamo distinguere tra un valore mancante e zero. Nella mia esperienza, questo è qualcosa a cui prestare attenzione, ma può essere curato in caso di necessità --- la metà delle volte il valore predefinito è davvero zero.

Ho affrontato il problema di scrivere questo perché penso che argomenti e valori predefiniti in realtà rendano la codifica in C più semplice e ancora più divertente. E C è fantastico per essere così semplice e avere ancora abbastanza per rendere tutto ciò possibile.


16
+1 creativo! Ha i suoi limiti ma porta anche parametri denominati nella tabella. Si noti che, {}(inizializzatore vuoto) è un errore C99.
u0b34a0f6ae,

28
Tuttavia, ecco qualcosa di eccezionale per te: lo standard consente di specificare più volte i membri nominati, la sostituzione successiva. Pertanto, solo per i parametri nominati è possibile risolvere il problema dei valori predefiniti e consentire una chiamata vuota. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae,

3
Spero che gli errori del compilatore siano leggibili, ma questa è un'ottima tecnica! Sembra quasi kwargs di pitone.
Totowdue

6
@RunHolt Sebbene certamente più semplice, non è oggettivamente migliore; i parametri nominati offrono vantaggi quali la facilità di lettura delle chiamate (a scapito della leggibilità del codice sorgente). Uno è migliore per gli sviluppatori della fonte, l'altro è migliore per gli utenti della funzione. È un po 'frettoloso buttare fuori "questo è meglio!"
Alice,

3
@DawidPi: C11 6.7.9 (19), sull'inizializzazione degli aggregati: "tutti gli oggetti secondari che non sono stati inizializzati in modo esplicito devono essere inizializzati implicitamente come gli oggetti che hanno una durata di archiviazione statica" E come sai, gli elementi di durata statica sono inizializzati a zero | NULL | \ 0. [Anche questo era nel c99.]
bk.

161

Sì. :-) Ma non in un modo che ti aspetteresti.

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");
}

Sfortunatamente, C non ti consente di sovraccaricare i metodi in modo da finire con due diverse funzioni. Tuttavia, chiamando f2, si chiamerebbe effettivamente f1 con un valore predefinito. Questa è una soluzione "Non ripetere te stesso", che ti aiuta a evitare di copiare / incollare il codice esistente.


24
FWIW, preferirei usare il numero alla fine della funzione per indicare il numero di argomenti necessari. Rende più semplice il semplice utilizzo di qualsiasi numero arbitrario. :)
Macke,

4
Questa è di gran lunga la risposta migliore perché dimostra un modo semplice per raggiungere lo stesso obiettivo. Ho una funzione che fa parte di un'API fissa che non voglio modificare, ma ne ho bisogno per prendere un nuovo parametro. Certo, è così palesemente ovvio che l'ho perso (mi sono bloccato pensando al parametro predefinito!)
RunHolt,

4
f2 potrebbe anche essere una macro del preprocessore
osvein

41

Siamo in grado di creare funzioni che utilizzano (solo) parametri denominati per i valori predefiniti. Questa è una continuazione della risposta di 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);                                                             
}    

Lo standard C99 definisce che i nomi successivi nell'inizializzazione sovrascriveranno gli elementi precedenti. Possiamo anche avere alcuni parametri posizionali standard, basta cambiare la macro e la firma della funzione di conseguenza. I parametri del valore predefinito possono essere utilizzati solo nello stile di parametro denominato.

Uscita del programma:

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

2
Sembra un'implementazione più semplice e diretta rispetto alla soluzione Wim ten Brink o BK. Ci sono degli svantaggi di questa implementazione che gli altri non possiedono?
stephenmm,

24

OpenCV usa qualcosa come:

/* 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 l'utente non sa cosa dovrebbe scrivere, questo trucco può essere utile:

esempio di utilizzo


19

No.

Nemmeno l'ultimo standard C99 supporta questo.


un semplice no sarebbe stato anche meglio;)
KevinDTimm

21
@kevindtimm: questo non è possibile, SO richiede una lunghezza minima per le risposte. Provai. :)
Rilassati il

2
Si prega di fare riferimento alla mia risposta. :)
caos

18

No, questa è una funzionalità del linguaggio C ++.


14

Risposta breve: No.

Un po 'più a lungo risposta: C'è una vecchia, vecchia soluzione in cui si passa una stringa che si analizza per argomenti opzionali:

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

dove opt può includere una coppia "name = value" o qualcosa del genere, e che chiamereste come

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

Ovviamente questo è utile solo occasionalmente. Generalmente quando si desidera un'unica interfaccia per una famiglia di funzionalità.


Trovi ancora questo approccio nei codici di fisica delle particelle che sono scritti da programmi professionali in c ++ (come ad esempio ROOT ). Il suo principale vantaggio è che può essere esteso quasi indefinitamente mantenendo la retrocompatibilità.


2
Combina questo con varargs e ti diverti in tutti i modi!
David Thornley,

5
Vorrei utilizzare un'abitudine structe fare in modo che il chiamante ne creasse uno, compilasse i campi per le diverse opzioni, quindi lo passasse per indirizzo o passasse NULLper le opzioni predefinite.
Chris Lutz,

Copiare modelli di codice da ROOT è un'idea terribile!
Innisfree

13

Probabilmente il modo migliore per farlo (che può o non è possibile nel tuo caso a seconda della situazione) è quello di passare a C ++ e usarlo come 'una C migliore'. È possibile utilizzare C ++ senza utilizzare classi, modelli, overloading dell'operatore o altre funzionalità avanzate.

Questo ti darà una variante di C con sovraccarico della funzione e parametri predefiniti (e qualunque altra caratteristica tu abbia scelto di usare). Devi solo essere un po 'disciplinato se sei davvero serio nell'usare solo un sottoinsieme limitato di C ++.

Molte persone diranno che è una pessima idea usare C ++ in questo modo, e potrebbero avere ragione. Ma è solo un'opinione; Penso che sia valido utilizzare le funzionalità di C ++ con cui ti senti a tuo agio senza dover acquistare tutto. Penso che una parte significativa della ragione del successo del C ++ sia che è stato utilizzato da moltissimi programmatori nei suoi primi giorni esattamente in questo modo.


13

Ancora un'altra opzione 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

No.


2
qual è la soluzione? Vedo che è 20202020in esadecimale, ma come lo digito?
Lazer,

5

Un altro trucco con le macro:

#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 viene passato solo un argomento, briceve il valore predefinito (in questo caso 15)


4

No, ma potresti prendere in considerazione l'utilizzo di un insieme di funzioni (o macro) per approssimare l'utilizzo degli argomenti predefiniti:

// 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

Generalmente no, ma in gcc È possibile rendere l'ultimo parametro di funcA () opzionale con una macro.

In funcB () uso un valore speciale (-1) per segnalare che ho bisogno del valore predefinito per il parametro '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

Ho migliorato la risposta di Jens Gustedt in modo che:

  1. le funzioni inline non sono utilizzate
  2. le impostazioni predefinite vengono calcolate durante la preelaborazione
  3. macro riutilizzabili modulari
  4. possibile impostare un errore del compilatore che corrisponda in modo significativo al caso di argomenti insufficienti per le impostazioni predefinite consentite
  5. le impostazioni predefinite non sono necessarie per formare la coda dell'elenco dei parametri se i tipi di argomento rimarranno inequivocabili
  6. interagisce con C11 _Generico
  7. variare il nome della funzione in base al numero di argomenti!

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

Scenario di utilizzo semplificato:

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 con _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 con la selezione del nome della funzione variadica, che non può essere combinata con _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

Attraverso le macro

3 parametri:

#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 si desidera il 4 ° argomento, è necessario aggiungere un ulteriore my_func3. Notare le modifiche in VAR_FUNC, my_func2 e my_func

4 parametri:

#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);
}

Unica eccezione a quel galleggiante variabili non possono essere assegnati valori predefiniti (a meno che non sia l'ultimo argomento come nel caso dei 3 parametri ), poiché hanno bisogno del punto ('.'), Che non è accettato all'interno degli argomenti macro. Ma può capire come aggirare il problema visto nella macro my_func2 ( del caso di 4 parametri )

Programma

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;
}

Produzione:

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

Sì, puoi fare qualcosa di simile, qui devi conoscere i diversi elenchi di argomenti che puoi ottenere ma hai la stessa funzione per gestire tutto.

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

Perché non possiamo farlo.

Assegna all'argomento facoltativo un valore predefinito. In tal modo, il chiamante della funzione non deve necessariamente passare il valore dell'argomento. L'argomento accetta il valore predefinito. E facilmente tale argomento diventa facoltativo per il cliente.

Per es

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

Qui b è un argomento facoltativo.


10
Incredibile intuizione, il problema è che C non supporta argomenti opzionali o funzioni sovraccaricate, quindi la soluzione diretta non viene compilata.
Thomas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.