Types simples

De Appmethod Topics
Aller à : navigation, rechercher

Les types simples, qui comportent les types ordinaux et les types réels, définissent des ensembles ordonnés de valeurs.

Types ordinaux

Les types ordinaux incluent les types entier, caractère, booléen, énuméré et intervalle. Un type ordinal définit un ensemble ordonné de valeurs dans lequel chaque valeur, sauf la première, a un prédécesseur unique et chaque valeur, sauf la dernière, a un successeur unique. En outre, chaque valeur a un rang qui détermine l'ordre du type. Dans la plupart des cas, si une valeur a le rang n, son prédécesseur a le rang n-1 et son successeur a le rang n+1.

Pour les types entiers, le rang d'une valeur est la valeur elle-même. Les types sous-intervalle conservent les rangs de leurs types de base. Pour les autres types ordinaux, la première valeur a par défaut le rang 0, la suivante a le rang 1, et ainsi de suite. La déclaration d'un type énuméré peut explicitement redéfinir cette valeur par défaut.

Plusieurs fonctions prédéfinies opèrent sur les valeurs ordinales et les identificateurs de type. Les plus importantes sont résumées ci-dessous.

Fonction Paramètre Valeur de retour Remarques
Ord Expression ordinale Rang de la valeur de l'expression Ne prend pas d'arguments Int64.
Pred Expression ordinale Prédécesseur de la valeur de l'expression
Succ Expression ordinale Successeur de la valeur de l'expression
High Identificateur de type ordinal ou variable de type ordinal Plus grande valeur du type Opère aussi sur les types chaîne courte et les tableaux.
Low Identificateur de type ordinal ou variable de type ordinal Plus petite valeur du type Opère aussi sur les types chaîne courte et les tableaux.


Par exemple, High(Byte) renvoie 255 car la plus grande valeur du type Byte est 255, et Succ(2) renvoie 3 car 3 est le successeur de 2.

Les procédures standard Inc et Dec incrémentent et décrémentent la valeur d'une variable ordinale. Par exemple, Inc(I) est équivalent à I := Succ(I) et, si I est une variable entière, également équivalent à I := I + 1.

Types entiers

Un type entier représente un sous-ensemble des nombres intégraux.

Les types entiers peuvent être dépendants de la plate-forme et indépendants de la plate-forme.

Types entiers dépendants de la plate-forme

Les types entiers dépendants de la plate-forme sont transformés pour s'ajuster à la taille en bits de la plate-forme du compilateur en cours. Les types entiers dépendants de la plate-forme sont NativeInt et NativeUInt. L'utilisation de ces types dans la mesure du possible est souhaitable, puisqu'ils donnent de meilleures performances pour le système d'exploitation et la CPU sous-jacente. Le tableau suivant illustre leurs intervalles et les formats de stockage pour le compilateur Object Pascal.

Types entiers dépendants de la plate-forme

Type Intervalle Format Alias

NativeInt

-2147483648..2147483647
-2^63..2^63-1

Signé sur 32 bits sur les plates-formes 32 bits ou
Signé sur 64 bits sur les plates-formes 64 bits

Integer
Int64

NativeUInt

0..4294967295
0..2^64-1

Non signé sur 32 bits sur les plates-formes 32 bits ou
Non signé sur 64 bits sur les plates-formes 64 bits

Cardinal
UInt64


Types entiers indépendants de la plate-forme

Les types entiers indépendants de la plate-forme ont toujours la même taille, quelle que soit la plate-forme utilisée. Les types entiers indépendants de la plate-forme incluent ShortInt, SmallInt, LongInt, Integer, Int64, Byte, Word, LongWord, Cardinal et UInt64.

Types entiers indépendants de la plate-forme

Type Intervalle Format Alias

ShortInt

-128..127

Signé sur 8 bits

Int8

SmallInt

-32768..32767 Signé sur 16 bits

Int16

LongInt

-2147483648..2147483647 Signé sur 32 bits

Int32

Integer

-2147483648..2147483647 Signé sur 32 bits

Int32

Int64

-2^63..2^63-1 Signé sur 64 bits

Byte

0..255 Non signé sur 8 bits

UInt8

Word

0..65535 Non signé sur 16 bits

UInt16

LongWord

