web-dev-qa-db-fra.com

Itérer dans un vecteur C ++ à l'aide d'une boucle 'pour'

Je suis nouveau dans le langage C++. J'ai commencé à utiliser des vecteurs et j'ai remarqué que dans tout le code que je lis pour itérer à travers un vecteur via des index, le premier paramètre de la boucle for est toujours basé sur le vecteur. Dans Java, je pourrais faire quelque chose comme ceci avec un ArrayList:

for(int i=0; i < vector.size(); i++){
   vector[i].doSomething();
}

Y a-t-il une raison pour laquelle je ne vois pas cela en C++? Est-ce une mauvaise pratique?

112
Flynn

Y a-t-il une raison pour laquelle je ne vois pas cela en C++? Est-ce une mauvaise pratique?

Non, ce n'est pas une mauvaise pratique, mais cela rend votre code certain flexibilité.

Avant le code C++ 11, le code permettant d'itérer des éléments de conteneur utilisait des itérateurs, comme:

std::vector<int>::iterator it = vector.begin();

C'est parce que cela rend le code plus flexible.

Tous les conteneurs de bibliothèque standard prennent en charge et fournissent des itérateurs. Etant donné que si, à un stade ultérieur du développement, vous devez changer de conteneur, ce code n'a pas besoin d'être modifié.

Remarque: L'écriture d'un code qui fonctionne avec tous les conteneurs de bibliothèques standard possibles n'est pas aussi facile qu'il y paraît.

78
Alok Save

La raison pour laquelle vous ne voyez pas une telle pratique est assez subjective et ne peut pas avoir de réponse précise, car j'ai vu beaucoup de code qui utilise votre manière mentionnée plutôt que le code de style iterator.

Vous pouvez suivre les raisons pour lesquelles les personnes ne considèrent pas vector.size() manière de mettre en boucle:

  1. Être paranoïaque à propos d'appeler size() à chaque fois dans l'état de boucle. Cependant, soit il ne s'agit pas d'un problème, soit il peut être résolu de manière triviale
  2. Préférer std::for_each() sur la boucle for elle-même
  3. Le remplacement ultérieur du conteneur de std::vector par un autre (par exemple, map, list) exigera également le changement du mécanisme de bouclage, car tous les conteneurs ne prennent pas en charge le style de bouclage size().

C++ 11 offre une bonne facilité pour se déplacer dans les conteneurs. Cela s'appelle "plage basée sur la boucle for" (ou "améliorée pour la boucle" en Java).

Avec peu de code, vous pouvez parcourir l'intégralité (obligatoire!) std::vector:

vector<int> vi;
...
for(int i : vi) 
  cout << "i = " << i << endl;
89
iammilind

Le moyen le plus propre de parcourir un vecteur consiste à utiliser des itérateurs:

for (auto it = begin (vector); it != end (vector); ++it) {
    it->doSomething ();
}

ou (équivalent à ce qui précède)

for (auto & element : vector) {
    element.doSomething ();
}

Avant C++ 0x, vous devez remplacer auto par le type itérateur et utiliser des fonctions membres au lieu de fonctions globales begin et end.

C'est probablement ce que vous avez vu. Par rapport à l'approche que vous avez mentionnée, l'avantage est que vous ne dépendez pas beaucoup du type de vector. Si vous remplacez vector par une autre classe "collection-type", votre code fonctionnera probablement encore. Vous pouvez cependant faire quelque chose de similaire dans Java. Il n'y a pas beaucoup de différence conceptuellement; C++, cependant, utilise des modèles pour implémenter cela (par rapport aux génériques en Java); Par conséquent, l'approche fonctionnera pour tous les types pour lesquels les fonctions begin et end sont définies, même pour les types non-classes tels que les tableaux statiques. Voir ici: Comment la plage est-elle basée sur le travail pour les tableaux simples?

71
JohnB

La bonne façon de faire est:

for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
    it->doSomething();
 }

Où T est le type de la classe à l'intérieur du vecteur. Par exemple, si la classe était CActivity, écrivez simplement CActivity au lieu de T.

Ce type de méthode fonctionnera sur toutes les LIST (pas seulement les vecteurs, ce qui est un peu meilleur).

Si vous souhaitez toujours utiliser des index, la procédure est la suivante:

for(std::vector<T>::size_type i = 0; i != v.size(); i++) {
    v[i].doSomething();
}
33
DiGMi

Il existe deux bonnes raisons d'utiliser des itérateurs, dont certains sont mentionnés ici:

Changer de conteneur ultérieurement n'invalide pas votre code.

c'est-à-dire que si vous passez d'un std :: vector à un std :: list ou à std :: set, vous ne pouvez pas utiliser d'indices numériques pour obtenir votre valeur contenue. L'utilisation d'un itérateur est toujours valide.

Capture à l'exécution d'une itération invalide

Si vous modifiez votre conteneur au milieu de votre boucle, la prochaine fois que vous utiliserez votre itérateur, une exception itérateur non valide sera émise.

8
Eddie Parker

J'ai été surpris que personne n'ait mentionné le fait qu'itérer dans un tableau d'indice entier facilite l'écriture de code défectueux en souscrivant un tableau avec le mauvais index. Par exemple, si vous avez des boucles imbriquées en utilisant i et j comme indices, vous pouvez attribuer un indice incorrect à un tableau avec j plutôt que i et ainsi introduire une erreur dans le programme. .

En revanche, les autres formes listées ici, à savoir la boucle basée sur la plage for, et les itérateurs, sont beaucoup moins sujets aux erreurs. La sémantique de la langue et le mécanisme de vérification de type du compilateur vous empêcheront d'accéder accidentellement à un tableau en utilisant le mauvais index.

4
Diomidis Spinellis

Avec STL, les programmeurs utilisent iterators pour parcourir les conteneurs, l’itérateur étant un concept abstrait, implémenté dans tous les conteneurs standard. Par exemple, std::list n'a aucun operator [].

4
ForEveR