Prenez les deux lignes de code suivantes:
for (int i = 0; i < some_vector.size(); i++)
{
//do stuff
}
Et ça:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
some_iterator++)
{
//do stuff
}
On me dit que la deuxième voie est préférée. Pourquoi est-ce exactement cela?
some_iterator++
à++some_iterator
. Le post-incrément crée un itérateur temporaire inutile.end()
dans la clause de déclaration.vector::end
probablement de plus gros problèmes à se soucier que de savoir si elle est retirée des boucles ou non. Personnellement, je préfère la clarté - si c'était un appel àfind
la condition de terminaison, je m'inquiéterais cependant.it != vec.end()
etit != end
, c'est entre(vector<T>::iterator it = vec.begin(); it != vec.end(); ++it)
et(vector<T>::iterator it = vec.begin(), end = vec.end(); it != end; ++it)
. Je n'ai pas besoin de compter les personnages. Bien sûr, préférez l'un plutôt que l'autre, mais le désaccord des autres avec votre préférence n'est pas «négligé», c'est une préférence pour un code plus simple avec moins de variables et donc moins de réflexion en le lisant.Réponses:
La première forme n'est efficace que si vector.size () est une opération rapide. Cela est vrai pour les vecteurs, mais pas pour les listes, par exemple. Aussi, que comptez-vous faire dans le corps de la boucle? Si vous prévoyez d'accéder aux éléments comme dans
alors vous faites l'hypothèse que le conteneur a
operator[](std::size_t)
défini. Encore une fois, cela est vrai pour le vecteur, mais pas pour les autres conteneurs.L'utilisation d'itérateurs vous rapproche de l' indépendance des conteneurs . Vous ne faites pas d'hypothèses sur la capacité d'accès aléatoire ou le
size()
fonctionnement rapide , mais seulement que le conteneur a des capacités d'itérateur.Vous pouvez encore améliorer votre code en utilisant des algorithmes standard. Selon ce que vous essayez de réaliser, vous pouvez choisir de l'utiliser
std::for_each()
,std::transform()
etc. En utilisant un algorithme standard plutôt qu'une boucle explicite, vous évitez de réinventer la roue. Votre code est susceptible d'être plus efficace (étant donné que le bon algorithme est choisi), correct et réutilisable.la source
size_t
variable membre en gardant une tracesize()
.size()
fonction membre devra avoir une complexité temporelle constante pour tous les conteneurs qui la prennent en charge, y comprisstd::list
.Cela fait partie du processus d'endoctrinement C ++ moderne. Les itérateurs sont le seul moyen d'itérer la plupart des conteneurs, vous l'utilisez donc même avec des vecteurs juste pour vous mettre dans le bon état d'esprit. Sérieusement, c'est la seule raison pour laquelle je le fais - je ne pense pas avoir déjà remplacé un vecteur par un autre type de conteneur.
Wow, c'est toujours en baisse après trois semaines. Je suppose que ça ne paie pas d'être un peu ironique.
Je pense que l'index du tableau est plus lisible. Il correspond à la syntaxe utilisée dans d'autres langages et à la syntaxe utilisée pour les tableaux C à l'ancienne. C'est aussi moins verbeux. L'efficacité devrait être un lavage si votre compilateur est bon, et il n'y a pratiquement aucun cas où cela importe de toute façon.
Malgré cela, je continue à utiliser fréquemment des itérateurs avec des vecteurs. Je crois que l'itérateur est un concept important, donc je le fais la promotion chaque fois que je le peux.
la source
parce que vous n'attachez pas votre code à l'implémentation particulière de la liste some_vector. si vous utilisez des indices de tableau, il doit s'agir d'une forme de tableau; si vous utilisez des itérateurs, vous pouvez utiliser ce code sur n'importe quelle implémentation de liste.
la source
Imaginez que some_vector soit implémenté avec une liste chaînée. La demande d'un élément à la i-ème place nécessite alors que i opérations soient effectuées pour parcourir la liste des nœuds. Maintenant, si vous utilisez l'itérateur, d'une manière générale, il fera de son mieux pour être aussi efficace que possible (dans le cas d'une liste chaînée, il maintiendra un pointeur sur le nœud actuel et le fera avancer à chaque itération, nécessitant juste un opération unique).
Il fournit donc deux choses:
la source
Je vais être l'avocat des démons ici, et je ne recommanderai pas les itérateurs. La principale raison est que tout le code source sur lequel j'ai travaillé, depuis le développement d'applications de bureau jusqu'au développement de jeux, n'a ni besoin d'utiliser des itérateurs. Tout le temps, ils n'ont pas été nécessaires et deuxièmement, les hypothèses cachées et les cauchemars de débogage et de débogage que vous obtenez avec les itérateurs en font un excellent exemple pour ne pas l'utiliser dans les applications qui nécessitent de la vitesse.
Même du point de vue de la maintenance, ils sont un gâchis. Ce n'est pas à cause d'eux mais à cause de tous les alias qui se produisent derrière la scène. Comment puis-je savoir que vous n'avez pas implémenté votre propre vecteur virtuel ou liste de tableaux qui fait quelque chose de complètement différent des normes. Est-ce que je sais quel type est actuellement en cours d'exécution? Avez-vous surchargé un opérateur, je n'ai pas eu le temps de vérifier tout votre code source. Enfer, je sais même quelle version de la STL vous utilisez?
Le problème suivant que vous avez avec les itérateurs est l'abstraction qui fuit, bien qu'il existe de nombreux sites Web qui en discutent en détail avec eux.
Désolé, je n'ai pas vu et je n'ai toujours pas vu d'utilité dans les itérateurs. S'ils font abstraction de la liste ou du vecteur loin de vous, alors qu'en fait vous devriez déjà savoir à quel vecteur ou à quelle liste vous traitez si vous ne le faites pas, alors vous allez simplement vous préparer pour de grandes sessions de débogage à l'avenir.
la source
Vous pouvez utiliser un itérateur si vous souhaitez ajouter / supprimer des éléments au vecteur pendant que vous l'itérez.
Si vous utilisez des index, vous devrez mélanger les éléments vers le haut / bas dans le tableau pour gérer les insertions et les suppressions.
la source
std::list
rapport à astd::vector
, cependant, si vous recommandez d'utiliser une liste chaînée au lieu de astd::vector
. Voir page 43: ecn.channel9.msdn.com/events/GoingNative12/GN12Cpp11Style.pdf D'après mon expérience, j'ai trouvé unstd::vector
est plus rapide qu'unstd::list
même si je recherche sur tout cela et supprime des éléments à des positions arbitraires.for (node = list->head; node != NULL; node = node->next)
plus courte que vos deux premières lignes de code réunies (déclaration et tête de boucle). Donc, je le répète - il n'y a pas beaucoup de différence fondamentale de concision entre l'utilisation d'itérateurs et leur non-utilisation - vous devez toujours satisfaire les trois parties d'unefor
déclaration, même si vous utilisezwhile
: déclarer, itérer, vérifier la terminaison.Séparation des préoccupations
C'est très agréable de séparer le code d'itération de la préoccupation «centrale» de la boucle. C'est presque une décision de conception.
En effet, l'itération par index vous lie à l'implémentation du conteneur. Demander au conteneur un itérateur de début et de fin active le code de boucle à utiliser avec d'autres types de conteneurs.
De plus, dans la
std::for_each
manière, vous dites à la collection quoi faire, au lieu de lui demander quelque chose sur ses internesLa norme 0x va introduire des fermetures, ce qui rendra cette approche beaucoup plus facile à utiliser - jetez un œil à la puissance expressive de Ruby par exemple
[1..6].each { |i| print i; }
...Performance
Mais peut-être un problème beaucoup plus surveillé est que l'utilisation de l'
for_each
approche donne la possibilité de paralléliser l'itération - les blocs de threads Intel peuvent distribuer le bloc de code sur le nombre de processeurs dans le système!Remarque: après avoir découvert la
algorithms
bibliothèque, et surtoutforeach
, j'ai passé deux ou trois mois à écrire des structures d'opérateur 'helper' ridiculement petites qui rendront vos collègues développeurs fous. Après ce temps, je suis revenu à une approche pragmatique - les petits corps en boucle ne méritent pasforeach
plus :)Une référence incontournable sur les itérateurs est le livre "Extended STL" .
Le GoF a un petit petit paragraphe à la fin du modèle Iterator, qui parle de cette marque d'itération; cela s'appelle un «itérateur interne». Jetez un œil ici aussi.
la source
Parce qu'il est plus orienté objet. si vous itérez avec un index, vous supposez:
a) que ces objets sont ordonnés
b) que ces objets peuvent être obtenus par un index
c) que l'incrément d'index atteindra chaque élément
d) que cet index commence à zéro
Avec un itérateur, vous dites "donnez-moi tout pour que je puisse travailler avec" sans savoir quelle est l'implémentation sous-jacente. (En Java, il existe des collections qui ne sont pas accessibles via un index)
De plus, avec un itérateur, pas besoin de s'inquiéter de sortir des limites du tableau.
la source
Une autre bonne chose à propos des itérateurs est qu'ils permettent mieux d'exprimer (et d'appliquer) votre préférence const. Cet exemple garantit que vous ne modifierez pas le vecteur au milieu de votre boucle:
la source
const_iterator
. Si je modifie le vecteur dans la boucle, je le fais pour une raison, et pendant 99,9% du temps, la modification n'est pas un accident, et pour le reste, c'est juste un bogue comme tout type de bogue dans le code de l'auteur doit réparer. Parce qu'en Java et dans de nombreux autres langages, il n'y a pas d'objet const du tout, mais les utilisateurs de ces langages n'ont jamais de problème sans prise en charge de const dans ces langages.const_iterator
, alors quelle pourrait être la raison sur Terre?Mis à part toutes les autres excellentes réponses ...
int
peut-être pas assez grandes pour votre vecteur. Au lieu de cela, si vous souhaitez utiliser l'indexation, utilisez lesize_type
pour votre conteneur:la source
int i
àmyvector.size()
.Je devrais probablement souligner que vous pouvez également appeler
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
la source
Les itérateurs STL sont principalement là pour que les algorithmes STL comme sort puissent être indépendants du conteneur.
Si vous souhaitez simplement parcourir toutes les entrées d'un vecteur, utilisez simplement le style de boucle d'index.
Il est moins dactylographique et plus facile à analyser pour la plupart des humains. Ce serait bien si C ++ avait une boucle foreach simple sans aller trop loin avec la magie des modèles.
la source
Je ne pense pas que cela fasse beaucoup de différence pour un vecteur. Je préfère utiliser un index moi-même car je le considère plus lisible et vous pouvez faire un accès aléatoire comme sauter en avant 6 éléments ou sauter en arrière si besoin est.
J'aime aussi faire référence à l'élément à l'intérieur de la boucle comme ceci, donc il n'y a pas beaucoup de crochets autour de l'endroit:
L'utilisation d'un itérateur peut être utile si vous pensez que vous devrez peut-être remplacer le vecteur par une liste à un moment donné dans le futur et il semble également plus élégant pour les monstres de la STL, mais je ne peux penser à aucune autre raison.
la source
I prefer to use an index myself as I consider it to be more readable
seulement dans certaines situations; dans d'autres, les indices deviennent rapidement très salissants.and you can do random access
qui n'est pas du tout une caractéristique unique des indices: voir en.cppreference.com/w/cpp/concept/RandomAccessIteratorLe deuxième formulaire représente ce que vous faites avec plus de précision. Dans votre exemple, vous ne vous souciez pas vraiment de la valeur de i - tout ce que vous voulez, c'est l'élément suivant dans l'itérateur.
la source
Après avoir appris un peu plus sur le sujet de cette réponse, je me rends compte que c'était un peu une simplification excessive. La différence entre cette boucle:
Et cette boucle:
Est assez minime. En fait, la syntaxe de faire des boucles de cette façon semble se développer sur moi:
Les itérateurs déverrouillent certaines fonctionnalités déclaratives assez puissantes, et lorsqu'ils sont combinés avec la bibliothèque d'algorithmes STL, vous pouvez faire des choses assez intéressantes qui sortent du cadre de l'administration d'index de tableau.
la source
for (Iter it = {0}; it != end; ++it) {...}
- vous venez d'omettre la déclaration - donc la brièveté n'est pas très différente de votre deuxième exemple. Pourtant, +1.L'indexation nécessite une
mul
opération supplémentaire . Par exemple, pourvector<int> v
, le compilateur se convertitv[i]
en&v + sizeof(int) * i
.la source
sizeof
et de simplement l'ajouter une fois par itération, plutôt que de refaire le calcul de l'offset à chaque fois.Pendant l'itération, vous n'avez pas besoin de connaître le nombre d'articles à traiter. Vous avez juste besoin de l'article et les itérateurs font de très bonnes choses.
la source
Personne n'a encore mentionné que l'un des avantages des index est qu'ils ne deviennent pas invalides lorsque vous les ajoutez à un conteneur contigu comme
std::vector
, vous pouvez donc ajouter des éléments au conteneur pendant l'itération.Cela est également possible avec les itérateurs, mais vous devez appeler
reserve()
, et donc avoir besoin de savoir combien d'éléments vous ajouterez.la source
Plusieurs bons points déjà. J'ai quelques commentaires supplémentaires:
En supposant que nous parlons de la bibliothèque standard C ++, "vector" implique un conteneur d'accès aléatoire qui a les garanties du C-array (accès aléatoire, disposition de la mémoire des contiguos, etc.). Si vous aviez dit 'some_container', la plupart des réponses ci-dessus auraient été plus précises (indépendance du conteneur, etc.).
Pour éliminer toute dépendance sur l'optimisation du compilateur, vous pouvez déplacer some_vector.size () hors de la boucle dans le code indexé, comme ceci:
Toujours pré-incrémenter les itérateurs et traiter les post-incréments comme des cas exceptionnels.
Donc, en supposant et indexable
std::vector<>
comme un conteneur, il n'y a aucune bonne raison de préférer l'un à l'autre, en passant séquentiellement par le conteneur. Si vous devez vous référer fréquemment à des index d'élément plus anciens ou plus récents, alors la version indexée est plus appropriée.En général, l'utilisation des itérateurs est préférable car les algorithmes les utilisent et le comportement peut être contrôlé (et implicitement documenté) en changeant le type de l'itérateur. Les emplacements de tableau peuvent être utilisés à la place des itérateurs, mais la différence syntaxique ressortira.
la source
Je n'utilise pas d'itérateurs pour la même raison que je n'aime pas pour chaque déclaration. Lorsqu'il y a plusieurs boucles internes, il est assez difficile de garder une trace des variables globales / membres sans avoir à se souvenir de toutes les valeurs locales et des noms d'itérateurs également. Ce que je trouve utile, c'est d'utiliser deux ensembles d'indices pour différentes occasions:
Je ne veux même pas abréger des choses comme "animation_matrices [i]" en un itérateur aléatoire nommé "anim_matrix" par exemple, car alors vous ne pouvez pas voir clairement de quel tableau provient cette valeur.
la source
it
,jt
,kt
, etc. ou même continuer à utiliseri
,j
,k
, etc. Et si vous avez besoin de savoir exactement ce que représente un itérateur, alors pour moi quelque chose commefor (auto anim = anims.begin(); ...) for (auto anim_bone = anim->bones.begin(); ...) anim_bone->wobble()
serait plus descriptif que d'avoir à indexer continuellement commeanimation_matrices[animIndex][boneIndex]
.for
boucle basée sur la plage , ce qui rend la manière basée sur l'itérateur de le faire encore plus concise.Vraiment, c'est tout ce qu'il y a à faire. Ce n'est pas comme si vous alliez gagner en brièveté dans les deux cas en moyenne, et si la brièveté est vraiment votre objectif, vous pouvez toujours vous rabattre sur les macros.
la source
Si vous avez accès aux fonctionnalités C ++ 11 , vous pouvez également utiliser une boucle basée sur
for
une plage pour itérer sur votre vecteur (ou tout autre conteneur) comme suit:L'avantage de cette boucle est que vous pouvez accéder aux éléments du vecteur directement via la
item
variable, sans courir le risque de gâcher un index ou de faire une erreur lors du déréférencement d'un itérateur. De plus, l'espace réservéauto
vous évite d'avoir à répéter le type des éléments de conteneur, ce qui vous rapproche encore plus d'une solution indépendante du conteneur.Remarques:
operator[]
existe pour votre conteneur (et qu'il est assez rapide pour vous), alors optez pour votre première méthode.for
boucle basée sur une plage ne peut pas être utilisée pour ajouter / supprimer des éléments dans / depuis un conteneur. Si vous voulez le faire, mieux vaut vous en tenir à la solution proposée par Brian Matthews.const
comme suit:for (auto const &item : some_vector) { ... }
.la source
Encore mieux que "dire au CPU quoi faire" (impératif), c'est "dire aux bibliothèques ce que vous voulez" (fonctionnel).
Donc, au lieu d'utiliser des boucles, vous devriez apprendre les algorithmes présents dans stl.
la source
Pour l'indépendance des conteneurs
la source
J'utilise toujours un index de tableau parce que beaucoup de mes applications nécessitent quelque chose comme "afficher l'image miniature". J'ai donc écrit quelque chose comme ceci:
la source
Les deux implémentations sont correctes, mais je préférerais la boucle "for". Comme nous avons décidé d'utiliser un vecteur et non aucun autre conteneur, l'utilisation d'index serait la meilleure option. L'utilisation d'itérateurs avec des vecteurs perdrait l'avantage même d'avoir les objets dans des blocs de mémoire continue qui facilitent leur accès.
la source
J'ai senti qu'aucune des réponses ici n'explique pourquoi j'aime les itérateurs comme concept général par rapport à l'indexation dans des conteneurs. Notez que la plupart de mon expérience avec les itérateurs ne vient pas réellement de C ++ mais de langages de programmation de niveau supérieur comme Python.
L'interface de l'itérateur impose moins d'exigences aux consommateurs de votre fonction, ce qui permet aux consommateurs d'en faire plus avec elle.
Si tout ce dont vous avez besoin est de pouvoir effectuer une itération directe, le développeur n'est pas limité à l'utilisation de conteneurs indexables - il peut utiliser n'importe quelle implémentation de classe
operator++(T&)
,operator*(T)
etoperator!=(const &T, const &T)
.Votre algorithme fonctionne pour le cas où vous en avez besoin - itération sur un vecteur - mais il peut également être utile pour les applications que vous n'anticipez pas nécessairement:
Tenter d'implémenter un opérateur entre crochets qui fait quelque chose de similaire à cet itérateur serait artificiel, tandis que l'implémentation de l'itérateur est relativement simple. L'opérateur entre crochets a également des implications sur les capacités de votre classe - que vous pouvez indexer à n'importe quel point arbitraire - qui peuvent être difficiles ou inefficaces à implémenter.
Les itérateurs se prêtent également à la décoration . Les utilisateurs peuvent écrire des itérateurs qui prennent un itérateur dans leur constructeur et étendent ses fonctionnalités:
Bien que ces jouets puissent sembler banals, il n'est pas difficile d'imaginer utiliser des itérateurs et des décorateurs d'itérateurs pour faire des choses puissantes avec une interface simple - décorer un itérateur en avant uniquement des résultats de la base de données avec un itérateur qui construit un objet modèle à partir d'un seul résultat, par exemple . Ces modèles permettent une itération efficace en mémoire d'ensembles infinis et, avec un filtre comme celui que j'ai écrit ci-dessus, une évaluation potentiellement paresseuse des résultats.
Une partie de la puissance des modèles C ++ est votre interface d'itérateur, lorsqu'elle est appliquée à des tableaux C de longueur fixe, se désintègre en une arithmétique de pointeur simple et efficace , ce qui en fait une abstraction vraiment sans coût.
la source