0..4294967295 Non signé sur 32 bits

UInt32

Cardinal

0..4294967295 Non signé sur 32 bits

UInt32

UInt64

0..2^64-1 Non signé sur 64 bits


En général, les opérations arithmétiques sur les entiers renvoient une valeur de type Integer, équivalente au LongInt sur 32 bits. Les opérations renvoient une valeur de type Int64 seulement quand elles portent sur un ou plusieurs opérandes Int64. Par conséquent, le code suivant produit des résultats incorrects :

var
I: Integer;
J: Int64;
...
I := High(Integer);
J := I + 1;

Pour obtenir une valeur de retour Int64 dans cette situation, transtypez I en Int64 :

...
J := Int64(I) + 1;

Pour plus d'informations, voir Opérateurs arithmétiques.

Remarque : Certaines routines standard qui prennent des arguments entiers tronquent les valeurs Int64 à 32 bits. Néanmoins, les routines High, Low, Succ, Pred, Inc, Dec, IntToStr et IntToHex supportent entièrement les arguments Int64. De même, les fonctions Round, Trunc, StrToInt64 et StrToInt64Def renvoient des valeurs Int64. Quelques routines ne peuvent pas prendre du tout des valeurs Int64.

Quand vous incrémentez la dernière valeur ou décrémentez la première valeur d'un type entier, le résultat boucle sur le début ou la fin de l'intervalle. Par exemple, le type ShortInt a l'intervalle -128..127 ; donc après l'exécution du code suivant :

var
I: Shortint;
...
I := High(Shortint);
I := I + 1;

la valeur de I est -128. Si la vérification des limites de compilation est activée, ce code génère néanmoins une erreur d'exécution.

Types caractères

Les types caractères sont Char, AnsiChar, WideChar, UCS2Char et UCS4Char:

  • Char dans l'implémentation en cours est équivalent à WideChar, puisque le type de chaîne par défaut est maintenant UnicodeString. Puisque l'implémentation de Char est susceptible de changer dans les releases futures, il est judicieux d'utiliser la fonction standard SizeOf plutôt qu'une constante codée en dur dans les programmes qui doivent gérer des caractères de taille différente.
  • Les valeurs AnsiChar sont des caractères sur un octet (8 bits) ordonnés selon le jeu de caractères local, probablement multi-octet.
  • Les caractères WideChar utilisent plus d'un octet pour représenter chaque caractère. Dans les implémentations en cours, les valeurs WideChar sont des caractères sur un mot (16 bits) ordonnés selon le jeu de caractères Unicode (éventuellement plus longs dans des implémentations futures). Les 256 premiers caractères Unicode correspondent aux caractères ANSI.
  • UCS2Char est un alias pour WideChar.
  • UCS4Char est utilisé pour fonctionner avec des caractères Unicode sur 4 octets.

Une constante chaîne de longueur 1, comme 'A', peut désigner une valeur caractère. La fonction prédéfinie Chr renvoie la valeur caractère pour tout entier de l'intervalle de WideChar ; par exemple Chr(65) renvoie la lettre A.

