Arguments par défaut C


279

Existe-t-il un moyen de spécifier des arguments par défaut à une fonction en C?


6
je veux juste un C légèrement meilleur, pas C ++. pensez C +. avec une variété de petites améliorations tirées du C ++, mais pas le gros bordel. Et, s'il vous plaît, pas de chargeur de lien différent. devrait être juste une autre étape de type préprocesseur. standardisé. partout ...
ivo Welch

Question connexe que je n'ai pas vue répertoriée dans la barre latérale.
Shelby Moore III

Je dirais d'arrêter d'être barbare et d'apprendre à bien utiliser C ++ (11, ...) - jk! / moi éteint les flammes ... mais ... vous allez adorer ça ... hahaha je ne peux pas m'en empêcher, désolé.
moodboom

Réponses:


147

Pas vraiment. La seule façon serait d' écrire une fonction varargs et de remplir manuellement les valeurs par défaut pour les arguments que l'appelant ne transmet pas.


22
Je déteste le manque de vérification lors de l'utilisation de varargs.
dmckee --- chaton ex-modérateur

16
Vous devriez aussi; En fait, je ne recommande pas cela; Je voulais juste dire que c'est possible.
Eli Courtwright

4
Cependant, comment voulez-vous vérifier si l'appelant passe l'argument ou non? Je pense que pour que ça marche, vous n'avez pas l'appelant pour vous dire qu'il ne l'a pas réussi? Je pense que cela rend l'approche un peu moins utilisable - l'appelant pourrait également appeler une fonction avec un autre nom.
Johannes Schaub - litb

3
L' open(2)appel système l'utilise pour un argument facultatif qui peut être présent en fonction des arguments requis et printf(3)lit une chaîne de format qui spécifie le nombre d'arguments. Les deux utilisent des varargs de manière assez sûre et efficace, et bien que vous puissiez certainement les visser, cela printf()semble particulièrement populaire.
Chris Lutz

4
@Eli: Tous les compilateurs C ne sont pas gcc. Il y a une magie de compilation avancée qui prévient lorsque vos arguments printf () ne correspondent pas à votre chaîne de format. Et je ne pense pas qu'il soit possible d'obtenir des avertissements similaires pour vos propres fonctions variadiques (à moins qu'elles n'utilisent le même style de chaîne de format).
tomlogic

280

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

Ce n'est pas anodin: à 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 fois que vous aurez compris le flux, il vous sera facile de faire de même.

J'ai écrit cela 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éfinissez une structure associée:

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

Renommez votre fonction f_baseet 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 à savoir qu'ils remplissent réellement une f_argsstructure et pensent qu'ils font l'habituel:

#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)parce que nous ne pouvons pas distinguer entre une valeur manquante et zéro. D'après mon expérience, c'est quelque chose à surveiller, mais peut être pris en charge lorsque le besoin s'en fait sentir --- la moitié du temps, votre valeur par défaut est vraiment nulle.

J'ai eu du mal à écrire ceci parce que je pense que les arguments nommés et les valeurs par défaut rendent le codage en C plus facile et encore plus amusant. Et C est génial pour être si simple et en avoir encore assez pour rendre tout cela possible.


16
+1 créatif! Il a ses limites mais apporte également des paramètres nommés à la table. Notez que {}(initialiseur vide) est une erreur C99.
u0b34a0f6ae

28
Cependant, voici quelque chose de génial pour vous: la norme permet de spécifier plusieurs fois les membres nommés, la substitution ultérieure. Ainsi, pour les paramètres nommés uniquement, vous pouvez résoudre le problème des valeurs par défaut et autoriser un appel vide. #define vrange(...) CALL(range,(param){.from=1, .to=100, .step=1, __VA_ARGS__})
u0b34a0f6ae

3
J'espère que les erreurs du compilateur sont lisibles, mais c'est une excellente technique! Ressemble presque à des kwargs en python.
totowtwo

6
@RunHolt Bien que certainement plus simple, ce n'est pas objectivement meilleur; les paramètres nommés présentent des avantages tels que la facilité de lisibilité des appels (au détriment de la lisibilité du code source). L'une est meilleure pour les développeurs de la source, l'autre est meilleure pour les utilisateurs de la fonction. C'est un peu hâtif de simplement jeter "celui-ci est meilleur!"
Alice

3
@DawidPi: C11 6.7.9 (19), sur l'initialisation des agrégats: "tous les sous-objets qui ne sont pas initialisés explicitement doivent être initialisés implicitement de la même manière que les objets qui ont une durée de stockage statique" Et comme vous le savez, les éléments de durée statique sont initialisés à zéro | NULL | \ 0. [C'était aussi en c99.]
bk.

161

Oui. :-) Mais pas d'une manière que vous attendez.

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, vous vous retrouveriez donc avec deux fonctions différentes. Pourtant, en appelant f2, vous appelleriez en fait f1 avec une valeur par défaut. Il s'agit d'une solution «Ne vous répétez pas», qui vous aide à éviter de copier / coller du code existant.


24
FWIW, je préfère utiliser le nombre à la fin de la fonction pour indiquer le nombre d'arguments qu'il faut. Rend plus facile que d'utiliser simplement un nombre arbitraire. :)
Macke

