93 votes

Utilisations de l'opérateur virgule C

Vous le voyez utilisé dans les déclarations de boucle for, mais c'est une syntaxe légale n'importe où. Quels usages avez-vous trouvés pour cela ailleurs, le cas échéant?

21 votes

Je crois que toutes les "utilisations délicates" de l'opérateur virgule rendent le code moins lisible.

2 votes

Je suis d'accord. L'abus de la virgule rend votre code C beaucoup moins lisible.

0 votes

105voto

AndreyT Points 139512

Le langage C (tout comme le C++) est historiquement un mélange de deux styles de programmation complètement différents, que l'on peut qualifier de "programmation par instructions" et de "programmation par expressions". Comme vous le savez, chaque langage procédural prend normalement en charge des constructions fondamentales telles que le séquençage et le branchement (voir Programmation structurée). Ces constructions fondamentales sont présentes dans les langages C/C++ sous deux formes : une pour la programmation par instructions, une autre pour la programmation par expressions.

Par exemple, lorsque vous écrivez votre programme en termes d'instructions, vous pouvez utiliser une séquence d'instructions séparées par ;. Pour réaliser des branchements, vous utilisez des instructions if. Vous pouvez également utiliser des boucles et d'autres types d'instructions de transfert de contrôle.

En programmation par expressions, les mêmes constructions sont également disponibles. C'est en fait là que l'opérateur , entre en jeu. L'opérateur , n'est rien d'autre qu'un séparateur d'expressions séquentielles en C, c'est-à-dire que l'opérateur , en programmation par expressions remplit le même rôle que ; en programmation par instructions. Le branchement en programmation par expressions se fait à travers l'opérateur ?: et, alternativement, à travers les propriétés d'évaluation en court-circuit des opérateurs && et || (la programmation par expressions n'a pas de boucles cependant. Et pour les remplacer par de la récursivité, il faudrait appliquer la programmation par instructions).

Par exemple, le code suivant

a = rand();
++a;
b = rand();
c = a + b / 2;
if (a < c - 5)
  d = a;
else
  d = b;

qui est un exemple de programmation par instructions traditionnelle, peut être réécrit en termes de programmation par expressions comme

a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? d = a : d = b;

ou comme

a = rand(), ++a, b = rand(), c = a + b / 2, d = a < c - 5 ? a : b;

ou

d = (a = rand(), ++a, b = rand(), c = a + b / 2, a < c - 5 ? a : b);

ou

a = rand(), ++a, b = rand(), c = a + b / 2, (a < c - 5 && (d = a, 1)) || (d = b);

Bien entendu, en pratique, la programmation par instructions produit généralement un code C/C++ beaucoup plus lisible, donc nous utilisons normalement la programmation par expressions de manière très mesurée et restreinte. Mais dans de nombreux cas, elle est utile. Et la frontière entre ce qui est acceptable et ce qui ne l'est pas est en grande partie une question de préférence personnelle et de capacité à reconnaître et lire des idiomes établis.

À titre d'information supplémentaire : la conception même du langage est clairement adaptée aux instructions. Les instructions peuvent librement invoquer des expressions, mais les expressions ne peuvent pas invoquer des instructions (à part l'appel de fonctions prédéfinies). Cette situation est modifiée de manière assez intéressante dans le compilateur GCC, qui prend en charge les dites "expressions d'instructions" comme une extension (symétrique des "instructions d'expressions" dans le C standard). Les "expressions d'instructions" permettent à l'utilisateur d'insérer directement du code basé sur des instructions dans des expressions, tout comme ils peuvent insérer du code basé sur des expressions dans des instructions dans le C standard.

À titre d'information supplémentaire : dans le langage C++, la programmation basée sur les foncteurs joue un rôle important, qui peut être considéré comme une autre forme de "programmation par expressions". Selon les tendances actuelles en matière de conception C++, cela pourrait être préféré à la programmation par instructions traditionnelle dans de nombreuses situations.

32voto

Jack Lloyd Points 4509

Je pense que généralement, la virgule en C n'est pas un bon style à utiliser simplement parce qu'elle est si facile à manquer - que ce soit par quelqu'un d'autre essayant de lire/comprendre/réparer votre code, ou vous-même un mois plus tard. En dehors des déclarations de variables et des boucles for, bien sûr, où c'est idiomatique.

