web-dev-qa-db-fra.com

C arguments par défaut

Est-il possible de spécifier les arguments par défaut d'une fonction en C?

243
Nathaniel Flath

Pas vraiment. Le seul moyen serait de écrire une fonction varargs et de renseigner manuellement les valeurs par défaut des arguments que l'appelant ne transmet pas.

125
Eli Courtwright

Wow, tout le monde est tellement pessimiste ici. La réponse est oui. 

Ce n'est pas trivial: à la fin, nous aurons la fonction principale, une structure de support, une fonction wrapper et une macro .__ autour de la fonction wrapper. Dans mon travail, j'ai un ensemble de macros pour automatiser tout cela. une foisvous comprenez le flux, il vous sera facile de faire de même.

Je l'ai écrit ailleurs, alors voici un lien externe détaillé pour compléter le résumé ici: http://modelingwithdata.org/Arch/00000022.htm

Nous aimerions tourner

double f(int i, double x)

dans une fonction qui prend les valeurs par défaut (i = 8, x = 3.14). Définir une structure compagnon:

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

Renommez votre fonction f_base et définissez une fonction wrapper qui définit les valeurs par défaut et appelle 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);
}

Ajoutez maintenant une macro en utilisant les macros variadiques de C. De cette façon, les utilisateurs n'ont pas besoin de savoir qu'ils remplissent une structure f_args et pensent qu'ils font comme d'habitude:

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

OK, maintenant tout ce qui suit fonctionnerait:

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

Vérifiez les règles sur la façon dont les initialiseurs composés définissent les valeurs par défaut pour les règles exactes.

Une chose qui ne fonctionnera pas: f(0), car nous ne pouvons pas faire la distinction entre une valeur manquante etzero. D'après mon expérience, il convient de se méfier de cela, mais cela peut être résolu car Le besoin s'en fait sentir - la moitié du temps que votre valeur par défaut est réellement zéro.

J'ai eu la peine d'écrire cela parce que je pense que les arguments nommés et les valeurs par défaut rend vraiment le codage en C plus facile et encore plus amusant. Et C est génial d’être si simple et d’avoir encore suffisamment d’argent pour rendre tout cela possible.

247
bk.

Oui. :-) Mais pas comme on pourrait s'y attendre.

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

Malheureusement, C ne vous permet pas de surcharger les méthodes afin de vous retrouver avec deux fonctions différentes. Pourtant, en appelant f2, vous appelleriez réellement f1 avec une valeur par défaut. Il s’agit d’une solution "Ne vous répétez pas" qui vous évite de copier/coller du code existant.

151
Wim ten Brink

Nous pouvons créer des fonctions qui utilisent des paramètres nommés (uniquement) pour les valeurs par défaut. Ceci est la suite de la réponse 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);                                                             
}    

La norme C99 définit que les noms ultérieurs lors de l'initialisation remplacent les éléments précédents. Nous pouvons également avoir des paramètres de position standard, il suffit de modifier la signature de macro et de fonction en conséquence. Les paramètres de valeur par défaut ne peuvent être utilisés que dans le style de paramètre nommé.

Sortie du programme:

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

OpenCV utilise quelque chose comme:

