Partager via


Système de type C++

Le concept de type est important en C++. Chaque variable, argument de fonction et valeur de retour de fonction doit avoir un type pour être compilé. En outre, toutes les expressions (y compris les valeurs littérales) reçoivent implicitement un type par le compilateur avant d’être évaluées. Certains exemples de types incluent des types intégrés tels que int pour stocker des valeurs entières, double pour stocker des valeurs à virgule flottante ou des types bibliothèque standard tels que la classe std::basic_string pour stocker du texte. Vous pouvez créer votre propre type en définissant un class ou struct. Le type spécifie la quantité de mémoire allouée pour la variable (ou le résultat de l’expression). Le type spécifie également les types de valeurs qui peuvent être stockées, la façon dont le compilateur interprète les modèles de bits dans ces valeurs et les opérations que vous pouvez effectuer sur ces valeurs. Cet article contient une présentation informelle des principales fonctionnalités du système de type C++.

Terminologie

Type scalaire : type qui contient une valeur unique d’une plage définie. Les scalaires incluent des types arithmétiques (valeurs intégrales ou à virgule flottante), des membres de type d’énumération, des types de pointeur, des types pointeur à membre et std::nullptr_t. Les types fondamentaux sont généralement des types scalaires.

Type composé : type qui n’est pas un type scalaire. Les types composés incluent les types de tableau, les types de fonction, les types de classe (ou struct), les types d’union, les énumérations, les références et les pointeurs vers des membres de classe non statiques.

Variable : nom symbolique d’une quantité de données. Le nom peut être utilisé pour accéder aux données qu’il fait référence dans toute l’étendue du code où il est défini. En C++, la variable est souvent utilisée pour faire référence à des instances de types de données scalaires, tandis que les instances d’autres types sont généralement appelées objets.

Objet : par souci de simplicité et de cohérence, cet article utilise l’objet terme pour faire référence à n’importe quelle instance d’une classe ou d’une structure. Lorsqu’il est utilisé dans le sens général, il inclut tous les types, même les variables scalaires.

Type POD (données anciennes simples) : cette catégorie informelle de types de données en C++ fait référence aux types scalaires (voir la section Types fondamentaux) ou sont des classes POD. Une classe POD n’a pas de membres de données statiques qui ne sont pas également des POD et n’a pas de constructeurs définis par l’utilisateur, de destructeurs définis par l’utilisateur ou d’opérateurs d’affectation définis par l’utilisateur. En outre, une classe POD n'a aucune fonction virtuelle, aucune classe de base et aucune donnée membre non static privée ou protégée. Les types POD sont souvent utilisés pour l'échange de données externes, par exemple avec un module écrit en langage C (disposant de types POD uniquement).

Spécification des types de variable et de fonction

C++ est à la fois un langage fortement typé et un langage typé statiquement ; chaque objet a un type et ce type ne change jamais. Lorsque vous déclarez une variable dans votre code, vous devez spécifier son type explicitement ou utiliser l’mot clé auto pour indiquer au compilateur de déduire le type de l’initialiseur. Lorsque vous déclarez une fonction dans votre code, vous devez spécifier le type de sa valeur de retour et de chaque argument. Utilisez le type void de valeur de retour si aucune valeur n’est retournée par la fonction. L’exception est lorsque vous utilisez des modèles de fonction, ce qui permet d’utiliser des arguments de types arbitraires.

Une fois que vous avez déclaré une variable, vous ne pouvez pas modifier son type à un moment ultérieur. Toutefois, vous pouvez copier la valeur de la variable ou la valeur de retour d’une fonction dans une autre variable d’un autre type. Ces opérations sont appelées conversions de type, qui sont parfois nécessaires, mais sont également des sources potentielles de perte de données ou d’erreur.

Lorsque vous déclarez une variable de type POD, nous vous recommandons vivement de l’initialiser, ce qui signifie qu’elle donne une valeur initiale. Tant que vous n'avez pas initialisé une variable, elle a la valeur « garbage » qui se compose des bits, quels qu'ils soient, présents précédemment dans cet emplacement mémoire. C’est un aspect important de C++ à mémoriser, en particulier si vous venez d’une autre langue qui gère l’initialisation pour vous. Lorsque vous déclarez une variable de type de classe non POD, le constructeur gère l’initialisation.

L'exemple suivant présente des déclarations de variable simples avec quelques descriptions. L'exemple montre également comment le compilateur utilise les informations de type pour autoriser ou interdire certaines opérations ultérieures sur la variable.

int result = 0;              // Declare and initialize an integer.
double coefficient = 10.8;   // Declare and initialize a floating
                             // point value.
