492 votes

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

Quelle est la manière correcte d'itérer sur un vecteur en C++ ?

Considérez ces deux fragments de code, celui-ci fonctionne bien :

for (unsigned i=0; i < polygon.size(); i++) {
    sum += polygon[i];
}

et celui-ci :

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 novice dans le monde du C++, donc les unsigned unsigned les variables peuvent être dangereuses si elles ne sont pas utilisées correctement, donc - est-ce correct ?

11 votes

La réponse non signée est correcte car polygon.size() est de type non signé. Unsigned signifie toujours positif ou 0. C'est tout ce que cela signifie. Donc, si l'utilisation de la variable est toujours seulement pour les comptes, alors unsigned est le bon choix.

0 votes

Pour résoudre le problème de l'avertissement sur les signatures/non signatures, il suffit de remplacer int par uint ( unsigned int ) en i déclaration.

3 votes

@AdamBruss .size() n'est pas de type unsigned alias. unsigned int . Il est de type std::size_t .

857voto

Johannes Schaub - litb Points 256113

Pour l'itération en arrière, voir cette réponse .

L'itération vers l'avant est presque identique. Il suffit de changer les itérateurs / de remplacer le décrément par l'incrément. Vous devriez préférer les itérateurs. Certaines personnes vous disent d'utiliser std::size_t comme type de variable d'index. Cependant, cela n'est pas portable. Il faut toujours utiliser la variable size_type du conteneur (alors que vous pourriez vous en tirer avec une simple conversion dans le cas d'une itération vers l'avant, vous pourriez vous tromper complètement dans le cas d'une itération vers l'arrière si vous utilisez std::size_t au cas où std::size_t est plus large que ce qu'est le typedef de size_type ) :


Utilisation de std::vector

Utilisation des itérateurs

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

L'important est de toujours utiliser la forme préfixe d'incrémentation pour les itérateurs dont vous ne connaissez pas la définition. Cela garantira que votre code s'exécute de manière aussi générique que possible.

Utilisation de Range C++11

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

Utilisation des indices

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

Utilisation des tableaux

Utilisation des itérateurs

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

Utilisation de Range C++11

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

Utilisation des indices

for(std::size_t i = 0; i != (sizeof a / sizeof *a); i++) {
    /* std::cout << a[i]; ... */
}

Lire dans la réponse itérative en arrière quel problème le sizeof approche peut céder à, cependant.

0 votes

Type de taille des pointeurs : l'utilisation de difference_type pourrait être plus portable. essayez iterator_traits<element_type*>::difference_type. c'est une déclaration qui a de la gueule, mais c'est plus portable...

0 votes

Wilhelmtell, pour quoi devrais-je utiliser difference_type ? sizeof est défini pour retourner size_t :) je ne vous comprends pas. si je devais soustraire des pointeurs les uns des autres, difference_type serait le bon choix.

0 votes

L'itération sur des tableaux en utilisant la technique que vous avez mentionnée dans cet article ne fonctionnera pas si l'itération est effectuée dans une fonction sur un tableau passé à cette fonction. Car sizeof array ne retournera que le pointeur sizeof.

180voto

kratenko Points 1960

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 façon agréable de le faire (au prix d'une rupture de la rétrocompatibilité) : la nouvelle fonction auto mot-clé. Il vous évite d'avoir à spécifier explicitement le type de l'itérateur à utiliser (en répétant à nouveau le type de vecteur), alors qu'il est évident (pour le compilateur), quel type utiliser. Avec v étant votre vector vous pouvez faire quelque chose comme ça :

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 itérer sur des collections comme les vecteurs. Elle 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 fonctionnel, construisez 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 où j'écris ces lignes, quand vous compilez ceci avec g++ vous devez normalement le configurer pour qu'il fonctionne avec la nouvelle norme en fournissant un drapeau supplémentaire :

g++ -std=c++0x -o auto auto.cpp

Vous pouvez maintenant 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).

9 votes

C++11 vous donne for (auto& val: vec)

1 votes

@flexo Merci, je ne sais pas comment j'ai pu oublier ça. Je ne fais pas assez de C++, je suppose. Je ne pouvais pas croire qu'il y avait quelque chose d'aussi pratique (je pensais que c'était la syntaxe JavaScript, en fait). J'ai modifié la réponse pour inclure cela.

0 votes

Votre réponse est très bien. Il est regrettable que la version par défaut de g++ dans les différents devkits de l'OS soit inférieure à 4.3, ce qui fait qu'il ne fonctionne pas.

43voto

paxos1977 Points 25088

Dans le cas précis de votre exemple, j'utiliserais les algorithmes STL pour y parvenir.

#include <numeric> 

sum = std::accumulate( polygon.begin(), polygon.end(), 0 );

Pour un cas plus général, mais encore 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 );

38voto

Polat Tuzla Points 483

Concernant 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 consiste à utiliser le mot clé "typename" comme indiqué :

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

2 votes

Vous devez préciser que cela ne s'applique que lorsque T est un argument de modèle, et donc l'expression std::vector<T*>::iterator est un nom dépendant. Pour qu'un nom dépendant soit analysé en tant que type, il doit être précédé de la balise typename comme l'indique le diagnostic.

17voto

Jasper Bekkers Points 4949

Un appel à vector<T>::size() renvoie une valeur de type std::vector<T>::size_type et non pas int, unsigned int ou autre.

De même, en général, l'itération sur un conteneur en C++ se fait à l'aide de itérateurs 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).

0 votes

Les itérateurs sont généralement une bonne idée, bien que je doute qu'il soit nécessaire de stocker "end" dans une variable séparée et tout peut être fait dans une instruction for(; ;).

1 votes

Je sais que begin() et end() sont amortis en temps constant, mais je trouve généralement que c'est plus lisible que de tout entasser en une seule ligne.

3 votes

Vous pouvez diviser le for en plusieurs lignes pour améliorer la lisibilité. Déclarer les itérateurs en dehors de la boucle signifie que vous devez utiliser un nom d'itérateur différent pour chaque boucle sur des conteneurs de types différents.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X