Choix du type de variables d'index

11

La plupart du temps, nous utilisons le type entier pour représenter les variables d'index. Mais dans certaines situations, nous sommes obligés de choisir

std::vector<int> vec;
....

for(int i = 0; i < vec.size(); ++i)
....

Cela entraînera le compilateur à déclencher l'avertissement que l'utilisation mixte de variables signées / non signées. si je crée la variable d'index en tant que for( size_t i = 0; i < vec.size(); i++ )(ou an unsigned int), cela résoudra les problèmes.

Lorsqu'il est plus spécifique d'utiliser des types de fenêtres, la plupart des API Windows traitent avec DWORD (qui est typé comme non signé depuis longtemps).

Donc, lorsque j'utilise une itération similaire, cela provoquera à nouveau le même avertissement. Maintenant, si je le réécris comme

DWORD dwCount;
....

for(DWORD i = 0; i < dwCount; ++i)
....

Je trouve ça un peu bizarre. Ce pourrait être le problème avec les perceptions.

Je suis d'accord que nous sommes censés utiliser le même type de variable d'index pour éviter que les problèmes de plage ne se produisent avec les variables d'index. Par exemple, si nous utilisons

_int64 i64Count; // 
....

for(_int64 i = 0; i < i64Count; ++i)
....

Mais dans le cas de DWORD, ou d'entiers non signés, y a-t-il des problèmes à le réécrire comme

for(int i = 0; (size_t)i < vec.size(); ++i)

Comment la plupart des gens travaillent avec des problèmes similaires?

Sarat
la source
4
Pourquoi utiliseriez-vous un entier signé pour représenter l'index? C'est comme utiliser un vecteur d'entiers pour stocker une chaîne.
Šimon Tóth
3
@Let_Me_Be: Parce que cela rend les conditions aux limites plus faciles à tester. Par exemple, sur un compte à rebours jusqu'à zéro, un test inférieur à avant d'exécuter le corps de la boucle ne peut pas fonctionner avec une valeur non signée. De même, un décompte jusqu'au maximum ne fonctionne pas. Bien sûr, dans ce cas, un entier signé ne fonctionnera pas non plus (car il ne peut pas représenter une valeur aussi élevée).
Yttrill
"Cela entraînera le compilateur à déclencher l'avertissement que l'utilisation mixte de variables signées / non signées." Ce n'est qu'un des deux problèmes auxquels vous devrez faire face. Dans de nombreux cas, std::size_tc'est un rang plus élevé que int (ou même long). Si jamais la taille du vecteur dépasse std::numeric_limits<int>::max(), vous regretterez d'avoir utilisé int.
Adrian McCarthy

Réponses:

11

vector a un typedef qui vous indique le type correct à utiliser: -

for(std::vector<int>::size_type i = 0; i < thing.size(); ++i)
{
}

Il est presque toujours défini comme étant size_t mais vous ne pouvez pas vous y fier

JohnB
la source
8
Pas vraiment une amélioration de la lisibilité, à mon humble avis.
Doc Brown
Non, mais comme c'est le seul moyen de savoir quel est le type correct à utiliser pour un index dans le vecteur, cela n'a pas vraiment d'importance ...
JohnB
4
Ces jours-ci en c ++ 11, utilisez simplement auto
JohnB
6
@JohnB Vous voulez dire comme auto i = 0? Cela n'aide pas du tout, idevenir un int.
zénith
1
La lisibilité peut être améliorée, avec using index_t = std::vector<int>::size_type;.
Toby Speight du
4
std::vector<int> vec;

for(int i = 0; i < vec.size(); ++i)

Utilisez un itérateur pour cela, pas une forboucle.

