Nous pouvons écrire une instruction if
comme
if (a == 5, b == 6, ... , thisMustBeTrue)
et seule la dernière condition doit être satisfaisante pour entrer dans le corps if
.
pourquoi est-ce permis?
Changer légèrement votre exemple, supposons que ce soit
if ( a = f(5), b = f(6), ... , thisMustBeTrue(a, b) )
(notez le =
au lieu de ==
). Dans ce cas, les virgules garantissent un ordre d'évaluation de gauche à droite. En contraste, avec ce
if ( thisMustBeTrue(f(5), f(6)) )
vous ne savez pas si f(5)
est appelée avant ou après f(6)
.
Plus formellement, les virgules vous permettent d'écrire une séquence d'expressions (a,b,c)
De la même manière que vous pouvez utiliser ;
Pour écrire une séquence d'instructions a; b; c;
. Et tout comme un ;
Crée un point de séquence (fin de l'expression complète), une virgule aussi. Seuls les points de séquence régissent l'ordre d'évaluation, voir cet article .
Mais bien sûr, dans ce cas, vous écririez
a = f(5);
b = f(6);
if ( thisMustBeTrue(a, b) )
Alors, quand une séquence d'expressions séparées par des virgules est-elle préférable à une séquence d'instructions séparées par ;
? Je ne dirais presque jamais. Peut-être dans une macro lorsque vous souhaitez que le remplacement de droite soit une seule expression.
En bref: Bien qu'il soit légal de le faire, cela n'a généralement aucun sens d'utiliser l'opérateur virgule dans la partie condition d'un if
ou while
statement (EDIT: Bien que ce dernier puisse parfois être utile comme l'explique user5534870 dans sa réponse).
ne explication plus élaborée: Mis à part sa fonction syntaxique (par exemple, la séparation des éléments dans les listes d'initialisation, les déclarations de variables ou les appels/déclarations de fonctions), en C et C++, le ,
peut aussi être un normal opérateur comme par exemple +
, et donc il peut être utilisé partout, où une expression est autorisée (en C++ vous pouvez même la surcharger).
La différence avec la plupart des autres opérateurs est que - bien que les deux côtés soient évalués - il ne combine en aucune façon les sorties des expressions gauche et droite, mais renvoie simplement la bonne.
.
Maintenant, la condition d'une instruction if
est (entre autres) un tel endroit et par conséquent, vous pouvez également utiliser le ,
opérateur là-bas - que cela ait du sens de le faire ou non est une question entièrement différente! En particulier - et différent de par ex. appels de fonctions ou déclarations de variables - la virgule n'a pas de signification particulière, elle fait donc ce qu'elle fait toujours: elle évalue les expressions à gauche et à droite, mais ne renvoie que le résultat de la droite, qui est ensuite utilisé par le if
instruction.
Les deux seuls points auxquels je peux penser en ce moment, où utiliser le (non surchargé) ,
- L'opérateur est logique:
Si vous souhaitez incrémenter plusieurs itérateurs dans la tête d'une boucle for
:
for ( ... ; ... ; ++i1, ++i2){
*i2=*i1;
}
Si vous souhaitez évaluer plusieurs expressions dans une fonction constexpr C++ 11.
Pour répéter encore une fois: Utiliser l'opérateur virgule dans une instruction if
ou while
- comme vous l'avez montré dans votre exemple - n'est pas quelque chose de sensé. C'est juste un autre exemple où les syntaxes de langage de C et C++ vous permettent d'écrire du code, qui ne se comporte pas comme on pourrait s'y attendre - à première vue. Il y en a beaucoup plus ....
Pour une instruction if
, il est inutile de mettre quelque chose dans une expression virgule plutôt qu'à l'extérieur.
Pour une instruction while
, mettre une expression virgule à la condition exécute la première partie soit lors de l'entrée dans la boucle, o lors de la boucle. Cela ne peut pas être facilement répliqué sans duplication de code.
Alors que diriez-vous d'une instruction s do
...while
? Là, nous n'avons qu'à nous soucier de la boucle elle-même, non? Il s'avère que même ici, une expression de virgule ne peut pas être remplacée en toute sécurité en déplaçant la première partie dans la boucle.
D'une part, les destructeurs des variables dans le corps de la boucle n'auront pas déjà été exécutés, ce qui pourrait faire une différence. D'autre part, toute instruction continue
à l'intérieur de la boucle atteindra la première partie de l'expression virgule niquement lorsqu'elle est en effet dans la condition plutôt que dans le corps de la boucle.
Il n'y a pas avantage: l'opérateur virgule est simplement une expression avec le type de la dernière expression dans sa liste d'expressions et une instruction if évalue une expression booléenne.
if(<expr>) { ... }
with type of <expr> boolean
C'est un opérateur étrange, certes, mais il n'y a pas de magie - sauf qu'il confond les listes d'expressions avec les listes d'arguments dans les appels de fonction.
foo(<args>)
with <args> := [<expr>[, <expr>]*]
Notez que dans la liste des arguments, la virgule se lie plus fortement aux arguments de séparation.
Ce qui suit est un peu extensible, selon le degré de retors que vous pourriez souhaiter.
Considérez la situation où une fonction renvoie une valeur en modifiant un paramètre passé par référence ou via un pointeur (peut-être à partir d'une bibliothèque mal conçue, ou pour vous assurer que cette valeur n'est pas ignorée en n'étant pas affectée après le retour, peu importe).
void calculateValue(FooType &result) {/*...*/}
Alors, comment utilisez-vous les instructions conditionnelles qui dépendent de result
?
Vous pouvez déclarer la variable qui sera modifiée, puis la vérifier avec un if:
FooType result;
calculateValue(result);
if (result.isBared()) {
//...
}
Cela pourrait être raccourci
FooType result;
if (calculateValue(result) , result.isBared()) {
//...
}
Ce qui ne vaut pas vraiment la peine. Cependant, pour les boucles while
, il pourrait y avoir quelques petits avantages. Si calculateValue
doit/peut être appelé jusqu'à ce que le résultat ne soit plus bar
'd, nous aurions quelque chose comme:
FooType result;
calculateValue(result); //[1] Duplicated code, see [2]
while (result.isBared()) {
//... possibly many lines
//separating the two places where result is modified and tested
//How do you prevent someone coming after you and adds a `continue`
//here which prevents result to be updated in the and of the loop?
calculateValue(result); //[2] Duplicated code, see [1]
}
et pourrait être condensé en:
FooType result;
while (calculateValue(result) , result.isBared()) {
//all your (possibly numerous) code lines go here
}
De cette façon, le code à mettre à jour result
est à un seul endroit, et est près de la ligne où ses conditions sont vérifiées.
peut-être sans rapport: Une autre raison pour laquelle les variables pourraient être mises à jour via le passage de paramètres est que la fonction doit renvoyer le code d'erreur en plus de modifier/renvoyer la valeur calculée. Dans ce cas:
ErrorType fallibleCalculation(FooType &result) {/*...*/}
ensuite
FooType result;
ErrorType error;
while (error = fallibleCalculation(result) , (Success==error && result.isBared())) {
//...
}
mais comme indiqué dans les commentaires, vous pouvez également le faire sans la virgule:
FooType result;
ErrorType error;
while (Success == fallibleCalculation(result) && result.isBared()) {
//...
}
Pas du tout. Les comparaisons sur a
dans ce code sont complètement redondantes.
Ma question est quel est l'avantage des virgules dans les instructions if
ou while
? Pourquoi est-il autorisé?
Il existe parce que les déclarations et les expressions sont des choses différentes en C. Une expression composée est une construction qui est comprise par la théorie (et certains autres langages) et serait manquant sans l'avoir ajouté sous forme de virgule. Son utilisation dans l'instruction for
était la justification originale de la raison pour laquelle ils en avaient besoin.
Mais, en rendant le langage plus complet d'un point de vue théorique solide, il trouve plus tard des usages que personne n'avait prévus. Le premier C++ était un traducteur qui générait C comme sortie, et avoir une expression séquentielle était absolument essentiel pour permettre aux fonctions en ligne de générer vraiment "en ligne" logique dans le code C.
Cela inclut tout endroit où l'expression apparaît, y compris la condition d'une instruction if
.
De même, il a été utilisé dans des macros "intéressantes". Et autant que C++ a supprimé les macros en fournissant des fonctions en ligne, aussi tard que les compilateurs jusqu'à x11 ont trouvé la boucle de plage Boost FOREACH (finalement, une émulation de la fonctionnalité ajoutée au langage en x11) très pratique, et c'était un ensemble de macros diaboliquement intelligent qui impliquait l'opérateur virgule.
(Hmm, la version actuelle se développe en plusieurs instructions en utilisant if
/else
chaîné, plutôt que de tout entasser dans un seul while
.)
Maintenant, il existe une autre façon de mettre n'importe quelle instruction dans une expression (lambdas), de sorte que les futures entreprises folles de macros qui émulent des fonctionnalités de langage encore plus récentes ou des langages intégrés spécifiques à un domaine pourraient ne pas avoir besoin pour l'utiliser plus.
Donc, n'écrivez pas de code comme ça. À moins que ce soit clair et même plus simple que d'écrire des fonctions d'aide ou de se diviser en plusieurs instructions.
Mais cela peut être juste la chose pour une macro que vous souhaitez facilement utiliser en un seul endroit et cet endroit est à l'intérieur des parenthèses d'un if
ou while
. Cela pourrait être justifié dans un langage spécifique au domaine hébergé dans le code source C++, ou une fonctionnalité d'émulation de langage comme (peut-être) une alternative à la gestion des exceptions utilisée dans un réel incorporé système de temps.
En bref, il n'a pas une bonne utilisation normale. Mais il est là pour être complet et on ne sait jamais quand quelqu'un le trouvera utile.