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érable. Pourquoi est-ce exactement?
La première forme n'est efficace que si vector.size () est une opération rapide. Ceci 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 envisagez d’accéder aux éléments comme dans
T elem = some_vector[i];
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 l'opération rapide size()
, mais uniquement sur le fait que le conteneur dispose de capacités d'itérateur.
Vous pouvez améliorer davantage votre code en utilisant des algorithmes standard. En fonction de l'objectif recherché, vous pouvez choisir d'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 sera probablement plus efficace (à condition de choisir le bon algorithme), correct et réutilisable.
parce que vous ne liez 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 pour toute implémentation de liste.
Cela fait partie du processus moderne d'endoctrinement C++. Les itérateurs sont le seul moyen d'itérer la plupart des conteneurs, vous pouvez donc les utiliser, même avec des vecteurs, pour vous mettre dans la bonne mentalité. Sérieusement, c'est la seule raison pour laquelle je le fais - je ne pense pas avoir jamais remplacé un vecteur par un type de conteneur différent.
Je pense que l'index de tableau est plus lisible. Cela 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. Si votre compilateur est bon, l’efficacité devrait faire l’objet d’un lavage, et il n’existe guère de cas où cela est important.
Malgré tout, je me retrouve toujours à utiliser des itérateurs fréquemment avec des vecteurs. Je crois que l'itérateur est un concept important, je le promeut dès que je peux.
Imagine some_vector est implémenté avec une liste chaînée. Ensuite, demander un élément à la i-ème place nécessite que i des opérations soient effectuées pour parcourir la liste des nœuds. Maintenant, si vous utilisez iterator, 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, ne nécessitant qu'un simple affichage. opération unique).
Donc, il fournit deux choses:
Je vais être le défenseur des démons ici, et non pas recommander des itérateurs. La raison principale en est que tout le code source sur lequel j'ai travaillé, du développement d'applications bureautiques au développement de jeux, n'a pas besoin d'utiliser des itérateurs. Tout le temps qu’ils n’ont pas été nécessaires et deuxièmement, les hypothèses cachées, le désordre de code et les cauchemars de débogage que vous obtenez avec les itérateurs en font un excellent exemple à ne pas utiliser dans des applications qui nécessitent de la rapidité.
Même du point de vue de la maintenance, ils sont en désordre. Ce n'est pas à cause d'eux mais à cause de tous les pseudonymes qui se produisent dans les coulisses. Comment puis-je savoir que vous n'avez pas mis en œuvre votre propre liste de vecteurs ou de tableaux virtuels 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. Hell, est-ce que je sais même quelle version de la STL vous utilisez?
Le prochain problème que vous avez avec les itérateurs est l’abstraction qui fuit, bien qu’il existe de nombreux sites Web qui discutent de cela en détail avec eux.
Désolé, je n'ai pas vu et je n'ai toujours pas vu de point en itérateurs. S'ils font abstraction de la liste ou du vecteur, alors qu'en fait, vous devez déjà savoir quel vecteur ou répertorier vos relations si vous ne le faites pas, vous vous apprêtez à vous préparer à de superbes sessions de débogage dans le futur.
Vous voudrez peut-être utiliser un itérateur si vous souhaitez ajouter/supprimer des éléments au vecteur tout en effectuant une itération dessus.
some_iterator = some_vector.begin();
while (some_iterator != some_vector.end())
{
if (/* some condition */)
{
some_iterator = some_vector.erase(some_iterator);
// some_iterator now positioned at the element after the deleted element
}
else
{
if (/* some other condition */)
{
some_iterator = some_vector.insert(some_iterator, some_new_value);
// some_iterator now positioned at new element
}
++some_iterator;
}
}
Si vous utilisiez des index, vous auriez à mélanger les éléments dans le tableau pour gérer les insertions et les suppressions.
Il est très agréable de séparer le code d'itération de la préoccupation principale de la boucle. C'est presque une décision de conception.
En effet, itérer par index vous lie à la mise en oeuvre 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 conteneur.
Aussi, dans le std::for_each
chemin, vous DITES à la collection ce qu'il faut faire, au lieu de DEMANDER quelque chose à propos de ses composants internes
La norme 0x va introduire des fermetures, ce qui rendra cette approche beaucoup plus facile à utiliser - jetez un coup d'œil au pouvoir expressif de, par exemple. Ruby [1..6].each { |i| print i; }
...
Mais peut-être un problème beaucoup oublié est que, en utilisant le for_each
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ L'une des possibilités de paralléliser l'itération - les intel threading blocks peuvent répartir le bloc de code sur le nombre de processeurs du système!
Remarque: après avoir découvert la bibliothèque algorithms
, et plus particulièrement foreach
, j'ai passé deux ou trois mois à écrire des structures d'opérateur 'aide' ridiculement petites qui rendront fous vos collègues développeurs. Après cette période, je suis revenu à une approche pragmatique: les petits corps de boucle ne méritent pas foreach
pas plus :)
Une référence incontournable sur les itérateurs est le livre "Extended STL" .
Le GoF a un tout petit paragraphe à la fin du motif Iterator, qui parle de cette marque d'itération; cela s'appelle un "itérateur interne". Regardez ici , aussi.
Parce que c'est plus orienté objet. si vous parcourez un index, vous supposez:
a) que ces objets sont commandé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 lui" sans connaître l’implémentation sous-jacente. (En Java, certaines collections ne sont pas accessibles via un index)
De plus, avec un itérateur, nul besoin de craindre de sortir des limites du tableau.
En dehors de toutes les autres excellentes réponses ... int
peut ne pas être assez grand pour votre vecteur. Si vous souhaitez utiliser l'indexation, utilisez plutôt le size_type
pour votre conteneur:
for (std::vector<Foo>::size_type i = 0; i < myvector.size(); ++i)
{
Foo& this_foo = myvector[i];
// Do stuff with this_foo
}
Une autre bonne chose à propos des itérateurs est qu’ils vous 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:
for(std::vector<Foo>::const_iterator pos=foos.begin(); pos != foos.end(); ++pos)
{
// Foo & foo = *pos; // this won't compile
const Foo & foo = *pos; // this will compile
}
Je devrais probablement préciser que vous pouvez aussi appeler
std::for_each(some_vector.begin(), some_vector.end(), &do_stuff);
Les itérateurs STL sont généralement présents afin que les algorithmes STL tels que le tri puissent être indépendants du conteneur.
Si vous souhaitez simplement parcourir toutes les entrées d'un vecteur, utilisez simplement le style de la boucle d'index.
Il est moins typé et plus facile à analyser pour la plupart des humains. Ce serait bien si C++ avait une simple boucle foreach sans aller trop loin avec le template magique.
for( size_t i = 0; i < some_vector.size(); ++i )
{
T& rT = some_vector[i];
// now do something with rT
}
'
Je ne pense pas que cela fasse une grande différence pour un vecteur. Je préfère utiliser moi-même un index, car j'estime qu'il est plus lisible et que vous pouvez effectuer un accès aléatoire, par exemple en sautant de 6 éléments ou en sautant si nécessaire.
J'aime aussi faire référence à l'élément à l'intérieur de la boucle comme ceci afin qu'il n'y ait pas beaucoup de crochets autour de la place:
for(size_t i = 0; i < myvector.size(); i++)
{
MyClass &item = myvector[i];
// Do stuff to "item".
}
L'utilisation d'un itérateur peut s'avérer utile si vous pensez que vous devrez peut-être remplacer le vecteur par une liste à un moment ultérieur. Il est également plus élégant pour les fous de la STL, mais je ne vois aucune autre raison.
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:
for (some_iterator = some_vector.begin(); some_iterator != some_vector.end();
some_iterator++)
{
//do stuff
}
Et cette boucle:
for (int i = 0; i < some_vector.size(); i++)
{
//do stuff
}
Est assez minime. En fait, la syntaxe de faire des boucles de cette façon semble grandir sur moi:
while (it != end){
//do stuff
++it;
}
Les itérateurs déverrouillent certaines fonctionnalités déclaratives assez puissantes et, une fois combinés à la bibliothèque d'algorithmes STL, vous pouvez réaliser des tâches plutôt intéressantes qui sortent du cadre de la gestion des index de tableaux.
L'indexation nécessite une opération supplémentaire mul
. Par exemple, pour vector<int> v
, Le compilateur convertit v[i]
En &v + sizeof(int) * i
.
La deuxième forme représente ce que vous faites plus précisément. Dans votre exemple, vous ne vous souciez pas de la valeur de i, tout ce que vous voulez, c'est l'élément suivant de l'itérateur.
Pendant l'itération, vous n'avez pas besoin de connaître le nombre d'éléments à traiter. Vous avez juste besoin du produit et les itérateurs font de très bonnes choses.
Personne n'a encore mentionné que l'un des avantages des index est qu'ils ne sont pas invalidés lorsque vous ajoutez un conteneur contigu tel que std::vector
, Vous pouvez donc ajouter des éléments au conteneur lors de l'itération.
Cela est également possible avec les itérateurs, mais vous devez appeler reserve()
et, par conséquent, savoir combien d'éléments vous allez ajouter.
Plusieurs bons points déjà. J'ai quelques commentaires supplémentaires:
En supposant que nous parlions de la bibliothèque standard C++, "vecteur" implique un conteneur à accès aléatoire ayant les garanties d'un tableau C (accès aléatoire, disposition de la mémoire contiguë, etc.). Si vous aviez dit 'un_container', nombre des réponses ci-dessus auraient été plus précises (indépendance du conteneur, etc.).
Pour éliminer les dépendances sur l'optimisation du compilateur, vous pouvez déplacer some_vector.size () en dehors de la boucle dans le code indexé, comme suit:
const size_t numElems = some_vector.size (); pour (size_t i = 0; i
Toujours pré-incrémenter les itérateurs et traiter les post-incréments comme des cas exceptionnels.
Donc supposant et indexable std::vector<>
_ comme un conteneur, il n’ya aucune raison de préférer l’un aux autres, en passant par le conteneur. Si vous devez vous référer fréquemment à des index elemnent plus anciens ou plus récents, 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 modifiant le type de l’itérateur. Les emplacements de tableau peuvent être utilisés à la place d'itérateurs, mais la différence syntaxique sera visible.
Vraiment, c'est tout ce qu'il y a à dire. Ce n'est pas comme si vous alliez gagner en brièveté dans les deux cas en moyenne, et si l'objectif est vraiment d'être bref, vous pouvez toujours vous rabattre sur les macros.
Je n'utilise pas les itérateurs pour la même raison que je n'aime pas les déclarations. Lorsque vous avez plusieurs boucles internes, il est assez difficile de garder une trace des variables globales/membres sans avoir à mémoriser toutes les valeurs locales et les noms d'itérateurs. Ce que je trouve utile, c’est d’utiliser deux ensembles d’index pour différentes occasions:
for(int i=0;i<anims.size();i++)
for(int j=0;j<bones.size();j++)
{
int animIndex = i;
int boneIndex = j;
// in relatively short code I use indices i and j
... animation_matrices[i][j] ...
// in long and complicated code I use indices animIndex and boneIndex
... animation_matrices[animIndex][boneIndex] ...
}
Je ne veux même pas abréger des choses telles que "animation_matrices [i]" en un "almer_matrix" aléatoire, par exemple, parce que vous ne pouvez pas voir clairement de quel tableau provient cette valeur.
Pour l'indépendance des conteneurs
J'utilise toujours index de tableau parce que beaucoup de mes applications nécessitent quelque chose comme "afficher une vignette". Alors j'ai écrit quelque chose comme ça:
some_vector[0].left=0;
some_vector[0].top =0;<br>
for (int i = 1; i < some_vector.size(); i++)
{
some_vector[i].left = some_vector[i-1].width + some_vector[i-1].left;
if(i % 6 ==0)
{
some_vector[i].top = some_vector[i].top.height + some_vector[i].top;
some_vector[i].left = 0;
}
}
Même mieux que "dire au processeur quoi faire" (impératif), c'est "dire aux bibliothèques ce que vous voulez" (fonctionnel).
Au lieu d’utiliser des boucles, vous devriez donc apprendre les algorithmes présents dans stl.
Les deux implémentations sont correctes, mais je préférerais la boucle 'pour'. Comme nous avons décidé d’utiliser un vecteur et non pas un autre conteneur, l’utilisation d’index serait la meilleure option. L'utilisation d'itérateurs avec des vecteurs perdrait l'avantage même de disposer les objets dans des blocs de mémoire continue, ce qui faciliterait leur accès.