Accueil > Dossiers > C++

C++ (tutorial 6)

Indications pour ceux qui veulent lire vite...

Les titres principaux des parties sont centrés en gras et soulignés. Les sous-parties sont alignées dans la gauche, en gras-italique et marron. Les récapitulatifs sont en bleu. Les codes C++ sont écrit en type "codé" et gras.


SOMMAIRE DU TUTORIAL 6

Quelques détails du langage
1.1 Portabilité du programe
1.2 Utilisation de la bibliothèque limits
1.3 Opérateur sizeof
1.4 Les pointeurs
1.5 Opérations élémentaires de calcul d'adresse
1.6 Initialisations atypiques de tableaux
1.7 Les structures
1.8 Utilisation de la bibliothèque ctype



Quelques détails du langage >> vers le sommaire

1.1 Portabilité du programme >> vers le sommaire

Quand on parle de "porter le programme" cela veut dire qu'on souhaite l'adapter à un système différent. Par exemple, un programme tapé pour Windows peut-être "porté" pour Linux. Il est important de savoir que les types de variables n'ont pas la même possibilité de contenance sur tous les systèmes ; sur certaines machines, les int ont 16 bits et 32 sur d'autres. Ainsi, l'astuce consiste à créer soit-même un type facilement modifiable d'un système à l'autre. On utilise pour cela typedef, comme dans l'exemple suivant :
typedef int int32;

Ici, on crée un nouveau type int32 calqué sur le type int. Toutes les fois que l'on souhaitera déclarer une variable de type "nombre de 32 bits", on utilisera int32, comme ci-dessous :
int32 exemple;

Si le programme construit par cette méthode doit, un jour, être porté sur un système où les int prennent 16 bits et les long 32 bits, alors on modifiera la ligne typedef int int32; en typedef long int32; ce qui évitera d'avoir à remplacer dans tout le programme des int par des long.

Si vous souhaitez faire appel à des types classiques (int, long, short...) en les nommant différent, vous pouvez aussi utiliser cette technique bien qu'il n'y ait pas d'intérêt particulier à exploiter un code comme :
typedef short petite_otarie;

Récapitulatif sur la gestion des variables dans la portabilité du programme :
- Les variables n'ont pas la même contenance en bit sur tous les systèmes.
- Quand il est possible qu'un programme soit adapté d'un système à un autre, plutôt que d'avoir à remplacer toutes les déclarations de variables il est préférable d'utiliser typedef qui possède la syntaxe typedef typehabituel copie; Par exemple : typedef int int32;

1.2 Utilisation de la bibliothèque limits >> vers le sommaire

Nous allons utiliser une nouvelle bibliothèque appelée limits qui permet d'avoir des renseignements sur les types de variables. Ouvrez le projet habituel Base, videz le, et tapez les instructions ci-dessous :

En premier, on déclare notre bibliothèque habituelle iostream, ensuite on déclare la bibliothèque limits. La dernière ligne demande l'utilisation de l'espace de nom standard, nécessaire pour utiliser la bibliothèque limits.

Maintenant, nous allons apprendre les fonctionnalités les plus utiles de limits. La première est la demande de la valeur minimale que peut prendre une variable, qui se fait de la façon suivante :
numeric_limits<int>::max()

Cependant, la valeur affichée dépendra du type demandé. Par exemple, un int vous affichera sa valeur maximale en chiffre, mais un char vous l'affichera en caractères. Tapez et exécutez ce code d'exemple pour comprendre :

Le résultat sera comme ci-dessous :

Ainsi, si vous voulez exprimer la valeur maximale d'un char sous la forme de chiffre, il faudra placer un type spécifique correspondant au résultat avant la commande, de la façon suivante :
(int)numeric_limits<char>::max()

On demande le minimum d'une variable sur un principe quasiment identique, à l'exception du remplacement de max par min :
numeric_limits<int>::min()

Nous pouvons aussi demander la contenance d'un type de variable. La commande suivante affiche le nombre de bits que représente le type :
numeric_limits<int>::digits

Si vous voulez connaître le nombre de bits que possède un char, voici la demande :
cout << "Le nombre de bits pour coder un char est " << numeric_limits<char>::digits << "\n";

Enfin, la dernière demande intéressante consiste à savoir si la variable a un signe. Pour cela, on utilise :
numeric_limits<int>::is_signed

La réponse sera affichée en booléen, c'est-à-dire 0 ou 1. Si c'est 1, la variable possède un signe ; si c'est 0, elle n'en possède pas.

Pour récapituler tout cela, créez un programme qui affiche les renseignements suivants :
- Valeur maximale et minimale d'un unsigned short
- Bits représentant un unsigned short
- Est-ce qu'un unsigned short a un signe