Vous pouvez l'utiliser, par exemple, pour regrouper plusieurs instructions dans un opérateur ternaire (?:), comme :

int x = some_bool ? printf("WTF"), 5 : fprintf(stderr, "Non, vraiment, WTF"), 117; 

mais bon sang, pourquoi ?!? (Je l'ai vu utilisé de cette manière dans du vrai code, mais je n'y ai malheureusement pas accès pour le montrer)

11 votes

Difficile, je ne savais pas ça. Le C(++) a vraiment trop de 'fonctionnalités' qui ne sont bonnes que pour provoquer d'interminables rires idiots lors des soirées cocktails.

19 votes

C'est certainement délicat car vous mélangez la virgule op avec la virgule dans les déclarations xD Maintenant, que ce soit légal ou non, je ne le savais pas mais le Standard sait :) Le fait est : vous devez mettre entre parenthèses le troisième opérande de op?:, sinon la liaison est la suivante : int x = (cond ? A : B), 117; xD

1 votes

@litb Bonne trouvaille! Je ne vais pas le changer car cela rend l'exemple particulièrement confus / obscur, ce qui était en quelque sorte le but de toute façon. :)

23voto

Pavel Shved Points 34706

Deux fonctionnalités tueuses de l'opérateur virgule en C++ :

a) Lire à partir du flux jusqu'à ce qu'une chaîne spécifique soit rencontrée (aide à garder le code DRY) :

 while (cin >> str, str != "STOP") {
   //process str
 }

b) Écrire du code complexe dans les initialiseurs de constructeur :

class X : public A {
  X() : A( (global_function(), global_result) ) {};
};

1 votes

En ce qui concerne a), vous seriez mieux avec while (cin >> str && str != ""), bien qu'il puisse y avoir d'autres utilisations similaires.

3 votes

@UncleBens, cin >> str renvoie iostream, qui se convertira en valeur booléenne false lorsque la fin du fichier est atteinte, et non lorsqu'une chaîne vide est rencontrée !

2 votes

Ne cela signifie-t-il pas que votre code restera bloqué sur la dernière ligne d'un fichier? cin >> str ne va pas écraser str (je pense?), et str != "" sera éternellement vrai.

22voto

Adisak Points 3328

J'ai vu cela utilisé dans des macros où la macro fait semblant d'être une fonction et veut renvoyer une valeur mais doit d'abord effectuer un autre travail. C'est toujours laid et ressemble souvent à un hack dangereux cependant.

Exemple simplifié:

#define SomeMacro(A) ( DoWork(A), Permute(A) )

Ici, B=SomeMacro(A) "retourne" le résultat de Permute(A) et l'assigne à "B".

9 votes

+1 bien que je ne sois pas d'accord que c'est laid et dangereux. Il suffit de déclarer vos macros avec prudence et vous êtes en sécurité

10voto

fa. Points 2117

J'ai dû utiliser une virgule pour déboguer les verrous mutex afin de mettre un message avant que le verrou commence à attendre.

Je n'ai pas pu mettre le message de journalisation dans le corps du constructeur de verrou dérivé, donc j'ai dû le mettre dans les arguments du constructeur de la classe de base en utilisant : baseclass((log("message"), actual_arg)) dans la liste d'initialisation. Notez les parenthèses supplémentaires.

Voici un extrait des classes :

class NamedMutex : public boost::timed_mutex
{
public:
    ...

private:
    std::string name_ ;
};

void log( NamedMutex & ref__ , std::string const& name__ )
{
    LOG( name__ << " attend pour " << ref__.name_ );
}

class NamedUniqueLock : public boost::unique_lock< NamedMutex >
{
public:

    NamedUniqueLock::NamedUniqueLock(
        NamedMutex & ref__ ,
        std::string const& name__ ,
        size_t const& nbmilliseconds )
    :
        boost::unique_lock< NamedMutex >( ( log( ref__ , name__ ) , ref__ ) ,
            boost::get_system_time() + boost::posix_time::milliseconds( nbmilliseconds ) ),
            ref_( ref__ ),
            name_( name__ )
    {
    }

  ....

};

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X