web-dev-qa-db-fra.com

Itération sur std :: vector: variable d'index non signée vs signée

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?

423
Yuval Adam

Itérer en arrière

Voir cette réponse

Itérations en avant

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):

Utiliser std :: vector

Utiliser les itérateurs

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. 

Utiliser Range C++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Utilisation d'indices

for(std::vector<int>::size_type i = 0; i != v.size(); i++) {
    /* std::cout << v[i]; ... */
}

Utiliser des tableaux

Utiliser les itérateurs

for(element_type* it = a; it != (a + (sizeof a / sizeof *a)); it++) {
    /* std::cout << *it; ... */
}

Utiliser Range C++ 11

for(auto const& value: a) {
     /* std::cout << value; ... */

Utilisation d'indices

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.

730

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).

154
kratenko

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 );
43
paxos1977

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) {
...
38
Polat Tuzla

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).

16
Jasper Bekkers

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 type size_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 que size_t est identique à unsigned int, ce qui peut entraîner des erreurs de programmation, notamment lorsque les architectures 64 bits deviennent plus courantes.

11
Igor Oks

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).

6
ecoffey

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.

6
Martin Cote

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
}
5
Jan Turoň

En C++ 11

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;
}
5
jave.web
for (vector<int>::iterator it = polygon.begin(); it != polygon.end(); it++)
    sum += *it; 
4
Mehrdad Afshari

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.

2
Charlie Martin

Déterminez si vous avez besoin d'itérer du tout

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.

2
Toby Speight

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)
0
Pierre

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.

0
user9758081

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.

0
Brett L