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?
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.
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:
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 trivialestd::for_each()
sur la boucle for
elle-mêmestd::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;
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?
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();
}
Il existe deux bonnes raisons d'utiliser des itérateurs, dont certains sont mentionnés ici:
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.
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.
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.
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 []
.