C ++ succinctement Types

Types fondamentaux

C ++ contient les mêmes mots-clés connus (par exemple, int) que vous reconnaissez à partir de C #. Ce n'est pas surprenant étant donné que les deux sont des langages de type C. Il existe toutefois une mine terrestre potentielle qui peut vous semer des ennuis. Alors que C # définit explicitement la taille des types fondamentaux (un raccourci est un entier de 16 bits, un entier est un entier de 32 bits, un long est un entier de 64 bits, un double est un flottant IEEE 754 double précision de 64 bits numéro de point, etc.), C ++ ne fait aucune garantie.

La plus petite unité fondamentale en C ++ est char, qui doit être au moins suffisamment grande pour contenir les 96 caractères de base spécifiés par la norme C ++, ainsi que tous les autres caractères du jeu de caractères de base de l'implémentation. En théorie, certaines implémentations de C ++ pourraient définir un caractère comme 7 ou 16 bits… presque tout est possible. Mais dans la pratique, vous n'avez pas à vous soucier qu'un caractère soit autre que 8 bits (l'équivalent du type octet ou sbyte en C #), qui est sa taille dans Visual C++.

En C ++, char, char signé et non signé sont trois types distincts. Tous les trois doivent utiliser la même quantité de mémoire en mémoire. En pratique, un caractère est soit signé, soit non signé. Qu'il soit signé ou non signé, l'implémentation est définie (voir l'encadré). Dans Visual C ++, le type de caractère est, par défaut, signé. Mais vous pouvez utiliser un commutateur de compilateur pour le traiter comme non signé. Dans GCC, qu’il soit signé ou non, cela dépend de l’architecture de la CPU que vous ciblez..

Les types d'entiers signés, par ordre de taille, du plus petit au plus grand, sont les suivants:

  1. caractère signé
  2. court int
  3. int
  4. long int
  5. long long int

La seule garantie de la taille de chacun de ces types entiers est que chacun d'eux est au moins aussi grand que le type entier le plus petit suivant. Dans Visual C ++, un int et un long int sont tous deux des entiers 32 bits. C'est seulement le long long int qui est un entier 64 bits.

Remarque: Vous pouvez simplement écrire long ou long long; vous n'avez pas besoin d'écrire long int ou long long int, respectivement. Il en va de même pour short int (vous pouvez simplement écrire en bref). Le type court est un entier signé de 16 bits dans Visual C++.

Chacun des types entiers a un type entier non signé correspondant. Vous venez de mettre le mot-clé non signé devant pour obtenir la version non signée (sauf pour le caractère signé, que vous changez en caractère non signé).

Si vous devez vous assurer que vous utilisez des tailles spécifiques, vous pouvez inclure le fichier d'en-tête C ++ Standard Library. cstdint (par exemple., #comprendre ), qui définit entre autres les types:

  1. int8_t
  2. int16_t
  3. int32_t
  4. int64_t
  5. uint8_t
  6. uint16_t
  7. uint32_t
  8. uint64_t

Ces types ont leur utilité, mais vous constaterez que la plupart des API ne les utilisent pas; au lieu de cela, ils utilisent directement les types fondamentaux. Cela peut rendre votre programmation déroutante, car vous devez constamment vérifier le type fondamental sous-jacent pour vous assurer de ne pas vous retrouver avec une troncature ou une expansion non intentionnelle..

Ces types pouvant être utilisés plus fréquemment, je vous recommande de vérifier de temps en temps leur utilisation dans les principales bibliothèques et les principales API et d'ajuster votre code en conséquence s'ils sont largement adoptés. Bien sûr, si vous avez absolument besoin qu'une variable soit, par exemple, un entier 32 bits non signé, vous devez certainement utiliser uint32_t et procéder aux ajustements nécessaires pour les appels d'API et la portabilité..

Les nombres en virgule flottante sont les mêmes en ce qui concerne les règles d'ordre de taille. Ils vont de float à doubler à long doubler. Dans Visual C ++, float est un nombre à virgule flottante de 32 bits et double et long double sont des nombres à virgule flottante de 64 bits (long double n'est pas plus grand que double, en d'autres termes)..

C ++ ne possède aucun type natif comparable au type décimal de C #. Cependant, l’un des avantages de C ++ est qu’il existe généralement un grand nombre de bibliothèques gratuites ou peu coûteuses que vous pouvez utiliser sous licence. Par exemple, il y a la bibliothèque decNumber, la bibliothèque mathématique de points décimaux Intel décimale et la bibliothèque arithmétique à précision multiple GNU. Aucun n'est exactement compatible avec le type décimal de C #, mais si vous écrivez pour des systèmes Windows uniquement, vous pouvez utiliser le type de données DECIMAL pour obtenir cette compatibilité si nécessaire, ainsi que les fonctions arithmétiques décimales et les fonctions de conversion de type de données..

Il existe également un type booléen, bool, qui peut être vrai ou faux. Dans Visual C ++, un bool prend un octet. Contrairement à C #, un booléen peut être transformé en un type entier. Lorsque la valeur est false, sa valeur est un entier équivalent à 0 et, lorsque la valeur est true, sa valeur est 1. L'instruction bool result = true == 1; compilera et le résultat sera évalué à vrai lorsque l'instruction aura été exécutée.

Ensuite, il y a le type wchar_t, qui contient un caractère large. La taille d'un personnage large varie en fonction de la plate-forme. Sur les plates-formes Windows, il s'agit d'un caractère 16 bits. C'est l'équivalent du type de caractère de C #. Il est fréquemment utilisé pour construire des chaînes. Nous discuterons des chaînes dans un autre chapitre car de nombreuses variantes peuvent être utilisées pour les chaînes..

Enfin, il y a le type void, qui est utilisé de la même manière qu'en C #. Et il existe un type std :: nullptr_t, ce qui est compliqué à expliquer correctement, mais il doit exister le type du littéral nullptr, ce que vous devez utiliser à la place de NULL ou d'un littéral 0 (zéro) pour rechercher la valeur null. valeurs.

Énumérations

Les énumérations sont assez similaires les unes des autres en C ++ et C #. C ++ a deux types d'énums: étendu et non défini.

Une énumération scoped est définie en tant que classe enum ou structure enum. Il n'y a pas de différence entre les deux. Une énumération non définie est définie comme une énumération simple. Regardons un exemple:

Exemple: EnumSample \ EnumSample.cpp

#comprendre  #comprendre  #comprendre  #include "… /pchar.h" enum class Couleur Rouge, Orange, Jaune, Bleu, Indigo, Violet; // Vous pouvez spécifier n'importe quel type d'intégral sous-jacent que vous voulez, à condition qu'il convienne. Enum Saveur: unsigned short int Vanille, Chocolat, Fraise, Menthe; int _pmain (int / * argc * /, _pchar * / * argv * / []) Saveur f = Vanille; f = menthe; // Ceci est légal puisque l'énumération des saveurs est une énumération non définie. Couleur c = Couleur :: Orange; // c = orange; // Ceci est illégal car l'énumération Color est une énumération étendue. std :: saveur de wstring; std :: wstring color; commutateur (c) case Color :: Red: color = L "Red"; Pause; Couleur du boîtier :: Orange: color = L "Orange"; Pause; Couleur du boîtier :: Jaune: color = L "Yellow"; Pause; Couleur du boîtier :: Bleu: color = L "Blue"; Pause; couleur du boîtier :: Indigo: color = L "Indigo"; Pause; Couleur du boîtier :: Violet: color = L "Violet"; Pause; défaut: color = L "Unknown"; Pause;  switch (f) case Vanilla: flavour = L "Vanilla"; Pause; cas Chocolat: saveur = L "Chocolat"; Pause; étui Fraise: saveur = L "Fraise"; Pause; étui menthe: flaveur = L "menthe"; Pause; défaut: break;  std :: wcout << L"Flavor is " << flavor.c_str() << L" (" << f << L"). Color is " << color.c_str() << L" (" << static_cast(c) << L")." << std::endl << L"The size of Flavor is " << sizeof(Flavor) << L"." << std::endl << L"The size of Color is " << sizeof(Color) << L"." << std::endl; return 0; 

Ce code donnera la sortie suivante:

La saveur est la menthe (3). La couleur est orange (1). La taille de Saveur est 2. La taille de Couleur est 4.

Comme vous pouvez le voir dans l'exemple, l'énumération Scoped Color nécessite que vous accédiez à ses membres de la même manière que C # en faisant précéder le membre d'énumération du nom de l'énumération et de l'opérateur de résolution de la portée. En revanche, l’énumération non ciblée de Flavour vous permet simplement de spécifier les membres sans préfixe. Pour cette raison, je pense qu'il est préférable de préférer les énumérations étendues: vous minimisez les risques de collision de noms et réduisez la pollution de l'espace de noms.

Notez qu'il existe une autre différence avec les énumérations scoped: lorsque nous voulions afficher la valeur numérique de l'énumération Color scoped, nous devions utiliser l'opérateur static_cast pour le convertir en int, alors que nous n'avions pas besoin de transtypage pour le fichier un. énumération des arômes.

Pour l'énumération Flavour, nous avons spécifié le type sous-jacent comme étant un unsigned short int. Vous pouvez également spécifier le type sous-jacent pour les énumérations étendues. La spécification du type sous-jacent est facultative, mais elle est obligatoire si vous souhaitez utiliser la déclaration en aval avec une énumération non délimitée. La déclaration forward est un moyen d’accélérer les temps de compilation du programme en indiquant simplement au compilateur ce qu’il doit savoir sur un type plutôt que de le forcer à compiler l’ensemble du fichier d’en-tête dans lequel le type est défini..

Nous verrons cela plus tard. Pour l’instant, rappelez-vous qu’une énumération non définie doit avoir son type sous-jacent explicitement spécifié pour que l’on puisse en utiliser une déclaration ultérieure; une énumération de portée ne nécessite pas que la spécification de son type sous-jacent utilise une déclaration directe de celle-ci (le type sous-jacent sera int si aucune n'est spécifiée).

Vous pouvez faire la même chose avec les énumérations en C ++ et en C # en ce qui concerne l’affectation explicite de valeurs aux membres et la création d’énumérations d’indicateurs. Vous le faites de la même manière, sauf que vous n'avez pas besoin d'appliquer quoi que ce soit comme FlagAttribute en C ++ pour créer des énumérations d'indicateurs; vous attribuez simplement les valeurs correctes et procédez à partir de là.

std :: wcout, std :: wcerr, std :: wcin

Le std :: wcout << L”Flavor… code outputs wide character data to the standard output stream. In the case of a console program such as this, the standard output is the console window. There is also a std::wcerr output stream, which will output wide character data to the standard error output stream. This is also the console window, but you can redirect std::wcout output to one file and std::wcerr output to another file. There is also a std::wcin for inputting data from the console. We won't explore this, nor will we explore their byte counterparts: std::cout, std::cerr, and std::cin.

Juste pour vous permettre de voir à quoi ressemble l'entrée, voici un exemple.

Exemple: ConsoleSample \ ConsoleSample.cpp

#comprendre  #comprendre  #comprendre  #include "… /pchar.h" struct Color float ARGB [4]; void A (valeur flottante) ARGB [0] = valeur;  float A (void) const return ARGB [0];  void R (valeur flottante) ARGB [1] = valeur;  float R (void) const return ARGB [1];  void G (valeur flottante) ARGB [2] = valeur;  float G (void) const return ARGB [2];  void B (valeur flottante) ARGB [3] = valeur;  float B (void) const return ARGB [3]; ; // Ceci est une fonction autonome, qui se trouve être un opérateur // binaire pour le << operator when used with a wostream on // the left and a Color instance on the right. std::wostream& operator<<(std::wostream& stream, const Color& c)  stream << L"ARGB: " << c.A() << L"f, " << c.R() << L"f, " << c.G() << L"f, " << c.B() << L"f "; return stream;  int _pmain(int /*argc*/, _pchar* /*argv*/[])  std::wcout << L"Please input an integer and then press Enter: "; int a; std::wcin >> a; std :: wcout << L"You entered '" << a << L"'." << std::endl; std::wcout << std::endl << L"Please enter a noun (one word, no spaces) " << L"and then press Enter: "; std::wstring noun; // wcin breaks up input using white space, so if you include a space or // a tab, then it would just put the first word into noun and there // would still be a second word waiting in the input buffer. std::wcin >> nom; std :: wcerr << L"The " << noun << L" is on fire! Oh no!" << std::endl; Color c =   100.0f/255.0f, 149.0f/255.0f, 237.0f/255.0f, 1.0f  ; // This uses our custom operator from above. Come back to this sample // later when we've covered operator overloading and this should make // much more sense. std::wcout << std::endl << L"Cornflower Blue is " << c << L"." << std::endl; return 0; 

Le code précédent est une démo assez simple. Il n'y a pas d'erreur de vérification, par exemple. Donc, si vous entrez une valeur incorrecte pour le nombre entier, cela ira jusqu'au bout, avec std :: wcin revenant instantanément sans aucune donnée (c'est ce qu'il fait sauf si vous résolvez l'erreur).

Si vous êtes intéressé par la programmation iostream, y compris l’utilisation de choses comme std :: wofstream pour exporter des données dans un fichier et std :: wifstream pour les lire à partir d’un fichier (ils fonctionnent de la même manière que std :: wcout et std :: wcin, avec des fonctionnalités supplémentaires pour gérer le fait qu’ils travaillent avec des fichiers), consultez les pages de programmation MSDN iostream. Apprendre tous les tenants et aboutissants des flux pourrait facilement remplir un livre tout seul.

Une dernière chose cependant. Vous avez sans doute remarqué que la fonctionnalité de flux est un peu étrange avec les opérateurs à décalage de bits << and >> C'est parce que ces opérateurs ont été surchargés. Bien que vous vous attendiez à ce que les opérateurs de décalage de bits agissent d'une certaine manière sur les entiers, vous ne vous attendez probablement pas à savoir comment ils devraient fonctionner lorsqu'ils sont appliqués à un flux de sortie ou à un flux d'entrée, respectivement. Ainsi, les flux de la bibliothèque standard C ++ ont coopté ces opérateurs pour les utiliser pour l’entrée et la sortie de données dans des flux. Lorsque nous voulons pouvoir lire ou écrire un type personnalisé que nous avons créé (comme la structure Color précédente), nous devons simplement créer une surcharge d'opérateur appropriée. Nous en apprendrons plus sur la surcharge des opérateurs plus tard dans le livre, alors ne vous inquiétez pas si c'est un peu déroutant maintenant


Classes et Structures

La différence entre une classe et une structure en C ++ est simplement que les membres d'une structure sont passés à public alors que les membres d'une classe sont passés à privé. C'est tout. Ils sont autrement les mêmes. Il n'y a pas de distinction type valeur / référence comme en C #.

Cela dit, vous verrez généralement que les programmeurs utilisent des classes pour les types élaborés (combinaisons de données et de fonctions) et des structures pour des types simples contenant uniquement des données. Normalement, il s’agit d’un choix stylistique qui représente les origines non centrées de la structure en C, facilitant la distinction rapide entre un simple conteneur de données et un objet complet en cherchant à savoir s’il s’agit d’une structure ou d’une classe. Je recommande de suivre ce style.

Remarque: Une exception à ce style est lorsqu'un programmeur écrit un code destiné à être utilisé à la fois en C et en C ++. Comme C n'a pas de type classe, le type de structure peut être utilisé de la même façon que vous utiliseriez une classe en C ++. Je ne vais pas couvrir l'écriture C ++ compatible C dans ce livre. Pour ce faire, vous devez connaître le langage C et ses différences avec le C ++. Au lieu de cela, nous nous concentrons sur l'écriture de code C ++ propre et moderne.

Dans la programmation Windows Runtime («WinRT»), une structure publique ne peut avoir que des membres de données (pas de propriétés ni de fonctions). Ces membres de données ne peuvent être constitués que de types de données fondamentaux et d'autres structures publiques, qui, bien entendu, comportent les mêmes restrictions: données uniquement, données fondamentales et structures publiques uniquement. Gardez cela à l'esprit si vous travaillez sur des applications de style métropolitain pour Windows 8 à l'aide de C++.

Vous verrez parfois le mot-clé ami utilisé dans une définition de classe. Il est suivi d'un nom de classe ou d'une déclaration de fonction. Cette construction de code donne à cette classe ou fonction accès aux données et fonctions membres non publiques de la classe. En règle générale, vous voudrez éviter cela car votre classe doit normalement exposer tout ce que vous souhaitez exposer via son interface publique. Mais dans les rares cas où vous ne souhaitez pas exposer publiquement certains membres de données ou fonctions membres, mais souhaitez qu'une ou plusieurs classes ou fonctions y aient accès, vous pouvez utiliser le mot clé friend pour accomplir cela..

Les classes étant une partie très importante de la programmation C ++, nous les explorerons plus en détail plus loin dans le livre..

Les syndicats

Le type d'union est un peu étrange, mais il a ses utilisations. Vous le rencontrerez de temps en temps. Une union est une structure de données qui semble contenir de nombreux membres de données, mais vous permet uniquement d'utiliser l'un de ses membres de données à la fois. Le résultat final est une structure de données qui vous donne de nombreuses utilisations possibles sans gaspiller de mémoire. La taille du syndicat doit être suffisante pour contenir le membre le plus important du syndicat. En pratique, cela signifie que les membres de données se chevauchent en mémoire (par conséquent, vous ne pouvez en utiliser qu'un seul à la fois). Cela signifie également que vous n'avez aucun moyen de savoir ce qu'est le membre actif d'un syndicat à moins de le suivre d'une manière ou d'une autre. Vous pouvez le faire de nombreuses façons, mais mettre un syndicat et une enum dans une structure est une bonne façon de le faire, simple et ordonnée. Voici un exemple.

