Quelle est la bonne façon de parcourir un vecteur en C++?
Considérons ces deux fragments de code, celui-ci fonctionne bien:
for (unsigned i=0; i < polygon.size(); i++) {
sum += polygon[i];
}
et celui-là:
for (int i=0; i < polygon.size(); i++) {
sum += polygon[i];
}
qui génère warning: comparison between signed and unsigned integer expressions
.
Je suis nouveau dans le monde du C++, donc la variable unsigned
me parait un peu effrayante et je sais que les variables unsigned
peuvent être dangereuses si elles ne sont pas utilisées correctement, alors - est-ce correct?
Voir cette réponse .
C'est presque identique. Il suffit de changer les itérateurs/swap décrément par incrément. Vous devriez préférer les itérateurs. Certaines personnes vous conseillent d'utiliser std::size_t
comme type de variable d'index. Cependant, ce n'est pas portable. Utilisez toujours le typedef size_type
du conteneur (bien que vous puissiez vous échapper avec une conversion uniquement dans le cas d’itération en aval, il est possible qu’il se trompe complètement dans le cas d’itération en arrière lorsque std::size_t
est utilisé, dans le cas où std::size_t
est plus large que ce qui est le cas. typedef de size_type
):
for(std::vector<T>::iterator it = v.begin(); it != v.end(); ++it) {
/* std::cout << *it; ... */
}
Il est important de toujours utiliser le formulaire d'incrément de préfixe pour les itérateurs dont vous ne connaissez pas les définitions. Cela garantira que votre code est aussi générique que possible.
for(auto const& value: a) {
/* std::cout << value; ... */
for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
/* std::cout << v[i]; ... */
}
for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
/* std::cout << *it; ... */
}
for(auto const& value: a) {
/* std::cout << value; ... */
for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
/* std::cout << a[i]; ... */
}
Lisez la réponse itérative en arrière à quel problème l’approche sizeof
peut céder, cependant.
Quatre ans ont passé, Google m'a donné cette réponse. Avec le standard C++ 11 (alias C++ 0x), il existe en fait une nouvelle manière agréable de procéder (au prix de la compatibilité en amont): le nouveau mot clé auto
. Cela vous évite d'avoir à spécifier explicitement le type d'itérateur à utiliser (en répétant le type de vecteur), lorsqu'il est évident (pour le compilateur), quel type d'utiliser. v
étant votre vector
, vous pouvez faire quelque chose comme ceci:
for ( auto i = v.begin(); i != v.end(); i++ ) {
std::cout << *i << std::endl;
}
C++ 11 va encore plus loin et vous donne une syntaxe spéciale pour parcourir des collections comme des vecteurs. Cela supprime la nécessité d'écrire des choses qui sont toujours les mêmes:
for ( auto &i : v ) {
std::cout << i << std::endl;
}
Pour le voir dans un programme de travail, créez un fichier auto.cpp
:
#include <vector>
#include <iostream>
int main(void) {
std::vector<int> v = std::vector<int>();
v.Push_back(17);
v.Push_back(12);
v.Push_back(23);
v.Push_back(42);
for ( auto &i : v ) {
std::cout << i << std::endl;
}
return 0;
}
Au moment de l'écriture, lorsque vous compilez ceci avec g ++, vous devez normalement le configurer pour qu'il fonctionne avec la nouvelle norme en donnant un indicateur supplémentaire:
g++ -std=c++0x -o auto auto.cpp
Maintenant, vous pouvez exécuter l'exemple:
$ ./auto
17
12
23
42
Veuillez noter que les instructions sur la compilation et l'exécution sont spécifiques à gnu c ++ compilateur sur Linux, le programme doit être indépendant de la plate-forme (et du compilateur).
Dans le cas spécifique de votre exemple, j'utiliserais les algorithmes STL pour accomplir cela.
#include <numeric>
sum = std::accumulate( polygon.begin(), polygon.end(), 0 );
Pour un cas plus général, mais quand même assez simple, je choisirais:
#include <boost/lambda/lambda.hpp>
#include <boost/lambda/bind.hpp>
using namespace boost::lambda;
std::for_each( polygon.begin(), polygon.end(), sum += _1 );
En ce qui concerne la réponse de Johannes Schaub:
for(std::vector<T*>::iterator it = v.begin(); it != v.end(); ++it) {
...
}
Cela peut fonctionner avec certains compilateurs mais pas avec gcc. Le problème ici est la question de savoir si std :: vector :: iterator est un type, une variable (membre) ou une fonction (méthode). Nous obtenons l'erreur suivante avec gcc:
In member function ‘void MyClass<T>::myMethod()’:
error: expected `;' before ‘it’
error: ‘it’ was not declared in this scope
In member function ‘void MyClass<T>::sort() [with T = MyClass]’:
instantiated from ‘void MyClass<T>::run() [with T = MyClass]’
instantiated from here
dependent-name ‘std::vector<T*,std::allocator<T*> >::iterator’ is parsed as a non-type, but instantiation yields a type
note: say ‘typename std::vector<T*,std::allocator<T*> >::iterator’ if a type is meant
La solution utilise le mot clé 'typename' comme indiqué:
typename std::vector<T*>::iterator it = v.begin();
for( ; it != v.end(); ++it) {
...
Un appel à vector<T>::size()
renvoie une valeur de type std::vector<T>::size_type
, pas int, unsigned int ou autre.
De plus, l'itération sur un conteneur en C++ est généralement effectuée à l'aide de iterators , comme ceci.
std::vector<T>::iterator i = polygon.begin();
std::vector<T>::iterator end = polygon.end();
for(; i != end; i++){
sum += *i;
}
Où T est le type de données que vous stockez dans le vecteur.
Ou en utilisant les différents algorithmes d'itération (std::transform
, std::copy
, std::fill
, std::for_each
et cetera).
Utilisez size_t
:
for (size_t i=0; i < polygon.size(); i++)
Citant Wikipedia :
Les fichiers d'en-tête stdlib.h et stddef.h définissent un type de données appelé
size_t
, utilisé pour représenter la taille d'un objet. Les fonctions de bibliothèque prenant des tailles s’attendent à ce qu’elles soient de typesize_t
, et l’opérateur sizeof est évalué àsize_t
.Le type actuel de
size_t
dépend de la plate-forme; une erreur courante est de supposer quesize_t
est identique à unsigned int, ce qui peut entraîner des erreurs de programmation, notamment lorsque les architectures 64 bits deviennent plus courantes.
Un peu d'histoire:
Pour indiquer si un nombre est négatif ou non, utilisez un bit de «signe». int
est un type de données signé, ce qui signifie qu'il peut contenir des valeurs positives et négatives (environ -2 à 2 milliards). Unsigned
ne peut stocker que des nombres positifs (et puisqu'il ne gaspille pas un peu les métadonnées, il peut en stocker davantage: 0 à environ 4 milliards).
std::vector::size()
renvoie une unsigned
, car comment un vecteur pourrait-il avoir une longueur négative?
L'avertissement vous indique que l'opérande de droite de votre déclaration d'inégalité peut contenir plus de données que la gauche.
Essentiellement, si vous avez un vecteur avec plus de 2 milliards d’entrées et que vous utilisez un entier pour l’indexation, vous rencontrerez des problèmes de débordement (l’int sera de nouveau négatif).
J'utilise habituellement BOOST_FOREACH:
#include <boost/foreach.hpp>
BOOST_FOREACH( vector_type::value_type& value, v ) {
// do something with 'value'
}
Cela fonctionne sur les conteneurs STL, les tableaux, les chaînes de style C, etc.
Pour être complète, la syntaxe C++ 11 active une seule version des itérateurs ( ref ):
for(auto it=std::begin(polygon); it!=std::end(polygon); ++it) {
// do something with *it
}
Ce qui est également confortable pour l'itération inverse
for(auto it=std::end(polygon)-1; it!=std::begin(polygon)-1; --it) {
// do something with *it
}
J'utiliserais des algorithmes généraux tels que for_each
pour éviter de rechercher le bon type d'itérateur et d'expression lambda afin d'éviter des fonctions/objets nommés supplémentaires.
Le court "joli" exemple pour votre cas particulier (en supposant que polygone est un vecteur d’entiers):
for_each(polygon.begin(), polygon.end(), [&sum](int i){ sum += i; });
testé sur: http://ideone.com/i6Ethd
Ne pas oublier de inclure: algorithme et, bien sûr, le vecteur :)
Microsoft a en fait aussi un bel exemple à ce sujet:
source: http://msdn.Microsoft.com/en-us/library/dd293608.aspx
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
int main()
{
// Create a vector object that contains 10 elements.
vector<int> v;
for (int i = 1; i < 10; ++i) {
v.Push_back(i);
}
// Count the number of even numbers in the vector by
// using the for_each function and a lambda.
int evenCount = 0;
for_each(v.begin(), v.end(), [&evenCount] (int n) {
cout << n;
if (n % 2 == 0) {
cout << " is even " << endl;
++evenCount;
} else {
cout << " is odd " << endl;
}
});
// Print the count of even numbers to the console.
cout << "There are " << evenCount
<< " even numbers in the vector." << endl;
}
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
sum += *it;
Le premier est le type correct et correct au sens strict. (Si vous y réfléchissez, la taille ne peut jamais être inférieure à zéro.) Cet avertissement me semble toutefois être l’un des bons candidats.
L'en-tête standard <algorithm>
nous fournit des fonctionnalités pour ceci:
using std::begin; // allows argument-dependent lookup even
using std::end; // if the container type is unknown here
auto sum = std::accumulate(begin(polygon), end(polygon), 0);
Les autres fonctions de la bibliothèque d'algorithmes exécutent des tâches courantes - assurez-vous de connaître les ressources disponibles si vous souhaitez économiser votre effort.
Détail obscur mais important: si vous dites "pour (auto it)" comme suit, vous obtenez une copie de l'objet, pas l'élément réel:
struct Xs{int i} x;
x.i = 0;
vector <Xs> v;
v.Push_back(x);
for(auto it : v)
it.i = 1; // doesn't change the element v[0]
Pour modifier les éléments du vecteur, vous devez définir l'itérateur comme référence:
for(auto &it : v)
Les deux segments de code fonctionnent de la même manière. Unsigned int "route est correcte. L'utilisation de types unsigned int fonctionnera mieux avec le vecteur de l'instance où vous l'avez utilisé. L'appel de la fonction membre size () sur un vecteur renvoie une valeur entière non signée. Vous souhaitez donc comparer la variable. "i" à une valeur de son propre type.
De plus, si vous êtes encore un peu inquiet de la façon dont "unsigned int" apparaît dans votre code, essayez "uint". Ceci est fondamentalement une version abrégée de "unsigned int" et cela fonctionne exactement de la même manière. Vous n'avez également pas besoin d'inclure d'autres en-têtes pour l'utiliser.
Si votre compilateur le prend en charge, vous pouvez utiliser une plage basée sur pour accéder aux éléments vectoriels:
vector<float> vertices{ 1.0, 2.0, 3.0 };
for(float vertex: vertices){
std::cout << vertex << " ";
}
Tirages: 1 2 3. Notez que vous ne pouvez pas utiliser cette technique pour modifier les éléments du vecteur.