Question

    

Cette question a déjà une réponse ici:

    
            
  •              Que fait l'opérateur virgule?                                      9 réponses                          
  •     
    

Vous la voyez utilisée dans les instructions de boucle, mais sa syntaxe légale n’importe où. Quelles utilisations en avez-vous trouvées ailleurs, le cas échéant?

Était-ce utile?

La solution

Le langage C (ainsi que C ++) est historiquement un mélange de deux styles de programmation complètement différents, que l’on peut appeler "programmation d’instruction". et "programmation d'expression". Comme vous le savez, tous les langages de programmation procéduraux prennent normalement en charge des constructions fondamentales telles que séquençage et branchement (voir Programmation structurée ). Ces constructions fondamentales sont présentes dans les langages C / C ++ sous deux formes: l’une pour la programmation par instructions, l’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 ; . Lorsque vous souhaitez créer des branches, utilisez les instructions if . Vous pouvez également utiliser des cycles et d'autres types d'instructions de transfert de contrôle.

Dans la programmation d'expression, les mêmes constructions vous sont également disponibles. C’est 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 , dans la programmation d'expression joue le même rôle que ; dans l'instruction la programmation. Le branchement dans la programmation d'expression se fait via l'opérateur ?: et, alternativement, via les propriétés d'évaluation de court-circuit des opérateurs & amp; & amp; et || . . (La programmation d’expression n’a cependant pas de cycle. Et pour les remplacer par une récursion, vous devez appliquer la programmation des 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 d'instructions traditionnelle, peut être réécrit en termes de programmation d'expression de la manière

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

Il va sans dire que, dans la pratique, la programmation d’instruction produit généralement un code C / C ++ beaucoup plus lisible. Nous utilisons donc normalement la programmation par expression dans des quantités très bien mesurées et limitées. Mais dans de nombreux cas, cela devient pratique. Et la ligne de démarcation entre ce qui est acceptable et ce qui ne l’est pas est dans une large mesure une question de préférence personnelle et de capacité à reconnaître et à lire les idiomes établis.

Comme remarque supplémentaire: la conception même du langage est évidemment adaptée aux déclarations. Les instructions peuvent invoquer librement des expressions, mais celles-ci ne peuvent pas être invoquées (hormis l'appel de fonctions prédéfinies). Cette situation est modifiée de façon assez intéressante dans le compilateur GCC, qui prend en charge ce que l'on appelle " expressions d'expression " en tant qu'extension (symétrique à" expressions d'expression "en standard C). " Expressions " permettre à l'utilisateur d'insérer directement du code basé sur des instructions dans des expressions, de la même manière qu'il peut insérer du code basé sur des expressions dans des instructions en C standard.

Autre remarque supplémentaire: en langage C ++, la programmation par foncteur joue un rôle important, qui peut être considéré comme une autre forme de "programmation par expression". Selon les tendances actuelles en matière de conception C ++, il pourrait être considéré comme préférable à la programmation classique, dans de nombreuses situations.

Autres conseils

Je pense que, d’une manière générale, la virgule de C n’est pas un bon style à utiliser, tout simplement parce que c’est très facile à manquer - que ce soit par quelqu'un qui essaie de lire / comprendre / corriger votre code, ou vous-même un mois plus tard. En dehors des déclarations de variable et pour les boucles for, bien sûr, où il est idiomatique.

Vous pouvez l’utiliser, par exemple, pour intégrer plusieurs instructions à un opérateur ternaire (? :), ala:

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

mais mes dieux, pourquoi?!? (Je l'ai vu utilisé de cette manière dans le code réel, mais je n'y ai malheureusement pas accès)

Je l'ai vu utilisé dans des macros où la macro prétend être une fonction et souhaite renvoyer une valeur mais doit effectuer un autre travail en premier. C'est toujours moche et ressemble souvent à un hack dangereux.

Exemple simplifié:

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

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

Deux fonctionnalités d'opérateur Killer comma en C ++:

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

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

b) Écrivez le code complexe dans les initialiseurs du constructeur:

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

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

Je ne pouvais pas mais le message de log dans le corps du constructeur de verrou dérivé, je devais donc le mettre dans les arguments du constructeur de classe de base en utilisant: baseclass ((log ("message"), actual_arg)) dans la liste d'initialisation. Notez la parenthèse supplémentaire.

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__ << " waits for " << 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__ )
    {
    }

  ....

};