Exemple: UnionSample \ UnionSample.cpp

#comprendre  #comprendre  #include "… /pchar.h" enum class SomeValueDataType Int = 0, Float = 1, Double = 2; struct SomeData SomeValueDataType Type; union int iData; float fData; double dData;  Valeur; SomeData (void) SomeData (0);  SomeData (int i) Type = SomeValueDataType :: Int; Value.iData = i;  SomeData (float f) Type = SomeValueDataType :: Float; Value.fData = f;  SomeData (double d) Type = SomeValueDataType :: Double; Value.dData = d; ; int _pmain (int / * argc * /, _pchar * / * argv * / []) SomeData data = SomeData (2.3F); std :: wcout << L"Size of SomeData::Value is " << sizeof(data.Value) << L" bytes." << std::endl; switch (data.Type)  case SomeValueDataType::Int: std::wcout << L"Int data is " << data.Value.iData << L"." << std::endl; break; case SomeValueDataType::Float: std::wcout << L"Float data is " << data.Value.fData << L"F." << std::endl; break; case SomeValueDataType::Double: std::wcout << L"Double data is " << data.Value.dData << L"." << std::endl; break; default: std::wcout << L"Data type is unknown." << std::endl; break;  return 0; 

Comme vous pouvez le constater, nous définissons une énumération dans laquelle les membres représentent chacun des types de membres du syndicat. Nous définissons ensuite une structure qui inclut à la fois une variable du type de cette énumération, puis une union anonyme. Cela nous donne toutes les informations dont nous avons besoin pour déterminer le type actuel du syndicat dans un package encapsulé..