auto name = "Lady G.";       // Declare a variable and let compiler
                             // deduce the type.
auto address;                // error. Compiler cannot deduce a type
                             // without an intializing value.
age = 12;                    // error. Variable declaration must
                             // specify a type or use auto!
result = "Kenny G.";         // error. Can't assign text to an int.
string result = "zero";      // error. Can't redefine a variable with
                             // new type.
int maxValue;                // Not recommended! maxValue contains
                             // garbage bits until it is initialized.

Types fondamentaux (intégrés)

Contrairement à certains langages, C++ n'a aucun type de base universel dont tous les autres types sont dérivés. Le langage comprend de nombreux types fondamentaux, également appelés types intégrés. Ces types incluent des types numériques tels que int, , longdouble, bool, et les types wchar_t pour les char caractères ASCII et UNICODE, respectivement. La plupart des types fondamentaux intégraux (saufbool, , et wchar_tles types associés) ont unsigned toutes des versions, qui modifient la plage de valeurs que la doublevariable peut stocker. Par exemple, un intentier signé 32 bits peut représenter une valeur comprise entre -2 147 483 648 et 2 147 483 647. Un unsigned int, qui est également stocké sous forme de 32 bits, peut stocker une valeur comprise entre 0 et 4 294 967 295. Le nombre total de valeurs possibles dans chaque cas est identique ; seule la plage est différente.

Le compilateur reconnaît ces types intégrés, et il a des règles intégrées qui régissent les opérations que vous pouvez effectuer sur elles, ainsi que la façon dont elles peuvent être converties en d’autres types fondamentaux. Pour obtenir la liste complète des types intégrés et de leur taille et de leurs limites numériques, consultez les types intégrés.

L’illustration suivante montre les tailles relatives des types intégrés dans l’implémentation Microsoft C++ :

Diagram of the relative size in bytes of several built in types.

Le tableau suivant répertorie les types fondamentaux les plus fréquemment utilisés et leurs tailles dans l’implémentation Microsoft C++ :