La bibliothèque Boost Assignment est un bon exemple de surcharger l'opérateur de virgule d'une manière utile et lisible. Par exemple:

using namespace boost::assign;

vector<int> v; 
v += 1,2,3,4,5,6,7,8,9;

À partir de la norme C:

  

L'opérande gauche d'un opérateur de virgule est évalué en tant qu'expression vide. il y a un point de séquence après son évaluation. Ensuite, l'opérande de droite est évalué. le résultat a son type et sa valeur. (Un opérateur de virgule ne produit pas de valeur lvalue.)) Si vous tentez de modifier le résultat d'un opérateur de virgule ou d'y accéder après le point de séquence suivant, le comportement n'est pas défini.

En bref, il vous permet de spécifier plusieurs expressions pour lesquelles C n’en attend qu’une. Mais dans la pratique, il est principalement utilisé dans les boucles.

Notez que:

int a, b, c;

N'EST PAS l'opérateur de virgule, c'est une liste de déclarateurs.

Il est parfois utilisé dans les macros, telles que les macros de débogage telles que:

#define malloc(size) (printf("malloc(%d)\n", (int)(size)), malloc((size)))

(Mais regardez cet horrible échec , de votre part, pour ce qui peut arriver lorsque vous en faites trop.)

Mais à moins que vous n'en ayez vraiment besoin, ou que vous soyez sûr que cela rend le code plus lisible et maintenable, je déconseille d'utiliser l'opérateur virgule.

