• Aucun résultat trouvé

2.4 Compilation de fichiers

3.1.2 Les types simples

Tout d’abord, une première remarque s’impose : il n’existe pas de type qui puisse gérer tous les entiers de N ou Z car la mémoire de l’ordinateur est finie et ne peut donc pas

3.1. LES TYPES 25

stocker des nombres entiers arbitrairement grands. Il existe différents types d’entiers selon la taille maximale que l’on souhaite pouvoir atteindre :

short int : codé sur 2 octets (16 bits, un pour le signe et ensuite 15 bits pour le codage du nombre en base deux : on peut donc atteindre 215= 32768, c’est peu), — int : codé sur 4 octets (32 bits, un pour le signe et ensuite 31 bits pour le codage

du nombre en base deux, valeur maximale 2147483648) — long int : codé sur 4 ou 8 octets

long long int : codé sur 8 octets depuis la norme de 1998.

Plus le stockage est gros, plus les opérations seront lentes : c’est le prix à payer pour atteindre de grandes tailles.

Pour affecter des valeurs à ces variables, on peut les entrer directement : — soit en base 10 :

int n=534;

— soit en base 8 en faisant précéder les chiffres par un zéro 0 muet :

int n=01026;

— soit en base 16 en faisant précéder les chiffres par un 0x muet :

int n=0x216;

Si l’on souhaite n’utiliser que des entiers positifs, il est possible de récupérer le bit de signe pour multiplier par deux les valeurs maximales en déclarant les entiers par le type

unsigned :

unsigned int x=34; 2 unsigned long int x=67;

Enfin, il existe une dernière manière de préciser la longueur du codage en utilisant int_Nt (ou uint_Nt pour la version sans signe) avec le symbole N remplacé par 8, 16, 32 ou 64 selon le nombre de bits utilisés.

Les opérations arithmétiques élémentaires disponibles sur les entiers sont les suivantes : — l’addition et la soustraction par les symboles + et -

int x=4, y=7, z, w;

2 z= x+y; // z vaut alors 11

w= x-y; // w vaut alors -3

— le passage à l’opposé par le signe - :

int x=4,z;

— la multiplication par le symbole * (qui doit impérativement être écrit)

int x=4, y=7, z;

2 z= x*y; //z vaut alors 28

— le reste modulo 𝑚 par le symbole % et le quotient euclidien par 𝑚 par le symbole /

int x=37, m=5, z, w;

2 z= x % m; //z vaut alors 2 car 32=5*7+2; w= x / m; //w vaut alors 7 car 32=5*7+2;

Dans les exemples précédents, nous avons rempli la valeur d’une troisième variable par opération sur deux autres variables. Très souvent, on souhaite seulement actualiser la valeur d’une variable en la combinant avec une autre. Il y a alors deux solutions et les deux codes suivants sont équivalents :

int x=4, y=7, z=5; 2 x = x+y; z = z*y; int x=4, y=7, z=5; 2 x += y; z *= y;

Il n’y a (presque) aucune différence d’efficacité entre ces deux syntaxes et l’on peut choisir ce que l’on préfère. Il faut néanmoins être capable de comprendre les deux pour les circonstances où l’on doit lire le code d’autrui. Ces opérateurs existent bien évidemment également pour la soustraction, le reste euclidien et le quotient euclidien avec -=, /= et

%=.

Enfin, lors du parcours d’une boucle, il est très fréquent de devoir incrémenter un compteur de 1 ; il existe quatre manières équivalentes de le faire :

n = n+1; 2 n += 1;

n++; 4 ++n;

et, bien évidemment, la même syntaxe marche avec le signe - en lieu et place du signe +. Il existe une subtilité entre les deux derniers opérateurs et, pour éviter les pièges, nous déconseillons leur utilisation autrement qu’isolément.

3.1. LES TYPES 27

Les « flottants »

Les problèmes de taille de stockage rencontrés pour les entiers se révèlent encore plus problématiques pour les nombres réels. De manière générale, il est raisonnable de considérer — du point de vue du programmeur — qu’il n’existe pas de nombres réels en informatique.

La notion la plus proche est celle de « flottants » qui correspond à l’écriture d’un nombre réel sous la forme

254, 36 = 2, 5436 × 102= 11111110, 0101112

= 1.111111100101112× 27= 1.11111110010111

2× 21112

où ∙2 désigne l’écriture en base 2. Dans la mémoire, un premier bit est réservé pour le signe, plusieurs octets sont utilisés pour représenter le nombre entier présent dans la puissance de 10 et enfin les bits restants pour la mantisse, i.e. le nombre sans virgule figurant devant la puissance de 10. Le nom de « flottant » provient du fait que l’écriture précédente s’appelle écriture en virgule flottante.

Les différents types de « flottants » sont ainsi caractérisés par le nombre de bits alloués à l’exposant et à la mantisse (tous deux écrits en base 2) :

float : codé sur 4 octets, i.e. 32 bits (1 bit de signe, 8 bits pour l’exposant, 23 bits pour la mantisse), cela permet de couvrir des réels tronqués au sixième chiffre après la virgule et des exposants variant de −38 à 38.

double : codé sur 8 octets, i.e. 64 bits (1 bit de signe, 11 bits d’exposants et 52 bits pour la mantisse), cela permet de couvrir des réels tronqués au quinzième chiffre après la virgule et des exposants variant de −308 à 308.