Le code répondant à ce petit cahier des charges est le suivant :

Récapitulatif sur l'utilisation de la bibliothèque limits :
- On doit la déclarer avec #include <limits> Elle nécessite aussi l'utilisation de l'espace de nom standard, que l'on déclare avec using namespace std;
- La syntaxe de son utilisation est numeric_limits<type>::fonction
- Pour savoir la valeur maximale que peut prendre un int, on demande numeric_limits<int>::max()
- Pour savoir la valeur minimale que peut prendre un int, on demande numeric_limits<int>::min()
- Pour savoir le nombre de bits que représente un int, on demande numeric_limits<int>::digits
- Pour savoir si un int est signé, on demande numeric_limits<int>::is_signed La réponse sera en booléen, c'est à dire 0 pour faux et 1 pour vrai.
- Si on veut que la réponse s'affiche dans un type particulier, il faut le préciser avant. Par exemple, pour afficher la valeur maximale d'un char sous forme de chiffre : (int)numeric_limits<char>::max()

1.3 Opérateur sizeof >> vers le sommaire

Cet opérateur permet de connaître la taille non pas en bits mais en octets. Voici un code d'exemple, exécutez le pour comprendre :

A l'exécution, vous obtiendrez :

On voit qu'un int est codé sur 4 octects. Puisque essai possède 10 cases de type char, et que char est codé sur un octect, alors, sizeof(essai) renvoit 10. Voici un autre exemple :

L'exécution du programme nous donne le résultat ci-dessous :

Il y a 10*3 cases donc 30 cases. Puisque char est codé sur un octet, alors 30*1=30.

Récapitulatif sur l'opérateur sizeof :
- La fonction sizeof() renvoit la taile en octet d'un élément. Par exemple, sizeof(char) donne 1 car char n'est codé que sur un octet. Si on a int exemple[2][3], sizeof(exemple) donnera 24 car 2*3=6 et int étant codé sur 4 octects 6*4=24.

1.4 Les pointeurs >> vers le sommaire

On s'en sert surtout en algorithme. C'est "théoriquement" important à savoir, mais pratiquement ce n'est pas vital. Comme vous le savez, toutes les variables occupent de la place en mémoire. Pour retrouver leur valeur, elles ont des adresses en mémoire : le pointeur renvoit à ses adresses en mémoires, qui elles-mêment renvoient à la valeur de la variable. Pour déclarer un pointeur, on utilise un astéristique * de la façon suivante :
int* exemple;

Ceci est un pointeur de int. L'astéristique se trouve à côté du type de pointeur, comme nous pouvons encore le voir avec les exemples suivants :
double* exemple2;
char* exemple3;

Si vous êtes vraiment tordus, vous pouvez même faire des pointeurs de pointeurs :
char** tordu;

Enfin, le but existentiel d'un pointeur est de pointer. Pour attribuer au pointeur l'élément vers lequel il doit pointer, on utilise "l'indirection" décrite par Bjarne Stroustrup comme "l'opération fondamentale" qui consiste à faire "référence à l'objet vers lequel est dirigé le pointeur". Appelez cela l'adressage, ce sera plus simple. Pour cette opération on utilise &, par exemple :
int variable(4);
int* pointeur=&variable;

Attention : pointeur ne renvoit pas la valeur de variable mais son adresse. A présent, créez un programme qui affiche la valeur d'une variable de votre choix et son adresse en mémoire. Le code que je propose est le suivant :

Le résultat du programme est celui ci-dessous :

Vous remarquerez que l'adresse en mémoire est exprimée sous forme héxadécimale, en raison de 0x et de la combinaison lettres/chiffres.

Récapitulatif sur les pointeurs :
- Une variable est stockée en mémoire. Le pointeur indique son adresse en mémoire.
- Pour déclarer un objet de type pointeur, on utilise un astérisque * à la fin du type qu'il pointe. Par exemple, pelican est un pointeur de char : char* pelican;
- Pour pointer une variable, c'est-à-dire vers son adresse mémoire, on utilise &. Par exemple, &otarie; pointe vers l'adresse en mémoire de la variable otarie. On peut aussi faire char* pelican=&otarie;

1.5 Opérations élémentaires de calcul d'adresse >> vers le sommaire

Les pointeurs peuvent pointer la quasi-totalité des éléments que vous connaissez, et particulièrement les tableaux. Créez un tableau de type int de 3 éléments et affichez-en les adresses en mémoire. Je propose le code suivant :

On obtient ce résultat :