Type Taille Commentaire
int 4 octets Choix par défaut pour les valeurs intégrales.
double 8 octets Choix par défaut pour les valeurs à virgule flottante.
bool 1 octet Représente des valeurs qui peuvent être true ou false.
char 1 octet À utiliser pour les caractères ASCII dans les chaînes de style C plus anciennes ou les objets std::string qui ne devront jamais être convertis en UNICODE.
wchar_t 2 octets Représente les valeurs à caractères « larges » qui peuvent être encodées au format UNICODE (UTF-16 sur Windows, mais peut varier sur les autres systèmes d'exploitation). wchar_t est le type de caractère utilisé dans les chaînes de type std::wstring.
unsigned char 1 octet C++ n’a pas de type d’octet intégré. Permet unsigned char de représenter une valeur d’octet.
unsigned int 4 octets Option par défaut pour les bits indicateurs.
long long 8 octets Représente une plage beaucoup plus grande de valeurs entières.

D’autres implémentations C++ peuvent utiliser des tailles différentes pour certains types numériques. Pour plus d’informations sur les tailles et les relations de taille requises par la norme C++, consultez les types intégrés.

Le type void

Le void type est un type spécial ; vous ne pouvez pas déclarer une variable de type void, mais vous pouvez déclarer une variable de type void * (pointeur vers void), qui est parfois nécessaire lors de l’allocation de mémoire brute (non typée). Toutefois, les pointeurs à void ne sont pas de type sécurisé et leur utilisation est déconseillée en C++moderne. Dans une déclaration de fonction, une void valeur de retour signifie que la fonction ne retourne pas de valeur ; l’utiliser comme type de retour est une utilisation courante et acceptable de void. Alors que les fonctions requises du langage C qui ont zéro paramètre à déclarer void dans la liste des paramètres, par exemple, fn(void)cette pratique est déconseillée en C++moderne ; une fonction sans paramètre doit être déclarée fn(). Pour plus d’informations, consultez Conversions de type et sécurité des types.

const qualificateur de type

Tout type intégré ou défini par l’utilisateur peut être qualifié par l’mot clé const . En outre, les fonctions membres peuvent être constqualifiées et même constsurchargées. La valeur d’un const type ne peut pas être modifiée après son initialisation.

const double PI = 3.1415;
PI = .75; //Error. Cannot modify const variable.

Le const qualificateur est utilisé en grande partie dans les déclarations de fonction et de variable et « const correctness » est un concept important en C++. Il signifie essentiellement qu’il const est utilisé pour garantir, au moment de la compilation, que les valeurs ne sont pas modifiées involontairement. Pour plus d’informations, consultez const.

Un const type est distinct de sa version nonconst - ; par exemple, const int il s’agit d’un type distinct de int. Vous pouvez utiliser l’opérateur C++ const_cast à ces rares occasions lorsque vous devez supprimer const-ness d’une variable. Pour plus d’informations, consultez Conversions de type et sécurité des types.

Types chaîne

Strictement parlant, le langage C++ n’a pas de type de chaîne intégré ; char et wchar_t stockez des caractères uniques : vous devez déclarer un tableau de ces types pour estimer une chaîne, en ajoutant une valeur null de fin (par exemple, ASCII '\0') à l’élément de tableau un au-delà du dernier caractère valide (également appelé chaîne de style C). Les chaînes de style C exigent beaucoup plus de code pour être écrites ou l'utilisation de fonctions de bibliothèque d'utilitaires de chaînes externes. Toutefois, dans C++moderne, nous avons les types std::string de bibliothèque standard (pour les chaînes de caractères de type 8 bits char) ou std::wstring (pour les chaînes de caractères de type 16 bits wchar_t). Ces conteneurs de bibliothèque standard C++ peuvent être considérés comme des types de chaînes natifs, car ils font partie des bibliothèques standard incluses dans n’importe quel environnement de build C++ conforme. Utilisez la #include <string> directive pour rendre ces types disponibles dans votre programme. (Si vous utilisez MFC ou ATL, la CString classe est également disponible, mais ne fait pas partie de la norme C++.) L’utilisation de tableaux de caractères arrêtés par null (les chaînes de style C mentionnées précédemment) est déconseillée en C++moderne.

Types définis par l'utilisateur

Lorsque vous définissez une classconstruction , ou enumstructunionune construction utilisée dans le reste de votre code comme s’il s’agissait d’un type fondamental. Il a une taille connue en mémoire, et certaines règles relatives à son utilisation s’appliquent à celui-ci pour la vérification au moment de la compilation et, au moment de l’exécution, pour la durée de vie de votre programme. Les principales différences entre les types intégrés fondamentaux et les types définis par l'utilisateur sont les suivantes :

  • Le compilateur n'a pas connaissance de manière intégrée d'un type défini par l'utilisateur. Il apprend le type lorsqu’il rencontre d’abord la définition pendant le processus de compilation.

  • Vous spécifiez les opérations qui peuvent être exécutées sur votre type et comment il peut être converti en d'autres types, en définissant (via la surcharge) les opérateurs appropriés, comme membres de classe ou comme fonctions non membres. Pour plus d’informations, consultez Surcharge de fonction

Types de pointeur

Comme dans les premières versions du langage C, C++ continue de vous permettre de déclarer une variable d’un type de pointeur à l’aide du déclarateur * spécial (astérisque). Un type pointeur stocke l'adresse de l'emplacement dans la mémoire où la valeur réelle des données est stockée. Dans C++moderne, ces types de pointeurs sont appelés pointeurs bruts et sont accessibles dans votre code via des opérateurs spéciaux : * (astérisque) ou -> (tiret avec une flèche supérieure à, souvent appelée flèche). Cette opération d’accès à la mémoire est appelée déreferencing. L’opérateur que vous utilisez varie selon que vous déreferencez un pointeur vers un scalaire ou un pointeur vers un membre dans un objet.

L'utilisation des types pointeur a longtemps été l'un des aspects les plus importants et les plus perturbants du développement de programmes en C et C++. Cette section décrit certains faits et pratiques pour vous aider à utiliser des pointeurs bruts si vous le souhaitez. Toutefois, en C++moderne, il n’est plus nécessaire (ou recommandé) d’utiliser des pointeurs bruts pour la propriété de l’objet, en raison de l’évolution du pointeur intelligent (abordé plus en détail à la fin de cette section). Il est toujours utile et sûr d’utiliser des pointeurs bruts pour observer des objets. Toutefois, si vous devez les utiliser pour la propriété de l’objet, vous devez le faire avec précaution et en tenant compte avec précaution de la façon dont les objets détenus par eux sont créés et détruits.

La première chose que vous devez savoir est qu’une déclaration de variable de pointeur brute alloue uniquement suffisamment de mémoire pour stocker une adresse : l’emplacement de mémoire auquel le pointeur fait référence lorsqu’il est déréférencé. La déclaration de pointeur n’alloue pas la mémoire nécessaire pour stocker la valeur des données. (Cette mémoire est également appelée magasin de stockage.) En d’autres termes, en déclarant une variable de pointeur brute, vous créez une variable d’adresse mémoire, et non une variable de données réelle. Si vous déréférencez une variable de pointeur avant de vous assurer qu’elle contient une adresse valide pour un magasin de stockage, cela provoque un comportement non défini (généralement une erreur irrécupérable) dans votre programme. L'exemple suivant illustre ce type d'erreur :

int* pNumber;       // Declare a pointer-to-int variable.
*pNumber = 10;      // error. Although this may compile, it is
                    // a serious error. We are dereferencing an
                    // uninitialized pointer variable with no
                    // allocated memory to point to.

L'exemple déréférence un type pointeur sans avoir de mémoire allouée pour stocker les données entières réelles, ni d'adresse mémoire valide assignée. Le code suivant corrige les erreurs suivantes :

    int number = 10;          // Declare and initialize a local integer
                              // variable for data backing store.
    int* pNumber = &number;   // Declare and initialize a local integer
                              // pointer variable to a valid memory
                              // address to that backing store.
...
    *pNumber = 41;            // Dereference and store a new value in
                              // the memory pointed to by
                              // pNumber, the integer variable called
                              // "number". Note "number" was changed, not
                              // "pNumber".

L'exemple de code corrigé utilise la mémoire de la pile locale pour créer le magasin de stockage vers lequel pointe pNumber. Nous utilisons un type fondamental pour des raisons de simplicité. Dans la pratique, les magasins de stockage pour les pointeurs sont des types définis par l’utilisateur qui sont alloués dynamiquement dans une zone de mémoire appelée tas (ou magasin libre) à l’aide d’une new expression mot clé (dans la programmation de style C, l’ancienne malloc() fonction de bibliothèque runtime C a été utilisée). Une fois allouées, ces variables sont normalement appelées objets, en particulier si elles sont basées sur une définition de classe. La mémoire allouée new doit être supprimée par une instruction correspondante delete (ou, si vous avez utilisé la malloc() fonction pour l’allouer, la fonction free()runtime C).

Toutefois, il est facile d’oublier de supprimer un objet alloué dynamiquement, en particulier dans le code complexe, ce qui provoque un bogue de ressource appelé fuite de mémoire. Pour cette raison, l’utilisation de pointeurs bruts est déconseillée dans C++moderne. Il est presque toujours préférable d’encapsuler un pointeur brut dans un pointeur intelligent, ce qui libère automatiquement la mémoire lorsque son destructeur est appelé. (Autrement dit, lorsque le code sort de l’étendue du pointeur intelligent.) En utilisant des pointeurs intelligents, vous éliminez pratiquement toute une classe de bogues dans vos programmes C++. Dans l'exemple suivant, supposez que MyClass est un type défini par l'utilisateur qui a une méthode DoSomeWork();publique

void someFunction() {
    unique_ptr<MyClass> pMc(new MyClass);
    pMc->DoSomeWork();
}
  // No memory leak. Out-of-scope automatically calls the destructor
  // for the unique_ptr, freeing the resource.

Pour plus d’informations sur les pointeurs intelligents, consultez Pointeurs intelligents.

Pour plus d’informations sur les conversions de pointeur, consultez Conversions de type et sécurité des types.

Pour plus d’informations sur les pointeurs en général, consultez Pointers.

Types de données Windows

Dans la programmation Win32 classique pour C et C++, la plupart des fonctions utilisent des typesdefs et #define des macros spécifiques à Windows (définis dans windef.h) pour spécifier les types de paramètres et les valeurs de retour. Ces types de données Windows sont principalement des noms spéciaux (alias) donnés aux types intégrés C/C++. Pour obtenir la liste complète de ces typesdefs et définitions de préprocesseur, consultez Les types de données Windows. Certains de ces typesdefs, tels que HRESULT et LCID, sont utiles et descriptifs. D’autres, comme INT, n’ont pas de signification particulière et sont simplement des alias pour les types C++ fondamentaux. D'autres types de données Windows ont des noms qui proviennent de l'époque de la programmation en C et des processeurs 16 bits ; ils n'ont aucune finalité ou signification particulière par rapport au matériel ou aux systèmes d'exploitation modernes. Il existe également des types de données spéciaux associés à la bibliothèque Windows Runtime, répertoriés comme types de données de base Windows Runtime. En C++moderne, l’orientation générale consiste à préférer les types fondamentaux C++, sauf si le type Windows communique une signification supplémentaire sur la façon dont la valeur doit être interprétée.

Plus d’informations

Pour plus d’informations sur le système de type C++, consultez les articles suivants.

Types valeur
Décrit les types valeur ainsi que les problèmes liés à leur utilisation.

Conversions de types et sécurité des types
Décrit les problèmes de conversion des types courants et indique comment les éviter.

Voir aussi

Bienvenue dans C++
Informations de référence sur le langage C++
Bibliothèque C++ standard