Si vous souhaitez que le syndicat soit utilisable dans plusieurs structures, vous pouvez le déclarer en dehors de la structure et lui donner un nom (par exemple, union SomeValue …;). Vous pouvez ensuite l'utiliser dans la structure, par exemple, SomeValue Value ;. Il est généralement préférable de le garder comme un syndicat anonyme, car vous n'avez pas à vous soucier des effets secondaires d'un changement, sauf dans les structures dans lesquelles il est défini..

Les syndicats peuvent avoir des constructeurs, des destructeurs et des fonctions membres. Mais comme ils ne peuvent avoir qu'un seul membre de données actif, il est rarement logique d'écrire des fonctions de membre pour une union. Vous les verrez rarement, peut-être jamais.

typedef

La première chose à comprendre à propos de typedef est que, malgré les implications de son nom, typedef ne crée pas de nouveaux types. C'est un mécanisme de repliement qui peut être utilisé pour beaucoup de choses.

Il est beaucoup utilisé pour implémenter la bibliothèque standard C ++ et d'autres codes basés sur des modèles. C'est sans doute son utilisation la plus importante. Nous l'explorerons plus en détail dans le chapitre sur les modèles.

Cela peut vous éviter beaucoup de frappe (bien que cet argument ait perdu de sa force avec la réutilisation du mot clé auto pour la déduction de types en C ++ 11). Si vous avez un type de données particulièrement compliqué, créer une typedef signifie que vous n’aurez à le taper qu’une seule fois. Si l'objectif de votre type de données complexe n'est pas clair, lui attribuer un nom plus significatif sémantiquement avec un typedef peut aider à rendre votre programme plus facile à comprendre..