Or, regardons les deux derniers chiffres, qui sont les seuls à varier : EC, F0, F4. Le lien est visible : on rajoute 4 pour passer à l'élément suivant. Si vous ne savez pas compter en héxadécimal, voici un tableau de correspondance :

Nombre en base 10 (décimal, courant)
Equivalent en hexadecimale
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
8
9
9
10
A
11
B
12
C
13
D
14
E
15
F

On continu après avec 16=10, 17=11, etc. etc. Donc, faites l'opération EC+4. Si vous n'y arrivez pas, voici un tableau :

Ajout à EC
+1
+2
+3
+4
Résultat
ED
EE
EF
F0

Vous voyez ainsi que EC+4=F0 et F0+4=F4. Or, nous sommes dans un tableau de int. Le chiffre 4 vous rappelles-t-il quelque chose ? Il s'agit de la taille en octet d'un int. En effet, sizeof(int)=4. Ainsi, si vous avez un élément d'un tableau, en ajoutant ou soustrayant la taille en octet de son type, vous pouvez obtenir l'adresse d'autres éléments.

Nous allons faire un petit exercice d'entraînement. Nous avons int tableau[5];, voici l'adresse de tableau[2] :

A partir de là, calculez l'adresse de tableau[1], tableau[0], tableau[3] et tableau [4].

La solution est :
- Adresse de tableau[1] : 0065FDC4-4 = 0065FDC0
- Adresse de tableau[0] : 0065FDC0 - 4 = 0065FDBC
- Adresse de tableau[3] : 0065FDC4+4= 0065FDC8
- Adresse de tableau[4] : 0065FDC8+4= 0065FDCC

Il est possible de soustraire deux pointeurs naturellement, comme dans le code ci-dessous :
int tableau[5];
cout << &tableau[3]-&tableau[2];

Cependant, le résultat affiché ne correspondra pas à la différence d'adressage ! Il correspondra à la différence d'emplacement dans le tableau, soit 3-2=1.

L'opération d'addition de pointeurs est, quant à elle, impossible.

Récapitulatif sur les opérations élémentaires de calcul d'adresse :
- Si, dans un tableau, on connait l'adresse de l'un des éléments, alors on peut connaître les éléments précédents ou suivants ou soustrayant ou additionnant la taille en octet du type de tableau. Par exemple, si le tableau est de type int, on soustraira 4 pour obtenir l'élément précédent et on aditionnera 4 pour obtenir l'élément suivant.
- La soustraction de deux pointeurs donne l'écart entre les pointeurs dans le tableau mais pas en adresse. Par exemple, &tableau[7]-&tableau[5]=2.
- L'addition de deux pointeurs est impossible.

1.6 Initialisations atypiques de tableaux >> vers le sommaire

Il est possible de créer des tableaux sans entrer leur taille mais en entrant uniquement leur contenu. Voici un code d'exemple :

D'après vous, quel sera le message qui apparaîtra à l'exécution du programme ? Les deux codes suivants sont équivalents :
int tableau[]={1,2,3,4,5};
et
int tableau[5];
for(int i(0);i<5;i++)
{
tableau[i]=i+1;
}

Partant de ce fait, on sait que nous avons fabriqué un tableau de 5 éléments. Puisque ces éléments sont des int, alors la taille du tableau est de 5*4 soit 20 bits et le message affiché sera "Le tableau fait 20 bits." Pour créer un tableau avec des valeurs numériques sans entrer sa taille, on donne immédiatement ={}; avec les valeurs successives entre accolades. Cependant, pour des caractères, on utilisera des guillements, par exemple :
char exemple[]="bonjour";

Un tableau de caractère, appelé "chaîne de caractères", se finit toujours par '\0' qui indique la fin de la chaîne. Ce caractère est hors de notre contrôle et placé automatiquement. Ainsi, puisque "bonjour" fait 7 lettres, on rajoute 1 lettre qui est '\0' pour avoir le total du contenu de la chaîne, soit 8 lettres. Un char valant 1 bits, on peut donc s'attendre à ce que le code suivant affiche "Le tableau fait 8 bits" :

Dans le cadre des chaînes de caractères, deux ensembles de guillements suivis sont regroupés. Par exemple, les deux codes suivants sont équivalents :
char tableau[]="Bonjour";
et
char tableau[]="Bon" "jour";

Si vous souhaitez initialiser des tableaux de cette manière, je vous conseilles de laisser un commentaire en dessous de la ligne de création du tableau, indiquant la taille de celui-ci. Il n'est pas en effet souhaitable de perdre du temps à compter les caractères ou les nombres...

