Incrémentation en C ++ - Quand utiliser x ++ ou ++ x?

91

J'apprends actuellement C ++ et j'ai appris l'incrémentation il y a quelque temps. Je sais que vous pouvez utiliser "++ x" pour faire l'incrémentation avant et "x ++" pour le faire après.

Pourtant, je ne sais vraiment pas quand utiliser l'un ou l'autre des deux ... Je n'ai jamais vraiment utilisé "++ x" et les choses ont toujours bien fonctionné jusqu'à présent - alors, quand dois-je l'utiliser?

Exemple: Dans une boucle for, quand est-il préférable d'utiliser "++ x"?

Aussi, quelqu'un pourrait-il expliquer exactement comment fonctionnent les différentes incrémentations (ou décrémentations)? J'apprécierai vraiment cela.

Jesse Emond
la source

Réponses:

114

Ce n'est pas une question de préférence, mais de logique.

x++incrémente la valeur de la variable x après le traitement de l'instruction courante.

++xincrémente la valeur de la variable x avant de traiter l'instruction courante.

Alors décidez simplement de la logique que vous écrivez.

x += ++iincrémentera i et ajoutera i + 1 à x. x += i++ajoutera i à x, puis incrémentera i.

Oliver Friedrich
la source
27
et notez que dans une boucle for, sur les primatives, il n'y a absolument aucune différence. De nombreux styles de codage recommandent de ne jamais utiliser d'opérateur d'incrémentation où il pourrait être mal compris; c'est-à-dire que x ++ ou ++ x ne doit exister que sur sa propre ligne, jamais comme y = x ++. Personnellement, je n'aime pas ça, mais c'est rare
Mikeage
2
Et s'il est utilisé sur sa propre ligne, le code généré est presque sûr d'être le même.
Nosredna
14
Cela peut ressembler à de la pédanterie (principalement parce que c'est :)) mais en C ++, x++est une rvalue avec la valeur xavant incrément, x++est une lvalue avec la valeur xaprès un incrément. Aucune des deux expressions ne garantit que la valeur incrémentée réelle est stockée dans x, il est seulement garanti que cela se produit avant le point de séquence suivant. «après le traitement de l'instruction courante» n'est pas strictement précis car certaines expressions ont des points de séquence et certaines instructions sont des instructions composées.
CB Bailey
10
En fait, la réponse est trompeuse. Le moment où la variable x est modifiée ne diffère probablement pas en pratique. La différence est que x ++ est défini pour renvoyer une rvaleur de la valeur précédente de x tandis que ++ x fait toujours référence à la variable x.
sellibitze
5
@BeowulfOF: La réponse implique un ordre qui n'existe pas. Il n'y a rien dans la norme pour dire quand les incréments ont lieu. Le compilateur est autorisé à implémenter "x + = i ++" comme: int j = i; i = i + 1; x + = j; "(c'est-à-dire. 'i' incrémenté avant de" traiter l'instruction courante "). C'est pourquoi" i = i ++ "a un comportement indéfini et c'est pourquoi je pense que la réponse doit être" peaufinée ". La description de" x + = ++ i "est correct car il n'y a aucune suggestion d'ordre:" incrémentera i et ajoutera i + 1 à x ".
Richard Corden
53

Scott Meyers vous dit de préférer le préfixe sauf dans les cas où la logique dicterait que le suffixe est approprié.

"C ++ plus efficace" item # 6 - c'est une autorité suffisante pour moi.

Pour ceux qui ne possèdent pas le livre, voici les citations pertinentes. À partir de la page 32:

De vos jours en tant que programmeur C, vous vous souviendrez peut-être que la forme du préfixe de l'opérateur d'incrémentation est parfois appelée «incrémentation et extraction», tandis que la forme suffixe est souvent appelée «extraction et incrémentation». Les deux phrases sont importantes à retenir, car elles agissent toutes comme des spécifications formelles ...

Et à la page 34:

Si vous êtes du genre à vous soucier de l'efficacité, vous avez probablement transpiré lorsque vous avez vu pour la première fois la fonction d'incrémentation de postfix. Cette fonction doit créer un objet temporaire pour sa valeur de retour et l'implémentation ci-dessus crée également un objet temporaire explicite qui doit être construit et détruit. La fonction d'incrémentation de préfixe n'a pas de tels temporaires ...

duffymo
la source
4
Si le compilateur ne se rend pas compte que la valeur avant l'incrémentation n'est pas un escargot, il peut implémenter l'incrément postfix dans plusieurs instructions - copier l'ancienne valeur, puis incrémenter. L'incrément de préfixe doit toujours être une seule instruction.
gnud
8
Il m'est arrivé de tester cela hier avec gcc: dans une boucle for dans laquelle la valeur est jetée après l'exécution i++ou ++i, le code généré est le même.
Giorgio du
Essayez-le en dehors de la boucle for. Le comportement dans une affectation doit être différent.
duffymo
Je suis explicitement en désaccord avec Scott Meyers sur son deuxième point - il n'est généralement pas pertinent puisque 90% ou plus des cas de «x ++» ou «++ x» sont généralement isolés de toute affectation, et les optimiseurs sont suffisamment intelligents pour reconnaître qu'aucune variable temporaire n'a besoin être créé dans de tels cas. Dans ce cas, les deux formulaires sont totalement interchangeables. L'implication de ceci est que les anciennes bases de code criblées de "x ++" devraient être laissées de côté - vous êtes plus susceptible d'introduire des erreurs subtiles en les changeant en "++ x" que d'améliorer les performances n'importe où. Il est sans doute préférable d'utiliser "x ++" et de faire réfléchir les gens.
omatai
2
Vous pouvez faire confiance à Scott Meyers tout ce que vous voulez, mais si votre code dépend tellement des performances que toute différence de performance entre les deux ++xet x++compte réellement, il est beaucoup plus important que vous utilisiez réellement un compilateur capable d'optimiser complètement et correctement l'une ou l'autre version, quel que soit le le contexte. «Depuis que j'utilise ce vieux marteau merdique, je ne peux enfoncer des clous qu'à un angle de 43,7 degrés» est un mauvais argument pour construire une maison en enfonçant des clous à seulement 43,7 degrés. Utilisez un meilleur outil.
Andrew Henle
28

De cppreference lors de l'incrémentation des itérateurs:

Vous devriez préférer l'opérateur de pré-incrémentation (++ iter) à l'opérateur de post-incrémentation (iter ++) si vous n'allez pas utiliser l'ancienne valeur. La post-incrémentation est généralement implémentée comme suit:

   Iter operator++(int)   {
     Iter tmp(*this); // store the old value in a temporary object
     ++*this;         // call pre-increment
     return tmp;      // return the old value   }

De toute évidence, c'est moins efficace que le pré-incrémentation.

Le pré-incrémentation ne génère pas l'objet temporaire. Cela peut faire une différence significative si votre objet est coûteux à créer.

Phillip Ngan
la source
8

Je veux juste remarquer que le code généré est souvent le même si vous utilisez l'incrémentation pré / post où la sémantique (de pré / post) n'a pas d'importance.

exemple:

pre.cpp:

#include <iostream>

int main()
{
  int i = 13;
  i++;
  for (; i < 42; i++)
    {
      std::cout << i << std::endl;
    }
}

post.cpp:

#include <iostream>

int main()
{

  int i = 13;
  ++i;
  for (; i < 42; ++i)
    {
      std::cout << i << std::endl;
    }
}

_

$> g++ -S pre.cpp
$> g++ -S post.cpp
$> diff pre.s post.s   
1c1
<   .file   "pre.cpp"
---
>   .file   "post.cpp"
chevaine
la source
5
Pour un type primitif comme un entier, oui. Avez-vous vérifié quelle est la différence pour quelque chose comme un std::map::iterator? Bien sûr, les deux opérateurs sont différents, mais je suis curieux de savoir si le compilateur optimisera le suffixe en préfixe si le résultat n'est pas utilisé. Je ne pense pas que ce soit autorisé - étant donné que la version postfix pourrait contenir des effets secondaires.
SEH
De plus, `` le compilateur réalisera probablement que vous n'avez pas besoin de l'effet secondaire et l'optimisez '' ne devrait pas être une excuse pour écrire du code bâclé qui utilise les opérateurs postfix plus complexes sans aucune raison, à part probablement le fait que tant de le matériel didactique supposé utilise postfix sans raison apparente et est copié en gros.
underscore_d
6

La chose la plus importante à garder à l'esprit, imo, est que x ++ doit renvoyer la valeur avant que l'incrément n'ait réellement lieu - par conséquent, il doit faire une copie temporaire de l'objet (pré-incrémentation). C'est moins efficace que ++ x, qui est incrémenté sur place et renvoyé.

Une autre chose à mentionner, cependant, est que la plupart des compilateurs seront en mesure d'optimiser ces choses inutiles lorsque cela est possible, par exemple, les deux options conduiront au même code ici:

for (int i(0);i<10;++i)
for (int i(0);i<10;i++)
rmn
la source
5

Je suis d'accord avec @BeowulfOF, mais pour plus de clarté, je recommanderais toujours de diviser les déclarations afin que la logique soit absolument claire, c'est-à-dire:

i++;
x += i;

ou

x += i;
i++;

Donc, ma réponse est que si vous écrivez un code clair, cela devrait rarement avoir une importance (et si cela compte, votre code n'est probablement pas assez clair).

Offres
la source
2

Je voulais juste souligner à nouveau que ++ x devrait être plus rapide que x ++, (surtout si x est un objet d'un type arbitraire), donc à moins que cela ne soit nécessaire pour des raisons logiques, ++ x devrait être utilisé.

Shailesh Kumar
la source
2
Je tiens simplement à souligner que cette insistance est plus que probablement trompeuse. Si vous regardez une boucle qui se termine par un "x ++" isolé et que vous pensez "Aha! - c'est la raison pour laquelle cela fonctionne si lentement!" et vous le changez en "++ x", alors ne vous attendez à aucune différence. Les optimiseurs sont suffisamment intelligents pour reconnaître qu'aucune variable temporaire ne doit être créée lorsque personne ne va utiliser leurs résultats. L'implication est que les anciennes bases de code criblées de "x ++" devraient être laissées seules - vous êtes plus susceptible d'introduire des erreurs en les modifiant que d'améliorer les performances n'importe où.
omatai
1

Vous avez bien expliqué la différence. Cela dépend simplement si vous voulez que x s'incrémente avant chaque exécution d'une boucle, ou après cela. Cela dépend de la logique de votre programme, de ce qui est approprié.

Une différence importante en ce qui concerne les itérateurs STL (qui implémentent également ces opérateurs) est qu'il ++ crée une copie de l'objet vers lequel l'itérateur pointe, puis incrémente, puis retourne la copie. ++, d'autre part, l'incrémentation est d'abord effectuée, puis renvoie une référence à l'objet vers lequel l'itérateur pointe maintenant. Ceci est principalement pertinent lorsque chaque bit de performance compte ou lorsque vous implémentez votre propre itérateur STL.

Edit: correction du mélange de notation de préfixe et de suffixe

Björn Pollex
la source
Le discours "avant / après" l'itération d'une boucle n'a de sens que si le pré / post inc / décrément se produit dans la condition. Le plus souvent, ce sera dans la clause de continuation, où cela ne peut changer aucune logique, bien qu'il puisse être plus lent pour les types de classe d'utiliser postfix et que les gens ne devraient pas l'utiliser sans raison.
underscore_d
1

Forme Postfix de ++, - l'opérateur suit la règle use-then-change ,

La forme du préfixe (++ x, - x) suit la règle de changement puis d'utilisation .

Exemple 1:

Lorsque plusieurs valeurs sont mises en cascade avec << en utilisant cout, les calculs (le cas échéant) ont lieu de droite à gauche mais l'impression a lieu de gauche à droite, par exemple (si val est initialement 10)

 cout<< ++val<<" "<< val++<<" "<< val;

se traduira par

12    10    10 

Exemple 2:

Dans Turbo C ++, si plusieurs occurrences de ++ ou (sous n'importe quelle forme) sont trouvées dans une expression, alors tout d'abord toutes les formes de préfixes sont calculées puis l'expression est évaluée et enfin les formes de suffixe sont calculées, par exemple,

int a=10,b;
b=a++ + ++a + ++a + a;
cout<<b<<a<<endl;

Sa sortie dans Turbo C ++ sera

48 13

Alors que sa sortie dans le compilateur moderne sera (car ils suivent strictement les règles)

45 13
  • Remarque: l'utilisation multiple d'opérateurs d'incrémentation / décrémentation sur la même variable dans une expression n'est pas recommandée. La gestion / les résultats de ces
    expressions varient d'un compilateur à l'autre.
Sunil Dhillon
la source
Ce n'est pas que les expressions contenant plusieurs opérations inc / décrémentation «varient d'un compilateur à l'autre», mais plutôt pire: ces multiples modifications entre les points de séquence ont un comportement indéfini et empoisonnent le programme.
underscore_d
0

La compréhension de la syntaxe du langage est importante lorsque l'on considère la clarté du code. Pensez à copier une chaîne de caractères, par exemple avec post-incrémentation:

char a[256] = "Hello world!";
char b[256];
int i = 0;
do {
  b[i] = a[i];
} while (a[i++]);

Nous voulons que la boucle s'exécute en rencontrant le caractère zéro (qui teste false) à la fin de la chaîne. Cela nécessite de tester la valeur avant l'incrémentation et également d'incrémenter l'index. Mais pas nécessairement dans cet ordre - un moyen de coder ceci avec le pré-incrément serait:

int i = -1;
do {
  ++i;
  b[i] = a[i];
} while (a[i]);

C'est une question de goût qui est plus claire et si la machine a une poignée de registres, les deux devraient avoir un temps d'exécution identique, même si a [i] est une fonction qui coûte cher ou qui a des effets secondaires. Une différence significative pourrait être la valeur de sortie de l'indice.

shkeyser
la source