Il existe également un type long double mais nous ne l’utiliserons pas car les erreurs d’arrondi sont le plus souvent causées par des problèmes de calculs qui ne relèvent pas du choix des double. En général, nous choisirons de manière quasi-systématique les double. Les opérations arithmétiques usuelles sont disponibles avec la même syntaxe que pour les entiers. Il est important de remarquer que la division avec l’opérateur / réalise pour, les flottants, la division réelle (multiplication par l’inverse) et non la division euclidienne, celle du cas entier. Un piège fréquent consiste à utiliser l’une à la place de l’autre. Le résultat de 11/3 vaut 3 alors que 11/3.0 vaut 3, 66666 . . ..

Les booléens

Les tests logiques n’ont que deux valeurs possibles, comme en mathématique : vrai et faux. Sur ces valeurs, il existe des opérations logiques élémentaires comme « et », « ou », « non », etc. Tout cela est géré enC++ par le type bool.

Une variable de type bool ne prend que deux valeurs possibles, true (écrit aussi 1)

et false (écrit aussi 0). Le tableau suivant résume les opérations logiques élémentaires :

Opération logique Opérateur en C++ exemple

ET && a && b ;

OU || a || b ;

NON ! !a ;

Nous présentons à présent quelques opérateurs à valeurs dans les booléens. Un test logique est la vérification de la présence d’une relation entre deux objets. Cela ne modifie pas les objets soumis au test, ni n’alloue de mémoire. Le résultat d’un test logique est vrai ou faux et est donc un booléen.

Le test d’égalité entre deux variables de même type a et b s’écrit (a == b) . Il est important de ne pas le confondre avec le signe = qui efface l’ancienne valeur de a pour lui

substituer la valeur de b . Il y a également tous les opérateurs de comparaisons usuels != (différent de), <= (inférieur ou égal), >= (supérieur ou égal), < (strictement inférieur) et

> strictement supérieur.

Pour trois variables entières 𝑛, 𝑚 et 𝑝 de valeurs prescrites, on peut tester si 4 ̸= 𝑛 < 𝑚 ≤ 𝑝 avec la syntaxe

(4 != n) && (n<m) && (m<=p)

Les énumérations

Imaginons que nous souhaitions décrire les couleurs d’objets parmi une palette finie de couleur. Quel type de variable utilisé ? Un premier choix consiste à associer un entier ou une lettre à chaque couleur et à bien documenté ce codage pour l’utilisateur du type ; c’est long et en général cela génère de nombreuses erreurs d’étourderies. Une seconde option consiste à utiliser une chaîne de caractères avec le nom de la couleur : c’est mieux du point de vue des erreurs potentielles mais cela demande d’alourdir énormément le code avec la gestion des chaînes de caractères et rend certaines énumérations difficiles. La construction de types avec un nombre fini de valeurs auxquelles on associe un nom se fait par :

enum TYPE { valeur1, valeur2, ..., valeurN};

Par exemple, la description des mois de l’année ou des couleurs de l’arc-en-ciel se fera par :

enum Mois {janvier, fevrier, mars, avril, mai, juin,

2 juillet, aout, septembre, octobre, novembre, decembre}; 4 enum Arc_en_ciel {rouge,orange,jaune,vert,bleu,indigo,violet};

La déclaration d’une variable de ce type se fait alors simplement par : Mois rentree_scolaire=septembre;

2 Arc_en_ciel espoir=bleu;

Les énumération enum sont très souvent utilisées avec des switch (cf. ci-dessous) qui permettent de tester des nombres finis de valeurs.

En interne, le compilateur associe un entier à chaque valeur et remplace à chaque apparation d’une valeur l’entier correspondant. L’entier est par défaut calculé en assignant 0 à la première valeur de la liste puis 1 à la deuxième, etc. Cela permet en particulier une conversion automatique vers les entiers de ces variables :

std::cout << rentree_scolaire;//affichera 8

Dans le cas des mois ci-dessus, il n’est pas malin de conserver le codage d’origine. Pour cela il suffit d’attribuer une valeur entière lors de la définition de l’énumération par :

3.1. LES TYPES 29

enum Mois {janvier=1, fevrier, mars, avril, mai, juin,

2 juillet, aout, septembre, octobre, novembre, decembre}; 4 enum Test { cedric = 2, damien, raphael=7, nicolas, thibaut };

Dans le premier cas, la valeur 1 est associée à janvier puis on continue par incrémentation de 1 à chaque nouvelle valeur : on recouvre ainsi la numérotation naturelle des mois de l’année. Dans le deuxième cas, cedric est associé à 2, puis damien à 3, puis raphael à 7, nicolas à 8 et thibaut à 9.

Conversion implicite entre types

Mathématiquement, l’inclusion naturelle des nombres entiers dans les réels fait qu’on ne distingue pas l’entier 2 et le réel 2. En informatique, c’est différent puisque, comme nous l’avons vu, le codage en mémoire varie suivant la nature de l’objet considéré : il faut ainsi spécifier dans un programme les règles de conversion que l’on souhaite utiliser.

Heureusement, pour les types simples vus précédemment, il existe des conversions naturelles déjà programmées en C++ mais il faut se méfier car cela est source de nombreux pièges. La seule conversion à connaître est celle des entiers vers les réels.

Si x est une variable de type entier (int, unsigned, long int, etc.), alors la syntaxe double(x) ou (double)x permet d’utiliser x à un endroit où un double est attendu.

Une utilisation fréquente de cette syntaxe est le choix de la division comme en témoigne l’exemple suivant :

int x=5, y=2;

2 std::cout << x/y ; // affiche 2 (division euclidienne)

std::cout << x/double(y); // affiche 2.5 (division réelle)

4 std::cout << x/(double) y; // affiche 2.5 (division réelle)

Documents relatifs