Pour les autres, tant que le type de variable est de la même taille, static_castdevrait fonctionner très bien (c'est- DWORDà- dire pour int16_t)

Demian Brecht
la source
2
for (std::vector<int>::iterator i = vec.begin(); i != vec.end(); ++i)est une douleur à écrire. Avoir for (auto i = vec.begin();...est beaucoup plus lisible. Bien sûr, foreachest également en C ++ 11.
David Thornley
3

Le cas que vous avez décrit est également une des choses que je n'aime pas en C ++. Mais j'ai appris à vivre avec ça, soit en utilisant

for( size_t i = 0; i < vec.size(); i++ )

ou

for( int i = 0; i < (int)vec.size(); i++ )

(bien sûr, ce dernier uniquement lorsqu'il n'y a aucun risque de débordement int).

Doc Brown
la source
3

La raison pour laquelle il vous avertit de la comparaison entre signé et non signé est que la valeur signée sera probablement convertie en non signé, ce qui pourrait ne pas être ce que vous attendez.

Dans votre exemple (comparé intà size_t), intsera implicitement converti en size_t(à moins intque sa portée ne soit en quelque sorte supérieure size_t). Ainsi, si le intest négatif, il sera probablement supérieur à la valeur à laquelle vous le comparez en raison de l'habillage. Ce ne sera pas un problème si votre index n'est jamais négatif, mais vous obtiendrez toujours cet avertissement.

Au lieu de cela, utilisez un type non signé ( par exemple unsigned int, size_tou, comme John B recommande , std::vector<int>::size_type) pour votre variable d'index:

for(unsigned int i = 0; i < vec.size(); i++)

Soyez prudent lors du décompte, cependant:

for(unsigned int i = vec.size()-1; i >= 0; i--) // don't do this!

Ce qui précède ne fonctionnera pas car il i >= 0est toujours vrai lorsqu'il in'est pas signé. Utilisez plutôt "l' opérateur flèche " pour les boucles qui décomptent:

for (unsigned int i = vec.size(); i-- > 0; )
    vec[i] = ...;

Comme d'autres réponses le soulignent, vous devez normalement utiliser un itérateur pour parcourir a vector. Voici la syntaxe C ++ 11:

for (auto i = vec.begin(); i != vec.end(); ++i)
Joey Adams
la source
1
Cela court toujours le risque qu'un unsigned intne soit pas assez grand pour contenir la taille.
Adrian McCarthy
2

Une nouvelle option pour C ++ 11, vous pouvez faire des choses comme suit

for(decltype(vec.size()) i = 0; i < vec.size(); ++i) {...}

et

for(decltype(dWord) i = 0; i < dWord; ++i) {...}

Bien qu'il répète un peu plus que la boucle for de base, il n'est pas aussi long que les façons de spécifier les valeurs d'avant '11, et l'utilisation de ce modèle fonctionnera de manière cohérente pour la plupart, sinon la totalité, des termes possibles que vous auriez veulent comparer, ce qui le rend idéal pour la refactorisation de code. Cela fonctionne même pour des cas simples comme celui-ci:

int x = 3; int final = 32; for(decltype(final) i = x; i < final; ++i)

De plus, bien que vous deviez utiliser autochaque fois que vous définissez iune valeur intelligente (comme vec.begin()), cela decltypefonctionne lorsque vous définissez une constante comme zéro, où auto résoudrait simplement cela intcar 0 est un simple littéral entier.

Pour être honnête, j'aimerais voir un mécanisme de compilation pour étendre la autodétermination de type pour les incrémenteurs de boucle afin de regarder la valeur à comparer.

matthias
la source
1

J'utilise un cast en int, comme dans for (int i = 0; i < (int)v.size(); ++i). Oui, c'est moche. Je le blâme sur la conception stupide de la bibliothèque standard où ils ont décidé d'utiliser des entiers non signés pour représenter les tailles. (Pour ... quoi? Étendre la plage d'un bit?)

zvrba
la source
1
Dans quelle situation une taille de collection de quelque chose de négatif aurait-elle un sens? L'utilisation de nombres entiers non signés pour différentes tailles de collection me semble un choix judicieux . La dernière fois que j'ai vérifié, prendre la longueur d'une chaîne retournait rarement un résultat négatif non plus ...
un CVn
1
Dans quelle situation serait-il significatif pour un test simple tel que if(v.size()-1 > 0) { ... }retourner vrai pour un conteneur vide? Le problème est que les tailles sont également souvent utilisées en arithmétique, en particulier. avec des conteneurs basés sur l'index, ce qui pose problème étant donné qu'ils ne sont pas signés. Fondamentalement, l'utilisation de types non signés pour autre chose que 1) des manipulations au niveau du bit ou 2) l'arithmétique modulaire appelle des ennuis.
zvrba
2
bon point. Bien que je ne vois pas vraiment le point de votre exemple particulier (j'écrirais probablement juste if(v.size() > 1) { ... }car cela rend l'intention plus claire, et comme bonus supplémentaire la question du signé / non signé devient nulle), je vois comment dans certains cas spécifiques la signature pourrait être utile. Je me suis trompé.
un CVn du
1
@Michael: Je suis d'accord que c'était un exemple artificiel. Pourtant, j'écris souvent des algorithmes avec des boucles imbriquées: pour (i = 0; i <v.size () - 1; ++ i) pour (j = i + 1; j <v.size (); ++ j) .. si v est vide, la boucle externe s'exécute (size_t) -1 fois. Donc, je dois soit vérifier v.empty () avant la boucle, soit convertir v.size () en un type signé, qui, selon moi, sont des solutions de contournement moches. Je choisis un casting car il y a moins de LOC, pas de if () s => moins de possibilités d'erreur. (En outre, dans le 2e complément, la conversion oveflow renvoie un nombre négatif, de sorte que la boucle ne s'exécute pas du tout.)
zvrba
L'extension de la plage de 1 bit était (et continue d'être) très utile dans les systèmes 16 bits.
Adrian McCarthy