Il est parfois utilisé comme une abstraction par les développeurs pour changer facilement un type de support (par exemple, d'un std :: vector à un std :: list) ou le type d'un paramètre (par exemple d'un int à long). Pour votre propre code à usage interne uniquement, cela devrait être mal vu. Si vous développez du code que d'autres utiliseront, par exemple une bibliothèque, vous ne devriez jamais essayer d'utiliser un typedef de cette manière. Tout ce que vous faites est de réduire la possibilité de briser les modifications apportées à votre API si vous modifiez un typedef. Utilisez-les pour ajouter un contexte sémantique, bien sûr, mais ne les utilisez pas pour changer un type de code sous-jacent sur lequel d'autres s'appuient.

Si vous avez besoin de changer le type de quelque chose, rappelez-vous que toute modification des paramètres d'une fonction est une modification majeure, tout comme une modification du type de retour ou l'ajout d'un argument par défaut. La bonne façon de gérer la possibilité d'un changement de type futur consiste à utiliser des classes abstraites ou des modèles (celui qui convient le mieux ou celui que vous préférez, si les deux serviront). De cette façon, l'interface publique avec votre code ne changera pas, seule l'implémentation changera. Le langage Pimpl est un autre moyen efficace de conserver une API stable tout en conservant la liberté de modifier les détails de la mise en œuvre. Nous explorerons le langage Pimpl, en abrégé "pointeur vers la mise en œuvre", dans un chapitre ultérieur.

Voici un petit bloc de code illustrant la syntaxe de typedef.

classe ExistingType; typedef ExistingType AliasForExistingType;

Et ce qui suit est un bref exemple montrant comment typedef peut être utilisé. Le but de cet exemple est d’illustrer une utilisation simplifiée mais réaliste d’une typedef. En pratique, une telle typedef entrerait dans un espace de noms et serait ensuite incluse dans un fichier d'en-tête. Puisque nous n’avons rien fait de tout cela, cet exemple a été gardé intentionnellement simple.

Exemple: TypedefSample \ TypedefSample.cpp

#comprendre  #comprendre  #comprendre  #comprendre  #include "… /pchar.h" // Ceci fait de WidgetIdVector un alias pour std :: vector, qui a // plus de signification que std :: vector aurait, puisque maintenant nous savons que // tout ce qui utilise cet alias attend un vecteur d'identifiants de widget // plutôt qu'un vecteur d'entiers. typedef std :: vector WidgetIdVector; bool ContainsWidgetId (WidgetIdVector idVector, ID int) return (std :: end (idVector)! = std :: find (std :: begin (idVector), std :: end (idVector), id));  int _pmain (int / * argc * /, _pchar * / * argv * / []) WidgetIdVector idVector; // Ajoute quelques identifiants au vecteur. idVector.push_back (5); idVector.push_back (8); // Affiche un résultat nous indiquant si l'ID est dans le // WidgetIdVector. std :: wcout << L"Contains 8: " << (ContainsWidgetId(idVector, 8) ? L"true." : L"false.") << std::endl; return 0; 

Conclusion

Vous devez maintenant avoir une compréhension claire des types disponibles en C ++. Dans le prochain article, nous examinerons de plus près les espaces de noms en C++.

Cette leçon représente un chapitre de C ++ Succinctly, un eBook gratuit de l’équipe de Syncfusion..