Les types
Pour créer une variable, il faut lui associer un type. Comment distinguer le type du nom de sa variable ? Voici quelques repères :
Dans l’écriture int a; on ne va pas utiliser ou manipuler le type int mais la variable a ainsi créée.
Le type int est unique mais il permet la création d’une multitude de variables.
Dans votre environnement de développement, l’éditeur affiche int et a avec deux couleurs différentes !
CONSIGNE : Un type doit être vu comme une description.
En résumé, les variables sont des choses contenant de l’information. Les types sont des descriptions, ils indiquent :
La quantité de mémoire nécessaire pour stocker l’information.
La manière dont est encodée et organisée l’information en mémoire.
Les opérations possibles : on ne peut pas appliquer une multiplication entre deux chaînes de caractères.
Par exemple, le type double désigne un format de nombre à virgule flottante :
Il occupe 8 octets én mémoire.
La norme IEEE 754 spécifie le codage en mémoire des nombres en double précision.
Le langage C++ définit les opérations de base disponible sur les double : + - * / …
Note
Pour qu’un ordinateur puisse manipuler des nombres au format double, les processeurs intègrent directement cette représentation dans leurs circuits.
Les types du C++ se divisent en deux catégories :
Les types fondamentaux
Les types composés (compound type) définis à partir des type fondamentaux
Les types fondamentaux
Les types fondamentaux (fundamental type) sont spécifiés par la norme du langage C++ et sont intégrés au compilateur. Ils sont ainsi utilisables dans le code à tout moment sans avoir à inclure de fichier d’en-tête. Les types fondamentaux sont divisés en catégories :
Le type void : il sert par exemple à préciser qu’une fonction ne retourne pas de valeur.
Le type booléen : bool
Les types caractères : hors programme.
- Les types numériques :
Les types entiers : short, int, long…
Les types flottant : float, double…
Type booléen
Le type booléen est associé au mot clef bool. Une variable de type booléen ne peut prendre que deux valeurs : true ou false.
Note
Le résultat d’un test est de type booléen. Ainsi, on peut écrire bool b = a < 3; comme on écrit int a = 4 + 7;.
Note
La taille du type booléen est indéterminée. Le compilateur peut choisir librement de réserver 1 octet ou plusieurs pour stocker cette valeur.
Types numériques
Nous présentons dans le tableau ci-dessous les types que nous allons utiliser dans nos exemples :
Type de données
Genre
Taille (octets)
Plage de valeurs
Chiffres significatifs
int
nombre entier
4
-2 147 483 648 à 2 147 483 647
9
double
nombre réel
8
±1.7976931348623157×10^308
16
Note
Le type double peut représenter des valeurs spéciales comme : +INF (+∞), -INF (-∞), -0.0 et NaN (Not A Number). Par exemple, la valeur +INF peut être obtenue en divisant 8.0 par 0.0, on peut obtenir la valeur -0.0 en divisant -1 par +INF. Le résultat des opérations : ∞ – ∞, ∞/∞, 0·∞ ou 0/0 est NaN. Le résultat de toute opération impliquant un NaN correspond à un NaN et toute comparaison impliquant un NaN vaut false.
Avertissement
Le comportement d’un programme face à la division par zéro dépend du type de la variable. En effet, si vous utilisez des entiers et que le processeur calcule 8/0 alors se produit une erreur de division par zéro qui stoppe le programme. Si vous effectuez cette division avec des doubles, le résultat de l’opération sera NaN mais aucune erreur ne va venir interrompre le programme. La suite du calcul sera cependant en mauvaise posture.
Conversion des types numériques
Le transtypage ou la conversion ou le cast désigne l’action de transformer une valeur d’un type donné vers un autre type.
REGLE 1 : la conversion entre valeurs numériques est valide et implicite en C++.
Le caractère valide signifie qu’aucun message d’erreur et qu’aucun message d’alerte ne se produit. Le caractère implicite signifie que le compilateur applique automatiquement une conversion sans que vous ayez à écrire du code (documentation sur les conversions implicites).
int a = 3.5; // conversion implicite de double en int
cout << a; // ===> 3
double f = 3; // conversion implicite int vers double
cout << f; // ===> 3.0000
La conversion implicite semble être une facilité à première vue, mais c’est aussi une source de problèmes. Dans le code ci-dessous, nous vous montrons un exemple d’une conversion implicite qui ne tourne pas forcément à notre avantage. En effet, nous appelons une fonction Affichage() ayant un paramètre de type int. Hors, nous lui transmettons une valeur de type double. Ainsi, une conversion implicite est déclenchée par le compilateur et l’affichage final donne 3 au lieu de 3.5. Nous aurions préféré avoir un message d’alerte nous indiquant que nous n’avions pas de fonction adéquate pour traiter ce type de variable.
#include <iostream>
void Aff(int v) // ==> conversion implicite de l'argument 3.5f
{
std::cout << v; // ==> 3
}
int main()
{
double a = 3.5;
Aff(a);
return 0;
}
REGLE 2 : une opération arithmétique entre deux types numériques identiques produit un résultat du même type.
Avertissement
Cette règle semble de bon sens, mais elle est généralement contre-intuitive pour la division. En effet, le résultat de la division de 1 par 4 entre deux valeurs de type int sera de type int, ce qui veut dire que le résultat vaudra 0 et non pas 0.25 comme certains auraient pu l’espérer.
REGLE 3 : une opération arithmétique entre deux types numériques différents implique la conversion implicite du type le plus faible vers le type le plus fort.
Par exemple, le type double est considéré comme plus fort que le type entier. Ainsi, l’addition entre un entier et un double entraîne la conversion implicite de la valeur entière vers le type double. Voici un exemple :
#include <iostream>
int main()
{
int a = 4;
double b = 7.5;
// => a est d'abord converti en double
std::cout << a + b; // ==>> 11.5
return 0;
}
Note
Vous pourrez croiser dans la documentation du C++ le terme promotion qui représente un cas particulier des conversions numériques : ce terme désigne une conversion vers un type supérieur sans changer la valeur d’origine et sans perte de précision numérique (sans arrondi). Ainsi, la conversion d’un entier 32 bits vers un entiers 64 bits est une promotion. Ainsi, les promotions pour les nombres entiers se font dans ce sens 8 bits -> 16 bits -> 32 bits -> 64 bits -> 128 bits et pour les nombres flottants de 32 bits -> 64 bits -> 128 bits.
Pour chacune des initialisations suivantes, indiquez celles qui déclenchent une conversion implicite et la valeur finale :
|
|
|
|
|
|
|
|
|
Pour chacune des expressions, donnez son type et sa valeur :
Expression |
Type |
Valeur |
---|---|---|
|
||
|
||
|
||
|
||
|
||
|
||
|
Les types composés
Les types composés (compound type) sont définis à partir des type fondamentaux.
Le type fonction
Le type fonction est défini à partir du type de retour et du type de chacun des paramètres. Ainsi dans l’exemple suivant :
int Test(int a, int b) { ... }
La fonction Test() est de type : fonction prenant deux entiers et retournant un entier.
Le type référence
Une référence crée un alias vers une variable déjà existante, nous en reparlerons plus tard. Le type référence se crée en ajoutant un symbole & (et commercial) après un type. Ainsi :
Le type int & se lit : référence vers un int.
Le type double & se lit : référence vers un double.
Le type structure
Une structure permet de regrouper plusieurs paramètres dans une sorte de container. C’est l’exemple même du type composé à partir de plusieurs types de base. Prenons l’exemple dans un jeu de rôle, nous avons un magicien qui dispose de plusieurs caractéristiques : points de vie, points de magie, pièces d’or. L’approche facile consiste à créer une variable par information :
int PV;
int PMagie;
int PiecesOR;
Le problème dans cette approche est que ces trois variables existent sans lien particulier avec notre magicien. L’approche un peu moins maladroite va consister à préfixer chacun des noms de variables :
int MAGE_PV;
int MAGE_ PMagie;
int MAGE_PiecesOR;
On progresse. Mais, problème ! Dans votre jeu, il peut y avoir deux magiciens et le risque de rapidement effectuer un copier-coller est grand :
int MAGE_PV;
int MAGE_ PMagie;
int MAGE_PiecesOR;
int MAGE2_PV;
int MAGE2_ PMagie;
int MAGE2_PiecesOR;
Ce code peut fonctionner, mais coté structuration, on est au niveau zéro. Un mage a un certain nombre de caractéristiques, alors autant les regrouper dans une structure. Cela augmente la lisibilité et l’organisation du code :
struct magicien
{
int PV;
int PMagie;
int PiecesOR;
};
int main()
{
magicien Mago1;
magicien Mago2;
}
Avertissement
Dans le langage C, la syntaxe des struct est différente. La version que nous présentons ici est la version simplifiée propre au C++.
Le type enumeration
Une énumération répond à un besoin pratique. Prenons l’exemple d’un jeu où plusieurs écrans sont disponibles :
Ecran d’accueil
Partie en cours
Options de jeu
HighScores
Une variable (entière) permet d’indiquer l’écran actif. Par soucis de rapidité, certains vont choisir de donner la valeur 1 pour désigner l’écran d’accueil et ainsi de suite. Mais, ce choix n’est pas une bonne pratique de programmation car vont se trouver dans le code des constantes 1, 2, 8… dont on finit par oublier le sens. Une approche plus sérieuse existe en C++ avec les enums permettant de nommer chaque élément :
enum class Ecran { Accueil, Jeu, Options, HighScores };
int main()
{
Ecran EcranCourant = Ecran::Accueil;
}
Note
La présence du mot clef class n’a rien à voir avec la création d’une classe. Ecran correspond à un type enumeration.
Pour chacune des affirmations suivantes, indiquez si elles sont vraies ou fausses :
En C++, le type fonction est donné uniquement par les paramètres de la fonction.
« Fonction recevant un entier et retournant un entier » correspond à un type de fonction.
Le type référence se construit en utilisant un signe $.
En C++ il existe les types composés et les types fondamentaux.
Le type class est un type fondamental.
Une énumération permet d’éviter des constantes entières sans signification.
Une structure permet de regrouper plusieurs informations.
Nommer des types
Il est possible d’associer un nom à un type grâce au mot-clef using. Cette fonctionnalité permet de donner des noms simples à des types relativement longs ou complexes. Elle doit être utilisée avec parcimonie car elle peut être à l’origine de conflits de noms. Cette syntaxe renouvelle la syntaxe historique du C utilisant le mot clef typedef.
Pour indiquer le type à associer avec ce nom, il faut penser à une définition de variable de ce type puis l’écrire en retirant le nom de la variable. Ainsi pour le type tableau d’entiers, la définition serait int T[10] ce qui nous permet d’écrire le type associé int[10].
SYNTAXE - Associer un nom à un type
using NouveauNomDeType = Type;
Ce nouveau nom de type est ensuite utilisé comme habituellement. Voici un exemple :
using mydouble = double; // création d'un type nommé mydouble associé au type double
int main()
{
mydouble p = 3.14; // mydouble s'utilise comme tout autre type
}
Prenons l’exemple d’un tableau d’entiers de taille 5x5. Pour l’utiliser, il faut écrire sa description complète : int [5][5]. On peut maintenant nommer ce type rapidement comme dans l’exemple ci-dessous :
#include <iostream>
using T55 = int[5][5]; // T55 est un nouveau nom désignant le type tableau d'entiers de taille 5x5
int main()
{
int T1[5][5]; // création d'un tableau d'entiers de taille 5x5
T55 T2; // création d'un tableau d'entiers de taille 5x5
}
Pour chacune des affirmations suivantes, indiquez si elles sont vraies ou fausses :
Il est possible de nommer un type.
La syntaxe : using monint int; est correcte.