Les valeurs AnsiChar et WideChar, comme les entiers, bouclent quand elles sont décrémentées ou incrémentées au-delà du début ou de la fin de leur intervalle (à moins que la vérification de l'intervalle ne soit activée). Ainsi, une fois le code suivant exécuté :

var Letter: AnsiChar;
I: Integer;
begin
  Letter := High(Letter);
  for I := 1 to 66 do Inc(Letter);
end;

Letter a la valeur A (ASCII 65).

Remarque : Les types AnsiChar et WideChar ne sont pas supportés par les compilateurs mobiles Object Pascal, mais sont utilisés par les compilateurs de bureau Object Pascal. Pour plus d'informations, voir Migration du code Object Pascal en mobile depuis le bureau.

Types booléens

Les 4 types booléens prédéfinis sont Boolean, ByteBool, WordBool et LongBool. Boolean est le type préféré. Les autres types existent pour fournir la compatibilité avec les bibliothèques des autres langages et systèmes d'exploitation.

Une variable Boolean occupe un octet de mémoire, une variable ByteBool occupe aussi un octet, une variable WordBool occupe 2 octets (1 mot), et une variable LongBool occupe 4 octets (2 mots).

Les valeurs booléennes sont désignées par les constantes prédéfinies True et False. Les relations suivantes s'appliquent :

Booléen ByteBool, WordBool, LongBool

False < True

False <> True

Ord(False) = 0

Ord(False) = 0

Ord(True) = 1

Ord(True) <> 0

Succ(False) = True

Succ(False) = True

Pred(True) = False

Pred(False) = True


Une valeur de type ByteBool, LongBool ou WordBool est considérée comme True quand son rang est différent de zéro. Si une telle valeur apparaît dans un contexte où un Boolean est attendu, le compilateur convertit automatiquement toute valeur de rang non nul en True.

Les remarques précédentes portent sur le rang des valeurs booléennes, non pas sur les valeurs mêmes. Dans Object Pascal, les expressions booléennes ne peuvent être comparées avec des entiers ou des réels. Par exemple, si X est une variable entière, l'instruction :

if X then ...;

génère une erreur de compilation. Le transtypage de la variable en un type booléen n'est pas fiable, mais les deux alternatives suivantes fonctionnent.

 if X <> 0 then ...;    { use an expression that returns a Boolean value }
  ...
 var OK: Boolean;       { use a Boolean variable }
   ...
 if X <> 0 then
   OK := True;
 if OK then ...;

Types énumérés

Un type énuméré définit un ensemble ordonné de valeurs en énumérant simplement les identificateurs désignant ces valeurs. Les valeurs n'ont pas de signification propre. Pour déclarer un type énuméré, utilisez la syntaxe suivante :

 type typeName = (val1, ...,valn)

typeName et chaque val sont des identificateurs valides. Par exemple, la déclaration :

type Suit = (Club, Diamond, Heart, Spade);

définit un type énuméré appelé Suit dont les valeurs possibles sont Club, Diamond, Heart et Spade, où Ord(Club) renvoie 0, Ord(Diamond) renvoie 1, et ainsi de suite.

Quand vous déclarez un type énuméré, vous déclarez chaque val comme une constante de type typeName. Si les identificateurs val sont utilisés dans un autre but au sein de la même portée, il y a un conflit de nom. Si, par exemple vous déclarez le type :

type TSound = (Click, Clack, Clock)

Malheureusement, Click est aussi le nom d'une méthode définie pour TControl et tous les objets de la VCL qui en descendent. Donc, si vous écrivez une application et créez un gestionnaire d'événement tel que :

 procedure TForm1.DBGridEnter(Sender: TObject);
  var
     Thing: TSound;
     begin
       ...
       Thing := Click;
     end;

vous obtiendrez une erreur de compilation ; le compilateur interprète Click dans la portée de la procédure comme une référence à la méthode Click de TForm. Vous pouvez contourner ce problème en qualifiant l'identificateur ; si TSound est déclarée dans MyUnit, vous devez utiliser :

Thing := MyUnit.Click;

Une meilleure solution consiste néanmoins à choisir des noms de constantes qui ne rentrent pas en conflit avec d'autres identificateurs. Exemples :

type
  TSound = (tsClick, tsClack, tsClock);
  TMyColor = (mcRed, mcBlue, mcGreen, mcYellow, mcOrange);
  Answer = (ansYes, ansNo, ansMaybe)

Vous pouvez utiliser directement la construction (val1, ..., valn) dans les déclarations des variables, comme nom de type :

var MyCard: (Club, Diamond, Heart, Spade);

Mais si vous déclarez MyCard de cette manière, vous ne pouvez pas déclarer une autre variable dans la même portée en utilisant ces identificateurs de constantes. Ainsi :

 var Card1: (Club, Diamond, Heart, Spade);
 var Card2: (Club, Diamond, Heart, Spade);

génère une erreur de compilation. Mais :

var Card1, Card2: (Club, Diamond, Heart, Spade);

se compile sans problème, tout comme :

type
  Suit = (Club, Diamond, Heart, Spade);
  var
    Card1: Suit;
    Card2: Suit;

Types énumérés avec les rangs explicitement assignés

Par défaut, le rang des valeurs énumérées commence à 0 et suit la séquence dans laquelle leurs identificateurs sont listés dans la déclaration de type. Vous pouvez redéfinir cela en assignant explicitement les rangs de certaines ou de toutes les valeurs de la déclaration. Pour assigner un rang à une valeur, faites suivre son identificateur de = constantExpression, où constantExpression est une expression constante dont le résultat est un entier. Par exemple :

type Size = (Small = 5, Medium = 10, Large = Small + Medium);

définit un type appelé Size dont les valeurs possibles sont Small, Medium et Large, où Ord(Small) renvoie 5, Ord(Medium) renvoie 10 et Ord(Large) renvoie 15.

Un type énuméré est en effet un intervalle dont les valeurs inférieure et supérieure correspondent aux rangs inférieur et supérieur des constantes de la déclaration. Dans l'exemple précédent, le type Size a 11 valeurs possibles dont le rang va de 5 à 15. Ici, le type array[Size] of Char représente un tableau de 11 caractères. Seules trois de ces valeurs ont un nom, mais les autres sont accessibles par le biais de transtypages et à travers les routines comme Pred, Succ, Inc et Dec. Dans l'exemple suivant, des valeurs "anonymes" dans l'intervalle de Size sont assignées à la variable X.

var
  X: Size;
  X := Small;   // Ord(X) = 5
  X := Size(6); // Ord(X) = 6
  Inc(X);       // Ord(X) = 7

Toute valeur à laquelle un rang n'a pas été explicitement assigné a pour rang un de plus que celui de la valeur précédente de la liste. Si aucun rang n'est assigné à la première valeur, son rang est 0. Ici, étant donné la déclaration :

type SomeEnum = (e1, e2, e3 = 1);

SomeEnum a seulement deux valeurs possibles : Ord(e1) renvoie 0, Ord(e2) renvoie 1, et Ord(e3) renvoie aussi 1 ; puisque e2 et e3 ont le même rang, ils représentent la même valeur.

Les constantes énumérées sans valeur spécifique ont des informations RTTI :

type SomeEnum = (e1, e2, e3);

alors que les constantes énumérées avec une valeur spécifique (comme dans l'exemple ci-dessous) n'en ont pas :

type SomeEnum = (e1 = 1, e2 = 2, e3 = 3);

Enumérations de portée

Vous pouvez utiliser les énumérations de portée dans le code Object Pascal si vous activez la directive {$SCOPEDENUMS ON} du compilateur.

La directive {$SCOPEDENUMS ON ou OFF} active ou désactive l'utilisation des énumérations de portée dans le code Object Pascal. {$SCOPEDENUMS ON} définit que les énumérations sont des énumérations de portée. {$SCOPEDENUMS ON} affecte des déclarations de types énumération jusqu'à la directive {$SCOPEDENUMS OFF} la plus proche. Les identificateurs d'énumérations introduits dans les types énumération déclarés après la directive {$SCOPEDENUMS ON} ne sont pas ajoutés à la portée globale. Pour utiliser un identificateur d'énumération de portée, vous devez le qualifier avec le nom du type énumération introduisant cet identificateur.

Par exemple, définissons l'unité suivante dans le fichier Unit1.pas

 unit Unit1;
 interface
 // {$SCOPEDENUMS ON} // clear comment from this directive
  type
    TMyEnum = (First, Second, Third);
 implementation
  
 end.

et le programme suivant en utilisant cette unité

 program Project1;
 {$APPTYPE CONSOLE}
 
 uses
   SysUtils, Unit1 in 'Unit1.pas';
  
 var
   // First: Integer;  // clear comment from this variable
   Value: TMyEnum;
 begin
   try
     Value := First;
 //  Value := TMyEnum.First;
 //  Value := unit1.First;
   except
     on E:Exception do
       Writeln(E.Classname, ': ', E.Message);
   end;
 end.

Nous pouvons maintenant étudier les effets de la directive {$SCOPEDENUMS} du compilateur sur les portées dans lesquelles les identificateurs First, Second et Third, définis dans l'énumération TMyEnum, sont visibles.

Commencez par exécuter (F9) ce code. Le code s'exécute correctement. Cela signifie que l'identificateur First, utilisé dans la variable

Value := First;

, est l'identificateur de porté globale introduit dans le type énumération

TMyEnum = (First, Second, Third);

.

A présent, effacez le commentaire de la directive

{$SCOPEDENUMS ON}

du compilateur dans l'unité unit1. Cette directive applique l'énumération TMyEnum à générer. Lancez la commande Exécuter. L'erreur E2003 Identificateur non déclaré 'First' est générée sur la ligne

Value := First;

. Elle vous informe que la directive {$SCOPEDENUMS ON} du compilateur empêche l'ajout de l'identificateur First, introduit dans l'énumération TMyEnum de portée, à la portée globale.

Pour utiliser les identificateurs introduits dans les énumérations de portée, préfixez une référence à un élément d'énumération avec son nom de type. Par exemple, effacez le commentaire dans la seconde version

Value := TMyEnum.First;

de la variable Value (et commentez la première version de Value). Lancez la commande Exécuter. Le programme s'exécute correctement. Cela signifie que l'identificateur First est connu dans la portée TMyEnum.

Maintenant, commentez la directive

// {$SCOPEDENUMS ON}

du compilateur dans unit1. Puis effacez le commentaire de la déclaration de la variable First

First: Integer;

et utilisez à nouveau la variable

Value := First;

. A présent, le code dans program Project1 ressemble à ce qui suit :

 var
   First: Integer;
   Value: TMyEnum;
 begin
   try
     Value := First;

Lancez la commande Exécuter. La ligne

 First: Integer;

provoque l'erreur E2010 Types incompatibles - 'TMyEnum' et 'Integer'. Cela signifie que le conflit de nommage se produit entre l'identificateur First de portée globale introduit dans l'énumération TMyEnum et la variable First. Vous pouvez contourner ce conflit en qualifiant l'identificateur First avec l'unité unit1 dans laquelle il est défini. Pour cela, commentez à nouveau la première version de la variable Value et effacez le commentaire de la troisième :

Value := unit1.First;

Lancez la commande Exécuter. Le programme s'exécute correctement. Cela signifie qu'à présent l'identificateur First peut être qualifié avec la portée unit1 de l'unité. Voyons ce qui se passe si nous activons à nouveau la directive

{$SCOPEDENUMS ON}

du compilateur dans unit1. Le compilateur génère l'erreur E2003 Identificateur non déclaré 'First' sur la ligne

Value := unit1.First;

. Cela signifie que {$SCOPEDENUMS ON} empêche l'ajout de l'identificateur d'énumération First dans la portée unit1. A présent l'identificateur First est ajouté seulement dans la portée TMyEnum de l'énumération. Pour vérifier cela, utilisons à nouveau la version

Value := TMyEnum.First;

de la variable Value. Lancez la commande Exécuter. Le code s'exécute correctement.

Types sous-intervalle

Un type sous-intervalle représente un sous-ensemble de valeurs d'un autre type ordinal (appelé le type de base). Toute construction de la forme Bas..Haut, où Bas et Haut sont des expressions constantes du même type ordinal, Bas étant inférieur à Haut, identifie un type sous-intervalle qui inclut toutes les valeurs comprises entre Bas et Haut. Si par exemple, vous déclarez le type énuméré :

type
  TColors = (Red, Blue, Green, Yellow, Orange, Purple, White, Black);

vous pouvez définir le type sous-intervalle suivant :

type
  TMyColors = Green..White;

Ici, TMyColors inclut les valeurs Green, Yellow, Orange, Purple et White.

Vous pouvez utiliser des constantes numériques ou des caractères (constantes chaîne de longueur 1) pour définir des types sous-intervalle :

type
 SomeNumbers = -128..127;
 Caps = 'A'..'Z';

Quand vous utilisez des constantes numérique ou caractère pour définir un sous-intervalle, le type de base est le plus petit type entier ou caractère contenant l'intervalle spécifié.

La construction LowerBound..UpperBound fonctionne directement comme nom de type, vous pouvez donc l'utiliser directement dans des déclarations de variables. Par exemple :

var SomeNum: 1..500;

déclare une variable entière dont la valeur est dans l'intervalle allant de 1 à 500.

Le rang de chaque valeur d'un sous-intervalle est préservé depuis le type de base. Dans le premier exemple, si Color est une variable contenant la valeur Green, Ord(Color) renvoie 2, que Color soit de type TColors ou TMyColors. Les valeurs ne bouclent pas au début ou à la fin du sous-intervalle même si le type de base est de type entier ou caractère ; l'incrémentation ou la décrémention au-delà des limites d'un sous-intervalle convertit simplement la valeur dans le type de base. Ainsi :

 type Percentile = 0..99;
 var I: Percentile;
   ...
   I := 100;

produit une erreur, le code suivant :

 ...
 I := 99;
 Inc(I);

assigne la valeur 100 à I (à moins que la vérification des limites de compilation ne soit activée).

L'utilisation d'expressions constantes dans les définitions des sous-intervalles introduit une difficulté syntaxique. Dans toute déclaration de type, quand le premier caractère significatif après = est une parenthèse gauche, le compilateur suppose qu'un type énuméré est défini. Ainsi, le code :

 const X = 50; Y = 10;
 type Scale = (X - Y) * 2..(X + Y) * 2;

produit une erreur. Pour contourner ce problème, il faut réécrire la déclaration de type afin d'éviter la parenthèse de début :

 type Scale = 2 * (X - Y)..(X + Y) * 2;

Types réels

Un type réel définit un ensemble de nombres pouvant être représentés avec la notation à virgule flottante. Le tableau ci-dessous donne les intervalles et les formats de stockage des types réels sur les plates-formes 32 bits et 64 bits.

Types réels

Type Intervalle positif approximatif Chiffres significatifs Taille en octets
Real48 2.9e-39 .. 1.7e+38 11-12 6
Single 1.5e-45 .. 3.4e+38 7-8 4
Double 5.0e-324 .. 1.7e+308 15-16 8
Real 5.0e-324 .. 1.7e+308 15-16 8
Extended
  • Plates-formes 32 bits : 3.4e-4932 .. 1.1e+4932
  • Plates-formes 64 bits : 5.0e-324 .. 1.7e+308
10-20

15-16

10

8

Comp -263+1 .. 263-1 10-20 8
Currency -922337203685477.5808.. 922337203685477.5807 10-20 8

Les remarques suivantes s'appliquent aux types réels :

  • Real est équivalent à Double, dans l'implémentation en cours.
  • Real48 est maintenu pour la compatibilité descendante. Comme son format de stockage n'est pas natif à l'architecture des processeurs Intel, ce type produit des performances plus mauvaises que les autres types à virgule flottante.
Le type Real48 sur 6 octets s'appelait Real dans les versions précédentes du Pascal Objet. Si vous recompilez du code utilisant l'ancien type Real sur 6 octets dans Object Pascal, vous pouvez le changer en Real48. Vous pouvez aussi utiliser la directive {$REALCOMPATIBILITY ON} du compilateur pour transformer Real en type sur 6 octets.
  • Extended offre une plus grande précision sur les plates-formes 32 bits que les autres types réels.
Sur les plates-formes 64 bits, Extended est un alias pour un Double ; c'est-à-dire que la taille du type de données Extended est de 8 octets. Vous avez ainsi moins de précision en utilisant un type Extended sur les plates-formes 64 bits que sur les plates-formes 32 bits, où la taille du type Extended est de 10 octets. Par conséquent, si vos applications utilisent le type de données Extended et que vous comptez sur la précision pour les opérations à virgule flottante, cette différence de taille peut affecter vos données. Soyez prudent en utilisant Extended si vous créez des fichiers de données à partager sur plusieurs plates-formes. Pour plus d'informations, voir Le type de données Extended est de 2 octets plus petit sur les systèmes Windows 64 bits.
  • Le type Comp est un type natif de l'architecture des processeurs Intel et représente un entier sur 64 bits. Il est néanmoins classé parmi les réels car il ne se comporte pas comme un type ordinal. Par exemple, vous ne pouvez pas incrémenter ou décrémenter une valeur Comp. Comp est maintenu seulement à des fins de compatibilité descendante. Utilisez le type Int64 pour de meilleures performances.
  • Currency est un type de données à virgule fixe qui minimise les erreurs d'arrondi dans les calculs monétaires. Il est stocké dans un entier sur 64 bits, les 4 chiffres les moins significatifs représentant implicitement les positions décimales. Combinées à d'autres types réels dans des affectations et des expressions, les valeurs Currency sont automatiquement divisées ou multipliées par 10000.

Voir aussi