/* 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));

Si l'utilisateur ne sait pas ce qu'il doit écrire, cette astuce peut être utile:

usage example

20
user1129665

Non.

Même la toute dernière norme C99 ne le supporte pas.

18
unwind

Non, c'est une fonctionnalité du langage C++.

17
Alex B

Encore une autre option utilise 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);
13
Chris Lutz

Réponse courte: Non.

Réponse légèrement plus longue: Il existe un ancien, ancien solution de contournement dans lequel vous passez une chaîne que vous parse pour les arguments facultatifs:

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

où opt peut inclure une paire "nom = valeur" ou autre, et que vous appelleriez comme

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

Évidemment, cela n'est utile que de temps en temps. Généralement, lorsque vous souhaitez une interface unique pour une famille de fonctionnalités.


Vous trouvez encore cette approche dans les codes de physique des particules écrits par des programmes professionnels en c ++ (comme par exemple ROOT ). Son principal avantage est qu’elle peut être étendue presque indéfiniment tout en maintenant la compatibilité.

13
dmckee

La meilleure façon de procéder (probablement ou non dans votre cas en fonction de votre situation) est de passer au C++ et de l'utiliser comme "meilleur C". Vous pouvez utiliser C++ sans utiliser de classes, de modèles, de surcharge d’opérateurs ou d’autres fonctionnalités avancées.

Cela vous donnera une variante de C avec surcharge de fonction et paramètres par défaut (et toutes les autres fonctionnalités que vous avez choisi d'utiliser). Vous devez juste être un peu discipliné si vous voulez vraiment utiliser uniquement un sous-ensemble restreint de C++.

Beaucoup de gens diront que c'est une très mauvaise idée d'utiliser le C++ de cette façon, et ils pourraient avoir un point. Mais c'est juste un avis; Je pense qu'il est valide d'utiliser les fonctionnalités de C++ avec lesquelles vous êtes à l'aise sans avoir à acheter tout le contenu. Je pense que le succès du C++ tient en grande partie au fait qu’il a déjà été utilisé de cette manière par un très grand nombre de programmeurs à ses débuts.

12
Michael Burr

Non. 

8
chaos

Oui, avec les fonctionnalités de C99, vous pouvez le faire. Cela fonctionne sans définir de nouvelles structures de données ou autres, et sans que la fonction ait à décider, au moment de l'exécution, comment elle a été appelée et Sans aucune surcharge de calcul.

Pour une explication détaillée voir mon post à

http://gustedt.wordpress.com/2010/06/03/default-arguments-for-c99/

Jens

4
Jens Gustedt

Non, mais vous pouvez envisager d'utiliser un set of fonctions (ou macros) pour effectuer une approximation à l'aide d'arguments par défaut

// 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
David R Tribble

Généralement non, mais dans gcc Vous pouvez rendre le dernier paramètre de funcA () facultatif avec une macro.

Dans funcB (), j'utilise une valeur spéciale (-1) pour signaler que j'ai besoin de la valeur par défaut pour le paramètre '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) );
}
3
sambowry

J’ai amélioré la réponse de Jens Gustedt pour que:

  1. les fonctions en ligne ne sont pas utilisées
  2. les valeurs par défaut sont calculées lors du prétraitement
  3. macros modulaires réutilisables
  4. possible de définir une erreur du compilateur qui correspond de manière significative au cas d'arguments insuffisants pour les valeurs par défaut autorisées
  5. les valeurs par défaut ne sont pas nécessaires pour former la queue de la liste de paramètres si les types d’arguments restent non ambigus
  6. interopte avec C11 _Générique
  7. varie le nom de la fonction par le nombre d'arguments!

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

Scénario d'utilisation simplifiée:

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__)

Et avec _Générique:

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__))

Et avec la sélection de nom de fonction variadic, qui ne peut pas être combinée avec _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
Shelby Moore III

Une autre astuce utilisant des 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;
}

Si un seul argument est passé, b reçoit la valeur par défaut (dans ce cas 15)

1
Keine Lust

OUI

À travers des macros

3 paramètres: 

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

Si vous voulez le 4ème argument, un extra my_func3 doit être ajouté. Notez les changements dans VAR_FUNC, my_func2 et my_func

4 paramètres: 

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

Seule exception que float les valeurs par défaut des variables ne peuvent pas être données (sauf si c'est le dernier argument comme dans le cas des 3 paramètres), car ils ont besoin d'un point ('.'), qui n'est pas accepté dans les arguments de macro. Mais je peux trouver un moyen de contourner ce qui est vu dans la macro my_func2 (de 4 paramètres cas)

Programme

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

Sortie:

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
Ramu

Oui, vous pouvez faire quelque chose de similaire, ici vous devez connaître les différentes listes d'arguments que vous pouvez obtenir, mais vous avez la même fonction pour toutes les gérer.

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