4
C'est de loin la meilleure réponse car elle démontre un moyen simple d'atteindre le même objectif. J'ai une fonction qui fait partie d'une API fixe que je ne veux pas changer, mais j'en ai besoin pour prendre un nouveau paramètre. Bien sûr, il est tellement évident que je l'ai raté (je suis resté coincé en pensant au
paramètre

4
f2 pourrait également être une macro de préprocesseur
osvein

41

Nous pouvons créer des fonctions qui utilisent des paramètres nommés (uniquement) pour les valeurs par défaut. Ceci est une continuation 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 dans l'initialisation remplacent les éléments précédents. Nous pouvons également avoir certains 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

2
Cela semble être une implémentation plus simple et plus simple que la solution Wim ten Brink ou BK. Y a-t-il des inconvénients à cette mise en œuvre que les autres ne possèdent pas non plus?
stephenmm

24

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:

exemple d'utilisation


19

Non.

Même la toute dernière norme C99 ne le prend pas en charge.


un simple non aurait été encore mieux;)
KevinDTimm

21
@kevindtimm: Ce n'est pas possible, SO impose une longueur minimale sur les réponses. J'ai essayé. :)
détendez-vous le

2
Veuillez vous référer à ma réponse. :)
chaos

18

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


14

Réponse courte: Non.

Réponse légèrement plus longue: il existe une ancienne, ancienne solution de contournement où vous passez une chaîne que vous analysez pour des arguments facultatifs:

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

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

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

De toute évidence, cela n'est utile qu'occasionnellement. Généralement lorsque vous souhaitez une interface unique à une famille de fonctionnalités.


Vous trouvez toujours cette approche dans les codes de physique des particules qui sont écrits par des programmes professionnels en c ++ (comme par exemple ROOT ). Son principal avantage est qu'il peut être étendu presque indéfiniment tout en conservant la rétrocompatibilité.


2
Combinez cela avec des varargs et vous vous amuserez de toutes sortes!
David Thornley,

5
J'utiliserais une coutume structet je demanderais à l'appelant d'en créer une, de remplir les champs pour différentes options, puis de la transmettre par adresse ou de passer NULLpour les options par défaut.
Chris Lutz

La copie de modèles de code depuis ROOT est une idée terrible!
innisfree

13

La meilleure façon de le faire (ce qui peut ou non être possible dans votre cas selon votre situation) est probablement de passer au C ++ et de l'utiliser comme «un meilleur C». Vous pouvez utiliser C ++ sans utiliser de classes, de modèles, de surcharge d'opérateur 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 quelles que soient les autres fonctionnalités que vous choisissiez 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 C ++ de cette façon, et ils pourraient avoir raison. Mais c'est juste une opinion; Je pense qu'il est valable d'utiliser des fonctionnalités de C ++ avec lesquelles vous êtes à l'aise sans avoir à acheter tout cela. Je pense qu'une partie importante de la raison du succès du C ++ est qu'il a été utilisé par un grand nombre de programmeurs à ses débuts exactement de cette façon.


13

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

9

Non.


2
quelle est la solution de contournement? Je peux voir qu'il est 20202020en hexadécimal, mais comment le taper?
Lazer

5

Une autre astuce à l'aide de 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é, breçoit la valeur par défaut (dans ce cas 15)


4

Non, mais vous pourriez envisager d'utiliser un ensemble de fonctions (ou macros) pour approximer à 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
}

4

Oui, avec les fonctionnalités de C99, vous pouvez le faire. Cela fonctionne sans définir de nouvelles structures de données ou ainsi et sans que la fonction doive 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 article sur

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

Jens


Voir aussi ma réponse qui est dérivée de la vôtre.
Shelby Moore III du

1
Insérez un exemple.
user877329

3

Généralement non, mais dans gcc Vous pouvez rendre le dernier paramètre de funcA () optionnel 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) );
}

1

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 pendant le prétraitement
  3. macros réutilisables modulaires
  4. possibilité de définir une erreur de compilation 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 des paramètres si les types d'arguments restent sans ambiguïté
  6. interopte avec C11 _Generic
  7. faites varier 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é:

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

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

OUI

Grâce aux 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, alors un my_func3 supplémentaire 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: les variables flottantes ne peuvent pas recevoir de valeurs par défaut ( sauf si c'est le dernier argument comme dans le cas des 3 paramètres ), car elles ont besoin de point ('.'), Ce qui n'est pas accepté dans les arguments de macro. Mais peut comprendre une solution de contournement comme on le voit dans la macro my_func2 (cas de 4 paramètres )

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

Production:

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

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

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

Pourquoi ne pouvons-nous pas faire cela.

Donnez à l'argument facultatif une valeur par défaut. De cette façon, l'appelant de la fonction n'a pas nécessairement besoin de passer la valeur de l'argument. L'argument prend la valeur par défaut. Et facilement cet argument devient facultatif pour le client.

Par exemple

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

Ici b est un argument facultatif.


10
Une vue étonnante, le problème est que C ne prend pas en charge les arguments facultatifs ou les fonctions surchargées, donc la solution directe ne compile pas.
Thomas
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.