Pourquoi les types comptent en C++
Sur la page des variables, vous avez déclaré des valeurs avec un type explicite comme int age = 30;. Ce type n'est pas qu'une étiquette - il indique au compilateur combien d'octets réserver, comment interpréter ces octets et quelles opérations sont autorisées. Choisissez le mauvais type et vous pouvez perdre silencieusement de la précision, déborder ou provoquer un comportement indéfini.
Le C++ regroupe ses types intégrés en quelques familles : entiers, nombres à virgule flottante, un type caractère et un booléen. Examinons chacun d'eux, puis les règles qui font trébucher les gens.
Les types fondamentaux
Voici un exemple de chaque type principal dans un seul programme. Remarquez les suffixes des littéraux (L, f, u) et les apostrophes simples sur char :
Un bool s'affiche par défaut comme 1 ou 0, et non true/false. Un char utilise des apostrophes simples - 'A' est un caractère, tandis que "A" (guillemets doubles) est un littéral de chaîne, un type complètement différent. Ces deux erreurs sont extrêmement courantes au début.
Les tailles ne sont pas fixes
C'est la plus grande surprise quand on vient de langages comme Java. La norme C++ ne garantit que les tailles minimales et un ordre relatif (short ≤ int ≤ long ≤ long long). La taille réelle dépend de votre compilateur et de votre plateforme. Vérifiez toujours avec sizeof :
Sur une compilation Linux 64 bits typique, vous verrez int = 4, long = 8. Sous Windows 64 bits, en revanche, long ne fait que 4 octets. Cet écart de portabilité est exactement la raison pour laquelle vous ne devriez pas écrire de code qui suppose que long fait 64 bits.
Quand vous avez besoin d'une largeur exacte, tournez-vous vers les types entiers à largeur fixe de <cstdint> :
Utilisez int32_t/int64_t pour les formats de fichiers, les protocoles réseau ou tout ce qui doit se comporter de façon identique d'une machine à l'autre. Notez la conversion (int)a - envoyer un type 8 bits dans un flux l'affiche comme un caractère, pas comme un nombre, alors convertissez-le d'abord.
Signed et unsigned
Chaque type entier existe en deux variantes. Un type signed peut contenir des négatifs ; un type unsigned non, échangeant la plage négative contre un maximum positif plus élevé. Le int simple est signed par défaut.
Soustraire à un 0 non signé revient à un nombre positif énorme au lieu de devenir négatif. Cela piège les gens en permanence - surtout avec size_t (un type unsigned) renvoyé par .size() :
vector<int> v = {1, 2, 3};
// DANGER : v.size() est unsigned. Si v est vide, v.size() - 1 boucle
// vers un nombre gigantesque et la boucle tourne presque indéfiniment.
for (size_t i = 0; i <= v.size() - 1; i++) { /* ... */ }
Préférez i < v.size() (jamais <= size() - 1), ou évitez tout le problème avec une boucle for basée sur une plage.
Le dépassement d'entier est un comportement indéfini
Contrairement au bouclage des unsigned (qui est bien défini), le dépassement d'entier signé est un comportement indéfini en C++. Le compilateur est autorisé à faire n'importe quoi - renvoyer des données aberrantes, supprimer la vérification lors de l'optimisation ou planter :
La solution est la même que le piège du dépassement dans n'importe quel langage : faites l'arithmétique dans un type plus large. Convertissez un opérande en long long avant le +, pour que l'addition se fasse en 64 bits. Convertir le résultat après coup est trop tard - le dépassement a déjà eu lieu.
Choisir le bon type
Pour la plupart du code, les valeurs par défaut conviennent : int pour les entiers, double pour les décimaux. Tournez-vous vers autre chose seulement quand vous avez une raison.
| Type | Taille typique | À utiliser quand |
|---|---|---|
int | 32 bits | La valeur par défaut pour les entiers |
long long | 64 bits | Valeurs au-delà d'~2 milliards : horodatages, gros compteurs |
double | 64 bits | La valeur par défaut pour les décimaux - bonne précision |
float | 32 bits | Tableaux à mémoire limitée où la précision peut souffrir |
bool | 1 octet | Un drapeau true/false |
int32_t / int64_t | exact | Formats multiplateformes, protocoles, manipulation de bits |
Quelques pièges à garder en tête. float n'a qu'environ 7 chiffres décimaux significatifs, donc 0.1f + 0.2f n'est pas exactement 0.3 - préférez double sauf si vous devez vraiment économiser de la mémoire. Et char peut être signed ou unsigned selon la plateforme, donc si vous faites de l'arithmétique sur des octets bruts, précisez signed char ou unsigned char.
Ensuite : Le mot-clé auto
Écrire le type à chaque fois devient fastidieux, et parfois le type est long ou difficile à nommer. Le C++ laisse le compilateur le déduire pour vous avec le mot-clé auto - auto x = 42; fait de x un int, et auto it = v.begin(); vous évite de taper un type d'itérateur verbeux. La page suivante explique quand auto rend le code plus clair et quand il en cache trop.
Questions fréquentes
Quels sont les types de données de base en C++ ?
Les types fondamentaux sont les entiers (short, int, long, long long), les flottants (float, double, long double), le type caractère char et le type booléen bool. Chaque type entier peut aussi être signed ou unsigned. Tout le reste - std::string, les tableaux, vos propres classes - se construit par-dessus.
Quelle est la différence entre int et long en C++ ?
Les deux stockent des nombres entiers, mais long est garanti d'être au moins aussi large qu'int (souvent 64 bits sur les plateformes 64 bits, mais seulement 32 bits sous Windows). La norme ne fixe que les tailles minimales, alors pour une largeur garantie utilisez les types à largeur fixe de <cstdint> comme int32_t et int64_t.
Quelle est la taille d'un int en C++ ?
La norme garantit seulement qu'int fait au moins 16 bits, mais sur pratiquement tous les ordinateurs de bureau et serveurs modernes il fait 32 bits. Comme les tailles dépendent de la plateforme, ne présumez jamais - affichez sizeof(int) pour vérifier, ou utilisez des types de <cstdint> comme int32_t quand vous avez besoin d'une largeur exacte.