C'est une question plutôt idiote, mais pourquoi int
est-il couramment utilisé au lieu de unsigned int
lors de la définition d'une boucle for pour un tableau en C ou C++?
for(int i;i<arraySize;i++){}
for(unsigned int i;i<arraySize;i++){}
Je reconnais les avantages d'utiliser int
lorsque vous effectuez une opération autre que l'indexation de tableau et les avantages d'un itérateur lorsque vous utilisez des conteneurs C++. Est-ce simplement parce que cela n'a pas d'importance lors de la boucle dans un tableau? Ou devrais-je éviter tout cela ensemble et utiliser un type différent tel que size_t
?
C'est un phénomène plus général, souvent les gens n'utilisent pas les types corrects pour leurs entiers. Le C moderne a des caractères de type sémantique qui sont bien préférables aux types entiers primitifs. Tout ce qui est une "taille", par exemple, doit simplement être saisi en tant que size_t
. Si vous utilisez systématiquement les types sémantiques pour vos variables d'application, les variables de boucle sont également beaucoup plus simples avec ces types.
Et j’ai vu plusieurs bugs difficiles à détecter, dus à l'utilisation de int
environ. Code que tout d'un coup s'est écrasé sur de grandes matrices et des trucs comme ça. Il suffit d'éviter de coder correctement avec les types corrects.
L'utilisation de int
est plus correcte d'un point de vue logique pour l'indexation d'un tableau.
unsigned
sémantique en C et C++ ne signifie pas vraiment "pas négatif" mais plutôt "bitmask" ou "modulo integer".
Pour comprendre pourquoi unsigned
n’est pas un bon type pour un nombre "non négatif", veuillez considérer
Évidemment, aucune des phrases ci-dessus n’a de sens ... mais c’est comme cela que fonctionnent C et C++ unsigned
sémantique.
En fait, utiliser un type unsigned
pour la taille des conteneurs est une erreur de conception de C++ et, malheureusement, nous sommes désormais condamnés à utiliser ce mauvais choix pour toujours (pour des raisons de compatibilité ascendante). Vous pouvez aimer le nom "unsigned" parce qu'il ressemble à "non-négatif" mais le nom n'a aucune pertinence et ce qui compte, c'est la sémantique ... et unsigned
est très loin d'être "non-négatif".
Pour cette raison, lors du codage de la plupart des boucles sur des vecteurs, ma forme préférée est:
for (int i=0,n=v.size(); i<n; i++) {
...
}
(Bien sûr, en supposant que la taille du vecteur ne change pas pendant l'itération et que j'ai réellement besoin de l'index dans le corps, sinon la fonction for (auto& x : v)...
est meilleure).
Cette fuite de unsigned
dès que possible et l'utilisation d'entiers simples ont l'avantage d'éviter les pièges résultant d'une erreur de conception de unsigned size_t
. Par exemple, considérons:
// draw lines connecting the dots
for (size_t i=0; i<pts.size()-1; i++) {
drawLine(pts[i], pts[i+1]);
}
le code ci-dessus posera des problèmes si le vecteur pts
est vide car pts.size()-1
est un nombre non-sens énorme dans ce cas. Traiter avec des expressions où a < b-1
n'est pas la même chose que a+1 < b
, même pour les valeurs couramment utilisées, c'est comme danser dans un champ de mines.
Historiquement, la justification pour avoir size_t
non signé est de pouvoir utiliser le bit supplémentaire pour les valeurs, par exemple. pouvoir avoir 65535 éléments dans les tableaux au lieu de 32767 sur les plates-formes 16 bits. À mon avis, même à ce moment-là, le coût supplémentaire de ce mauvais choix sémantique ne valait pas le gain (et si 32 767 éléments ne suffisent pas, 65535 ne suffira pas de toute façon).
Les valeurs non signées sont excellentes et très utiles, mais PAS pour représenter la taille du conteneur ou pour les index; pour la taille et l’index, les entiers signés normaux fonctionnent beaucoup mieux car la sémantique correspond à ce que vous attendez.
Les valeurs non signées constituent le type idéal lorsque vous avez besoin de la propriété arithmétique modulo ou lorsque vous souhaitez travailler au niveau du bit.
C'est purement la paresse et l'ignorance. Vous devez toujours utiliser les bons types pour les index et, à moins que vous ne disposiez d'informations supplémentaires limitant la plage d'index possibles, size_t
est le bon type.
Bien sûr, si la dimension était lue à partir d'un champ à un octet dans un fichier, vous savez qu'elle se situe dans la plage 0-255 et que int
serait un type d'index parfaitement raisonnable. De même, int
conviendrait si vous utilisez un nombre fixe de boucles, par exemple 0 à 99. Mais il existe encore une autre raison de ne pas utiliser int
: si vous utilisez i%2
dans votre corps de boucle pour traiter différemment les indices pairs/impairs, i%2
est beaucoup plus cher quand i
est signé que quand i
est non signé ...
Pas beaucoup de différence. Un avantage de int
est sa signature. Ainsi, int i < 0
a un sens, alors que unsigned i < 0
ne fait pas grand chose.
Si les index sont calculés, cela peut être bénéfique (par exemple, vous pourriez avoir des cas où vous ne ferez jamais une boucle si un résultat est négatif).
Et oui, c'est moins d'écrire :-)
L'utilisation de int
pour indexer un tableau est héritée, mais toujours largement adoptée. int
est simplement un type de numéro générique et ne correspond pas aux capacités d'adressage de la plate-forme. Au cas où il serait plus court ou plus long que cela, vous pourriez rencontrer des résultats étranges lorsque vous tentez d’indexer un très grand tableau qui va au-delà.
Sur les plates-formes modernes, off_t
, ptrdiff_t
et size_t
garantissent une plus grande portabilité.
Un autre avantage de ces types est qu'ils donnent context à quelqu'un qui lit le code. Lorsque vous voyez les types ci-dessus, vous savez que le code effectuera un indice sous forme de tableau ou une arithmétique de pointeur, pas n'importe quel calcul.
Donc, si vous voulez écrire du code à l'épreuve des balles, portable et sensible au contexte, vous pouvez le faire au détriment de quelques frappes.
GCC supporte même une extension typeof
qui vous évite de taper le même nom de type partout:
typeof(arraySize) i;
for (i = 0; i < arraySize; i++) {
...
}
Ensuite, si vous modifiez le type de arraySize
, le type de i
change automatiquement.
J'utilise int
car il nécessite moins de typage physique et peu importe - ils occupent le même espace disponible. À moins que votre tableau ne contienne quelques milliards d'éléments, vous ne déborderez pas si vous n'utilisez pas de compilateur 16 bits , ce que je ne suis généralement pas.
Cela dépend vraiment du codeur. Certains codeurs préfèrent le perfectionnisme de type, ils utiliseront donc le type comparé. Par exemple, s'ils parcourent une chaîne C, vous pouvez voir:
size_t sz = strlen("hello");
for (size_t i = 0; i < sz; i++) {
...
}
Bien qu’ils fassent quelque chose 10 fois, vous verrez probablement toujours int
:
for (int i = 0; i < 10; i++) {
...
}
Parce que, sauf si vous avez un tableau dont la taille est supérieure à deux gigaoctets de type char
, ou 4 gigaoctets de type short
ou 8 gigaoctets de type int
etc., le fait que la variable soit signée ou non importe peu.
Alors, pourquoi taper plus quand on peut taper moins?
Outre le fait qu'il est plus court de taper, la raison en est qu'il permet des nombres négatifs.
Comme nous ne pouvons pas dire à l'avance si une valeur peut être négative, la plupart des fonctions utilisant des arguments entiers prennent la variété signée. Comme la plupart des fonctions utilisent des entiers signés, il est souvent moins fastidieux d’utiliser des entiers signés pour des choses comme des boucles. Sinon, vous risquez de devoir ajouter un tas de dactylographes.
Au fur et à mesure que nous passons aux plates-formes 64 bits, la plage non signée d'un entier signé devrait être largement suffisante pour la plupart des applications. Dans ces cas, il n'y a pas beaucoup de raison de ne pas utiliser un entier signé.
Prenons l'exemple simple suivant:
int max = some_user_input; // or some_calculation_result
for(unsigned int i = 0; i < max; ++i)
do_something;
Si max
se trouve être une valeur négative, disons -1, le -1
sera considéré comme UINT_MAX
(lorsque deux entiers avec le même rang mais une signature différente sont comparés, celui qui est signé sera traité comme un non signé). D'autre part, le code suivant n'aurait pas ce problème:
int max = some_user_input;
for(int i = 0; i < max; ++i)
do_something;
Donnez une valeur max
négative, la boucle sera ignorée en toute sécurité.