Récapitulatif sur les initialisations atypiques de tableaux :
- On peut créer un tableau sans indiquer sa taille mais en lui attribuant directement les valeurs qu'il contiendra. Sa taille sera alors égale au nombre de valeurs qu'on lui a attribué. Par exemple, int tableau[]={1,2,3}; vient de faire la déclaration int tableau[3]; tout en remplissant le tableau.
- Dans le cas de valeurs numériques, elles sont entre accolades et séparées par des virgules.
- Dans le cas de caractères, ils sont entre guillements. Par exemple, char tableau[]="bonjour"; La taille d'un tableau de caractères, aussi appelée chaîne de caractères, est égale au nombre de caractères contenus + 1, pour le caractère automatique '\0' qui indique la fin de chaîne.
- Deux séries de guillements suivies sont regroupées. Exemple : char tableau[]="bonjour"; est égal à char tableau[]="bon""jour";

1.7 Les structures >> vers le sommaire

Vous pouvez créer des espèces d'objets que l'on appelle des structures. Vous pouvez en rencontrer dans certains programmes, généralement de bas niveau, il est donc utile d'en connaître le fonctionnement. Cependant, quand vous aurez appris les classes vous privilégierez ces dernières. Une structure se déclare par struct suivit du nom que vous lui donnez. Ensuite, entre accolade, vous définissez un certain nombre de variables associées à cette structure. Voici un exemple :
struct human{
int matricule;
int code_postal;
short age;
bool sexe;
};

Nous avons créée une structure appelée human et possédant plusieurs paramètres : matricule, code_postal, age, sexe. Nous allons maintenant fabriquer un objet utilisant cette structure. Pour cela il faut donner le nom de la structure suivie du nom que nous attribuons à l'objet, par exemple :
human henry;

henry est un objet de type human, et il possède donc les paramètres matricule, code_postal, age, sexe. Pour accéder à ces paramètres on utilise l'opérateur ".", voici un exemple :
henry.matricule=32;
cout << "Le matricule de l'objet est " << henry.matricule << "\n";

Si votre objet peut prendre un nom, il y a deux cas :
- Soit le nom ne fait pas partie d'une liste, auquel cas vous utiliserez des char
- Soit il fait partie d'une lise, et vous mettez à disposition un int avec, en commentaire, une équivalence entre les valeurs et le nombres

A présent, créez un objet dont les paramètres sont prix, masse et matiere ; ensuite, attribuez-lui des valeurs et affichez-les. Le code est le suivant :

Récapitulatif sur les structures :
- Pour créer une structure, on utilise struct suivie du nom qu'on donne à la structure. Après, entre accolades, on met les paramètres de la structure. Un exemple :
struct achat{
short nombre;
double prix_unitaire;
};

- Pour créer un objet d'une certaine structure, on marque le nom de la structure puis celui de l'objet, exemple :
achat exemple;
- Les paramètres de l'objet sont accessibles par l'opérateur ., par exemple : exemple.nombre=3;
- Si votre objet peut prendre un nom parmi plusieurs possibles, faites un int dont les valeurs auront une équivalence avec le nom indiquée en commentaires. Sinon, utilisez des caractères.

1.8 Utilisation de la bibliohèque ctype >> vers le sommaire

Cette bibliothèque permet de faire des tests sur des char pour voir s'ils contiennent des espaces, de la ponctuation, des majuscules, etc. etc. Vous pouvez faire exactement le même test en une ligne de vérification, mais puisqu'il y a une bibliothèque pour ça, autant la connaître. On la déclare simplement par #include <ctype.h>, elle n'a besoin de rien de plus. La fonction ispunct permet de vérifier s'il y a des signes de ponctuation ; s'il n'y en a pas, elle renvoit 0, et sinon une autre valeur. Voici un code d'exemple :

Voici le test avec la lettre 'g' :

Et le test avec un signe de ponctuation :

Récapitulatif sur l'utilisation de la bibliothèque ctype :
- On la déclare uniquement avec #include <ctype.h>
- Ses fonctions vérifient si un caractère contient un certain type. S'il ne contient pas le type en question, la fonction renvoit 0 et sinon elle renvoit un autre nombre.

Fonction
Vérifications
ispunct
ponctuations
isalpha
lettres, majuscules et minuscules
isupper
lettres majuscules
islower
lettres minuscules
isdigit
nombre décimal : de 0 à 9
isxdigit
nombre héxadécimal : 0 à 9, a à f et A à F
isspace
espace
iscntrl
caractères de contrôles
isalnum
lettres ou nombres
isprint
caractère imprimable ascii
isgraph
lettres, nombre ou ponctuation


Aller au tutorial suivant (tutorial 7) >>