Vous pouvez le surcharger (tant que cette question a une balise "C ++"). J'ai vu du code dans lequel des virgules surchargées étaient utilisées pour générer des matrices. Ou des vecteurs, je ne me souviens pas exactement. N'est-ce pas joli (bien qu'un peu déroutant):

MyVector foo = 2, 3, 4, 5, 6;

En dehors d’une boucle for, et même s’il peut y avoir une odeur de code, le seul endroit que j’ai vu comme une bonne utilisation pour l’opérateur par virgule est dans le cadre d’une suppression:

 delete p, p = 0;

La seule valeur possible est que vous ne pouvez copier / coller accidentellement que la moitié de cette opération si elle est sur deux lignes.

Je l’aime aussi parce que si vous le faites par habitude, vous n’oublierez jamais l’affectation zéro. (Bien entendu, pourquoi p n’est pas à l’intérieur de certains types d’auto_ptr, smart_ptr, shared_ptr, etc wrapper est une question différente.)

Étant donné la citation de @Nicolas Goy dans la norme, il semblerait que vous puissiez écrire une ligne pour les boucles du type:

int a, b, c;
for(a = 0, b = 10; c += 2*a+b, a <= b; a++, b--);
printf("%d", c);

Mais bon Dieu, mec, voulez-vous vraiment rendre votre code C plus obscur de cette manière?

En général, j'évite d'utiliser l'opérateur virgule, car cela rend simplement le code moins lisible. Dans presque tous les cas, il serait plus simple et plus clair de ne faire que deux déclarations. J'aime:

foo=bar*2, plugh=hoo+7;

n'offre aucun avantage évident par rapport à:

foo=bar*2;
plugh=hoo+7;

Le seul endroit à part les boucles où je l'ai utilisé dans les constructions if / else, comme:

if (a==1)
... do something ...
else if (function_with_side_effects_including_setting_b(), b==2)
... do something that relies on the side effects ...

Vous pouvez placer la fonction avant le SI, mais si l'exécution de la fonction est longue, vous pouvez éviter de le faire si ce n'est pas nécessaire, et si la fonction ne doit pas être exécutée à moins qu'un! = 1, alors ce n'est pas une option. L'alternative consiste à imbriquer les FI dans une couche supplémentaire. C'est ce que je fais habituellement parce que le code ci-dessus est un peu cryptique. Mais je l’ai fait de temps en temps par virgule car l’imbrication est aussi cryptée.

Il est très utile d'ajouter des commentaires dans les macros ASSERT :

ASSERT(("This value must be true.", x));

Etant donné que la plupart des macros de style d'assertion génèrent l'intégralité du texte de leur argument, cela ajoute un petit supplément d'informations utiles dans l'assertion.

Je l'utilise souvent pour exécuter une fonction d'initialisation statique dans certains fichiers cpp, afin d'éviter des problèmes d'initalisation paresseux avec les singletons classiques:

void* s_static_pointer = 0;

void init() {
    configureLib(); 
    s_static_pointer = calculateFancyStuff(x,y,z);
    regptr(s_static_pointer);
}

bool s_init = init(), true; // just run init() before anything else

Foo::Foo() {
  s_static_pointer->doStuff(); // works properly
}

Pour moi, le cas vraiment utile avec les virgules en C consiste à les utiliser pour effectuer quelque chose de manière conditionnelle.

  if (something) dothis(), dothat(), x++;

cela équivaut à

  if (something) { dothis(); dothat(); x++; }

Il ne s’agit pas de "taper moins", c’est très clair parfois.

Les boucles sont comme ça:

while(true) x++, y += 5;

Bien sûr, les deux ne peuvent être utiles que lorsque la partie conditionnelle ou exécutable de la boucle est assez petite, deux à trois opérations.

La seule fois où j'ai jamais vu l'opérateur , utilisé en dehors d'une boucle pour a été d'effectuer une détermination dans une instruction ternaire. C'était il y a longtemps, donc je ne peux pas me souvenir de la déclaration exacte, mais c'était à peu près comme:

int ans = isRunning() ? total += 10, newAnswer(total) : 0;

Évidemment, aucune personne sensée n’écrirait un code comme celui-ci, mais l’auteur était un génie diabolique qui construisait des instructions c en se basant sur le code assembleur qu’il avait généré, et non sur la lisibilité. Par exemple, il utilisait parfois des boucles plutôt que des instructions if car il préférait l'assembleur qu'il générait.

Son code était très rapide mais impossible à maintenir, je suis heureux de ne plus avoir à le travailler.

Je l'ai utilisé pour une macro "assigner une valeur de n'importe quel type à un tampon de sortie pointé par un caractère *, puis incrémenter le pointeur du nombre d'octets requis", comme ceci:

#define ASSIGN_INCR(p, val, type)  ((*((type) *)(p) = (val)), (p) += sizeof(type))

L'utilisation de l'opérateur virgule signifie que la macro peut être utilisée dans des expressions ou en tant qu'énoncés à votre guise:

if (need_to_output_short)
    ASSIGN_INCR(ptr, short_value, short);

latest_pos = ASSIGN_INCR(ptr, int_value, int);

send_buff(outbuff, (int)(ASSIGN_INCR(ptr, last_value, int) - outbuff));

Cela a réduit le nombre de saisies répétitives, mais vous devez faire attention à ce que cela ne soit pas trop illisible.

Voir ma version trop longue de cette réponse ici .

Cela peut être pratique pour "code golf":

Code Golf: Jouer des cubes

Le , dans si (i> 0) t = i, i = 0; enregistre deux caractères.

qemu a un code qui utilise l'opérateur de virgule dans la partie conditionnelle d'une boucle for (voir QTAILQ_FOREACH_SAFE dans qemu-queue.h). Ce qu’ils ont fait se résume à ce qui suit:

#include <stdio.h>

int main( int argc, char* argv[] ){
  int x = 0, y = 0;

  for( x = 0; x < 3 && (y = x+1,1); x = y ){
    printf( "%d, %d\n", x, y );
  }

  printf( "\n%d, %d\n\n", x, y );

  for( x = 0, y = x+1; x < 3; x = y, y = x+1 ){
    printf( "%d, %d\n", x, y );
  }

  printf( "\n%d, %d\n", x, y );
  return 0;
}

... avec la sortie suivante:

0, 1
1, 2
2, 3

3, 3

0, 1
1, 2
2, 3

3, 4

La première version de cette boucle a les effets suivants:

  • Cela évite de faire deux assignations, ce qui réduit les chances que le code ne se synchronise pas
  • Puisqu'elle utilise & amp; , l'affectation n'est pas évaluée après la dernière itération
  • Etant donné que l'assignation n'est pas évaluée, elle n'essaiera pas de dé-référencer le prochain élément de la file d'attente à la fin (dans le code de qemu, pas dans le code ci-dessus).
  • Dans la boucle, vous avez accès aux éléments actuel et suivant

trouvé dans l'initialisation du tableau:

En C, que se passe-t-il si j’utilise () pour initialiser un tableau à double dimension au lieu de {}?

Lorsque j'initialise un tableau a [] [] :

int a[2][5]={(8,9,7,67,11),(7,8,9,199,89)};

puis affichez les éléments du tableau.

je reçois:

11 89 0 0 0 
0 0 0 0 0
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top