Partager l'article ! Tout sur le langage C++ (episode 12): Un outil à employer avec prudence : Les macros sont la source de nombreuses erreurs très diffic ...
Les macros sont la source de nombreuses erreurs très difficiles à repérer, puisqu’on ne dispose pas de la version étendue du code. Par exemple, on peut se demander pourquoi dans la macro
CARRE ci-dessus nous avons placé ces parenthèses. Mais si l’on écrit :
#define CARRE(x) x * x // ... j = CARRE(i+1);
la dernière ligne deviendra :
j = i+1 * i+1;
qui est interprété comme i + (1*i) +1, soit 2*i+1.
Même avec une définition correcte de CARRE, on peut avoir des surprises :
#define CARRE(x) (x)*(x) // ... int i = 3, j = CARRE(i++);
en sortie i vaut 5, et non 4, parce que la macro a été étendue sous la forme (i++)*(i++) et provoque deux incrémentations. Ce genre d’erreur est particulièrement ardu à
repérer.
D’une façon générale les macros sont dangereuses, car il n’y a aucun contrôle des types ; ainsi, si l’on utilise la macro AFFICHE définie ci-avant avec un paramètre non entier, on risque de sérieux problèmes.
En C++, les macros qui définissent des constantes diverses seront avantageusement remplacées par des déclarations de constantes :
const Pi = 3.141592; const Errmsg = "Une erreur s'est produite.n";
Les macros qui définissent de courtes actions, avec ou sans paramètres, seront remplacées par des fonctions en ligne :
inline long carre(long l) { return l*l; } inline double carre(double d) { return d*d; }
qui ne posent pas de problèmes, même avec des effets de bord, et qui vérifient les types de leurs paramètres.
Certaines fonctions en ligne sont d’ailleurs bien plus simples que les macros correspondantes. Essayez d’écrire la macro correspondant à :
inline void echange(int& i, int& j) { int k = i; i = j; j = k; } inline void echange(long& i, long& j) { long k = i; i = j; j = k; } inline void echange(double& i, double& j) { double k = i; i = j; j = k; }
Inversement, certaines macros ne peuvent pas être évitées. C’est le cas par exemple de AFFICHE dans la section
précédente, qui utilise le nom des variables. On peut toutefois la rendre plus sûre en utilisant les flots de sortie :
#define AFFICHE(x) cout << "Valeur de " #x " = " << ;;
qui peut alors fonctionner quel que soit le type de x.
Les classes génériques fournissent un autre exemple d’utilisation pratique des macros en C++.
Il n’y a pas, dans les premières versions de C++, de moyen de définir une classe générique, c’est-à-dire dépendant d’un paramètre comme le type d’un élément contenu dans la classe. On
peut toutefois le simuler en utilisant une macro. Dans les versions plus récentes, cette fonctionnalité a été ajoutée sous le nom anglais de template (en français,
gabarits).
Revenons à notre exemple de la liste chaînée (chapitre 6). Cette liste utilise une classe element qui peut
désigner n’importe quoi ; c’est caractéristique d’une classe générique.
Pour changer facilement le type d’élément de liste, il y a deux possibilités. La première consiste à créer un fichier séparé liste.h ne contenant pas la définition de
element, par exemple comme ceci (en simplifiant beaucoup la définition de la classe noeud) :
class noeud { noeud *suivt; element elm; public : noeud(element e, noeud *suivant = 0) { elm = e; suivt = suivant; } noeud *suivant(void) { return suivt; } element &contenu(void) { return elm; } };
Dans ce cas, avant d’inclure liste.h dans votre fichier, il faudra écrire la définition de element :
typedef double element; #include "liste.h"
par exemple. C’est la méthode que nous avons employée jusqu’à présent. Elle a toutefois l’inconvénient de ne permettre l’utilisation que d’un seul type de liste chaînée.
Une seconde méthode consiste à écrire les deux macros suivantes (dans un fichier séparé en général, que nous nommons encore liste.h) :
#include <generic.h> #define noeud(typ) _Paste2(noeud_, typ) #define listedeclare(typ) class noeud(typ) { noeud(typ) *suivt; typ elm; public : noeud(typ)(typ e, noeud(typ) *suivant = 0) { elm = e; suivt = suivant; } noeud(typ) *suivant(void) { return suivt; } typ &contenu(void) { return elm; } }
Le fichier <generic.h> contient un certain nombre de macros pour coller des éléments, dont voici les principales :
#define _Paste2(x, y) x##y // coller x et y ensemble #define declare(x, y) _Paste2(x, declare)(y) // déclarer l’objet x avec paramètre y #define implement(x, y) _Paste2(x, implement)(y) // définir l’objet x avec paramètre y
À présent, écrivons le programme suivant :
#include "liste.h" declare(liste, int); declare(liste, double); main() { noeud(int) *ni = new noeud(int)(0); noeud(double) *nd = new noeud(double)(3.14); // ... }
Ce programme sera transformé ainsi par le préprocesseur :
class noeud_int { noeud_int *suivt; int elm; public : noeud_int(int e, noeud_int *suivant = 0) { elm = e; suivt = suivant; } noeud_int *suivant(void) { return suivt; } int &contenu(void) { return elm; } }; class noeud_double { noeud_double *suivt; double elm; public : noeud_double(double e, noeud_double *suivant = 0) { elm = e; suivt = suivant; } noeud_double *suivant(void) { return suivt; } double &contenu(void) { return elm; } }; main() { noeud_int *ni = new noeud_int(0); noeud_double *nd = new noeud_double(3.14); // ... }
Avec nos clauses declare, on a donc en fait déclaré deux classes noeud_int et noeud_double. Les noms de ces classes peuvent être utilisés tels quels, ou
sous la forme de macros noeud(int) et noeud(double) qui donne l’illusion d’une classe générique. Notons qu’il faut toutefois effectivement une déclaration pour chaque
type utilisé, ce qui ne serait pas le cas avec une vraie classe générique comme il en existe dans certains langages comme ADA, ou les versions récentes de C++.
Les implémentations, lorsqu’il y en a (fonctions qui ne sont pas en ligne en particulier), seront définies dans une seconde macro listeimplement(typ) et on écrira
implement(typ) dans les fichiers ayant besoin de ces implantations.
On gagne ainsi une certaine facilité d’utilisation, moyennant un surcoût au moment de l’écriture des classes génériques.
Nous avons vu qu’avant d’utiliser une fonction il fallait la déclarer, mais pas forcément la définir. De ce fait, lorsque le compilateur rencontre un appel d’une fonction dont il ne connaît pas la définition, et donc pas l’adresse exacte, il crée une demande de lien entre l’appel et la fonction à joindre.
Lorsque la compilation proprement dite est terminée, l’éditeur de liens prend la relève ; en deux passes, il va réaliser les liens, c’est-à-dire trouver les fonctions dont on ne connaissait pas l’adresse et mettre cette dernière au bon endroit.
Pour cela l’éditeur de liens examine deux types de fichiers compilés : le ou les fichiers du projet courant, et ceux des librairies standard. S’il ne trouve pas la fonction qu’il cherche, il proteste en affichant un message d’erreur.
Notons que l’éditeur de liens exécute une tâche complexe, car il vérifie aussi la cohérence des déclarations multiples, et ne conserve que les fonctions réellement utilisées : les autres, quelle que soit leur provenance, sont éliminées, ce qui garantit un programme de taille (presque) minimale.
Une librairie est un regroupement de fichiers objets déjà compilés. Ces fichiers objets (*.obj ou *.o) sont ceux fournis par le compilateur.
Les fonctions standard sont implémentées dans un certain de librairies nommées xxx.lib. Les déclarations des routines sont reproduites dans des fichiers d’en-têtes
(*.h) ; ces fichiers sont très nombreux (39 en Turbo C++). Lorsqu’on désire utiliser des routines standard, on doit inclure un ou plusieurs de ces fichiers d’en-têtes dans le
fichier courant, comme on l’a déjà vu à plusieurs reprises. L’éditeur de liens se chargera d’aller retrouver l’implantation des routines dans les librairies.
Lorsqu’un programme devient volumineux, il est peu rentable de le placer dans un seul fichier : la compilation devient très longue, puisqu’il faut tout recompiler chaque fois, et il est difficile de s’y retrouver.
Pour simplifier alors le travail, on répartit les différentes routines dans plusieurs fichiers source (*.cpp). Chacun de ces fichiers peut alors être compilé indépendamment,
produisant un fichier objet (*.obj). L’ensemble des fichiers objets est ensuite regroupé par l’éditeur de liens pour former un programme exécutable unique (*.exe, sur
PC).
Dans la pratique, les différents fichiers doivent d’abord répondre à une certaine logique. Par exemple, on place fréquemment dans un fichier séparé une classe entièrement implémentée, ou deux ou trois si elles sont reliées. Il ne faut surtout pas répartir au hasard les fonctions dans plusieurs fichiers, car il serait vite très difficile de s’y retrouver.
Comme les fichiers doivent communiquer entre eux, il faut au moins un fichier en-tête qui leur soit commun. En pratique, on utilise même souvent un fichier en-tête pour chaque source, sauf celui
qui contient main ; en général on donne à ce fichier le même nom avec le suffixe *.h (mais ce n’est nullement obligatoire). Lorsqu’on utilise cette méthode très
fréquente, le fichier en-tête peut être considéré comme l’interface d’un module dont le fichier source est l’implantation. Dans ce fichier d’en-tête on placera toutes les déclarations (de
classes, de fonctions, de constantes, de variables, etc.) susceptibles d’être utilisées par les autres. On n’oubliera pas les fonctions en ligne, car le compilateur doit les connaître entièrement
pour les placer directement dans le code produit.
Dans le fichier source proprement dit, on trouvera généralement une directive d’inclusion du fichier en-tête correspondant, suivie éventuellement d’autres directives d’inclusion, soit pour les librairies standard, soit pour les autres fichiers en-têtes du même programme utilisés par le fichier courant. On trouvera ensuite l’implantation des fonctions qui ne sont pas en ligne.
Dans le fichier contenant main, on trouvera toutes les inclusions d’en-têtes nécessaires, suivies par main et éventuellement quelques fonctions étroitement liées à elle.
Prenons un exemple simple (et artificiel). Imaginons un programme faisant des calculs sur des matrices de fractions, implantées par une classe spéciale du même genre que la classe
liste, mais adaptée aux fractions. Un tel programme pourrait être réparti dans cinq modules différents :
fraction.cpp : Fichier contenant la classe fraction et les opérations sur elle.
listefra.cpp : Fichier contenant les classes noeud et liste nécessaires à la gestion de listes chaînées de fractions.
matrfra.cpp : Fichier contenant la classe matrice utilisant les listes de fractions, et les opérations sur elle.
iosfract.cpp : Fichier contenant diverses fonctions d’écriture et de lecture de fractions et de matrices.
mainfra.cpp : Fichier contenant main et la gestion de base du programme (commandes, etc.).
En réalité, les quatre premiers modules sont répartis chacun en un en-tête et un source. Voici l’allure qu’ils pourraient avoir :
#ifndef _FRACTION_H #define _FRACTION_H class fraction { // ....définition de la classe // avec fonctions en ligne éventuelles }; #endif
#include "fraction.h" // ici implantation des fonctions membres de // la classe fraction qui ne sont pas en ligne.
#ifndef _LISTEFRA_H #define _LISTEFRA_H #include "fraction.h" class noeud_fra; // définition dans listefra.cpp class liste_fra { // listes de fractions }; #endif
#include "fraction.h" class noeud_fra { // ...classe utilisée seulement par liste_fra }; // implantations des fonctions de noeud_fra et // liste_fra qui ne sont pas en ligne
#ifndef _MATRFRA_H #define _MATRFRA_H #include "fraction.h" class matrice_fra { // ... }; #endif
#include "listefra.h" #include "matrfra.h" // implantations des fonctions membres de matrice_fra // qui ne sont pas en ligne
#ifndef _IOSFRACT_H #define _IOSFRACT_H #include "matrfra.h" // déclarations de routines d’affichage et de lecture // de fractions et de matrices #endif
#include <iostream.h> #include "iosfract.h" // implantations des routines définies dans iosfract.h
#include "iosfract.h" main() { // ... }
Voilà notre programme bien silhouetté. On notera que chaque fichier n’inclut que les en-têtes dont il a besoin. Par exemple, les listes de fractions ne sont utilisées que par l’implantation des
matrices ; elles n’apparaissent donc que dans matrfra.cpp, et non dans matrfra.h ni dans les autres. De même, les flots d’entrées-sorties déclarés dans
<iostream.h> ne sont employés que par l’implantation des fonctions d’entrées-sorties des fractions et matrices.
On remarquera aussi une écriture classique dans les fichiers en-têtes, avec la clause #ifndef _XXX_H suivie par un #define _XXX_H. Ceci permet de n’inclure qu’une seule
fois un même fichier d’en-tête dans un fichier. Cela peut paraître inutile, mais remarquez que listefra.h et matrfra.h incluent tous deux fraction.h, et
sont tous deux inclus dans matrfra.cpp. Dès lors, si l’on oublie d’utiliser cette clause conditionnelle, c sera inclus deux fois dans c, ce qui non
seulement augmentera le temps de compilation mais aussi risque de provoquer une erreur car le compilateur n’acceptera pas que l’on définisse deux fois certains éléments du fichier (variables
globales, etc.).
On peut à présent compiler chacun de ces fichiers, soit séparément, soit ensemble (avec un projet, voir ci-après). On obtient alors sur disque cinq fichiers objets fraction.obj,
..., mainfra.obj. Reste à les relier entre eux, ainsi qu’à la librairie standard, pour obtenir le programme souhaité. Cela est fait par l’intermédiaire d’un
projet, que nous allons examiner à présent.
Continuant sur notre exemple, il va falloir indiquer à l’éditeur de liens de chercher les fonctions dans les cinq fichiers
objets créés. Il n’est pas suffisant de lancer la compilation pour obtenir le programme, car l’éditeur de liens ne trouvera pas les fonctions ; en effet, il faut absolument comprendre que
bien que les fichiers en-tête et source correspondants aient généralement le même nom (avec un suffixe différent), il n’en est pas forcément ainsi. De plus, l’inclusion des fichiers est faite par
le préprocesseur, l’éditeur de liens ne la connaît pas. Enfin, les directives d’inclusion ne suffisent pas à indiquer quels sont les fichiers objets effectivement utilisés ; par exemple, en
regardant mainfra.cpp, on pourrait croire qu’un seul fichier objet est nécessaire ; même en remontant les inclusions successives, on ne trouverait pas iosfract qui
est « caché » dans matrfra.cpp. Il est pourtant certain que les cinq fichiers objets doivent être utilisés par l’éditeur de liens.
Sur les système d'exploitations en ligne de commande, on gére un tel ensemble à l’aide d’un fichier de « fabrication » (*.make) géré par l’utilitaire make.
Dans les systèmes graphiques, les compilateurs intégrés comme Turbo C++ fournit un système plus simple nommé projet. Il suffit d’ouvrir une fenêtre de projet nommée par exemple
calcfra.prj et d’y inclure les cinq fichiers source *.cpp.
Lorsque tout y est, il ne reste plus qu’à lancer la commande adéquate (Make dans le cas de Turb C++). Voici alors ce que l’environnement intégré fait :
pour chaque fichier dans la fenêtre de projet il vérifie si une modification a été faite depuis la dernière compilation en comparant les dates et heures du fichier objet et des sources. Il examine aussi les modifications indirectes comme la modification d’un fichier en-tête inclus (directement ou non) ;
si une modification a été faite, le compilateur est appelé et recrée le fichier objet correspondant ;
une fois tous les fichiers objets à jour, l’éditeur de liens est appelé avec la liste de tous ces fichiers objets ; si aucune erreur ne se produit, un fichier exécutable est alors écrit sur disque. Ce fichier a le même nom que le projet par défaut, avec le suffixe exe (calcfra.exe dans notre exemple).
Le seul travail du programmeur consiste donc à ne pas oublier de fichier dans la fenêtre de projet.
La gestion de programmes ayant de multiples fichiers est considérablement facilitée par ce système. Le gain est évident dès que le programme dépasse quelques centaines de lignes (ce qui arrive très vite).
Par exemple, imaginons que l’on modifie un détail dans iosfract.cpp, puis que l’on relance un Make. Dans ce cas, seul le fichier iosfract.cpp sera
recompilé, et l’édition de liens sera refaite.
Si l’on modifie un fichier en-tête, les fichiers qui l’utilisent seront recompilés. Par exemple une modification de fraction.h provoquera la recompilation des cinq fichiers source,
car tous utilisent directement ou non ce fichier d’en-tête. Par contre, si l’on modifie listefra.h, seuls listefra.cpp et matrfra.cpp seront recompilés.
Un objet comme une fonction, une variable, etc., peut être automatique, dynamique, statique ou externe. Nous connaissons déjà les variables automatiques et dynamiques. Nous nous intéressons ici plutôt aux variables définies en dehors d’une fonction, et aux fonctions elles-mêmes.
Si l’on souhaite qu’un tel objet soit utilisable dans tous les fichiers du projet, il faut le déclarer externe, en utilisant le mot-clé extern. Si l’on préfère que
l’objet ne soit utilisable que pour les fonctions du fichier courant, il faut le déclarer statique avec le mot-clé static. Les fonctions, définitions de types et
variables sont externes par défaut, les constantes statiques.
Imaginons par exemple que le fichier iosfract.cpp ait besoin de partager avec le programme principal mainfra.cpp une variable globale glob (indiquant par
exemple la dernière erreur d’entrée-sortie produite). Dans ce cas, on n’écrira pas, dans le fichier iosfract.h :
int glob = 1;
sinon la variable serait dupliquée en deux exemplaires différents dans les deux fichiers objets (ce qui n’est pas le but recherché), et l’éditeur de liens signalerait une erreur. Il faut indiquer
explicitement au compilateur que la variable glob est partagée entre deux fichiers objets, et que c’est l’éditeur de liens qui s’en occupera. Il faut donc placer dans le fichier
d’en-tête une déclaration externe :
extern int glob;
sans initialisation, et dans l’un des deux fichiers objets (n’importe lequel) une déclaration statique avec initialisation (explicite ou implicite puisque les objets statiques sont toujours initialisés) de l’objet :
#include "iosfract.h" int glob = 1; // ...
Ainsi, chaque fois que l’on fait référence à glob dans iosfract.cpp, le compilateur sait que la variable est en fait ailleurs, et place un lien que l’éditeur de liens se
chargera de résoudre.
Lorsqu’un objet est externe, il ne doit être initialisé que dans le fichier objet qui le contient effectivement, sinon une erreur est produite. Précisons aussi qu’une variable peut être déclarée externe à l’intérieur d’une fonction, bien que ce soit d’un intérêt assez faible.
Déclarés en dehors d’une fonction, les variables et les types (y compris les classes) sont externes par défaut ; les fonctions également. Elles peuvent être déclarées statiques si elles ne sont utilisées que dans le fichier courant :
static void fonc(void) { ...
Dans ce cas, il est possible à plusieurs fichiers objets d’avoir une fonction nommée fonc sans entraîner de conflit, même si les fonctions sont différentes. En fait, l’éditeur de
liens ne connaîtra pas le nom de ces fonctions statiques, qui n’est utilisé que par le compilateur et effacé en fin de fichier. La déclaration statique est donc utile pour des fonctions, des
variables et des types qui n’ont pas à être utilisés ailleurs, car elle facilite le travail du compilateur et de l’éditeur de liens. Noter aussi qu’une fonction en ligne est inconnue de l’éditeur de liens (elle est toujours statique).
Les constantes sont statiques par défaut ; de la sorte, deux fichiers objets peuvent utiliser chacun leur version d’une constante sans problèmes. Cela permet aussi d’écrire une constante dans un fichier en-tête et d’inclure ce fichier dans plusieurs sources. Une constante peut être déclarée externe, si l’on souhaite l’initialiser ailleurs. Précisons toutefois que les tableaux constants (y compris les chaînes de caractères) ainsi que les classes et structures constantes sont, eux, externes par défaut (afin d’éviter une duplication de leur contenu dans les différents fichiers objets).
Les déclarations de types sont externes par défaut. On peut cependant les répéter dans plusieurs fichiers objets, à
condition que ce soit de manière identique (en pratique en passant par un fichier en-tête) ; c’est ce que nous avons fait avec les types fraction et matrice_fra
dans notre exemple.
D’une façon générale, on évitera de placer dans un fichier en-tête une définition de fonction (sauf en ligne), de variables (mais une déclaration externe est possible) et de tableaux, classes ou structures constants.
Les mots suivants sont réservés en C++ :
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||||
Les mots suivants sont de plus réservés en Turbo C++ :
cdecl _cs _ds _es _export far huge
interrupt _loadds near pascal _regparam
_saveregs _seg _ss
Les mots suivants sont réservés par d’autres compilateurs, il est préférable de ne pas les utiliser :
entry fortran handle _handle overload
Les liens renvoient aux sections où ces opérateurs sont décrits. L’ordre indiqué est celui de précédence, indiquée aussi dans la deuxième colonne ; les opérateurs de précédence 15 ont une priorité plus grande que ceux de précédence 14, etc. Les opérateurs de priorité égale sont traités de gauche à droite ou de droite à gauche selon le sens indiqué. La colonne Nb.Op. indique le nombre d’opérandes ; ceux-ci sont également évalués dans le sens indiqué. Une description plus détaillée se trouve aux sections indiquées par les liens dans la colonne des noms.
Les opérateurs qui ne peuvent être redéfinis sont sur fond orange.
|
Opérateur |
Préc. |
Sens |
Nb. Op. |
Nom |
Description |
|
|
15 |
-> |
varie |
Appelle la fonction dont le nom se trouve devant les parenthèses, avec les arguments contenus dans celles-ci. |
|
|
|
15 |
-> |
2 |
Renvoie un élément d’indice calculé entre crochets dans le tableau dont le nom se trouve devant ceux-ci. |
|
|
|
15 |
-> |
2 |
Déréférence le pointeur situé devant l’opérateur puis adresse le membre situé à droite. |
|
|
|
15 |
-> |
1 ou 2 |
En opérateur unaire, devant un identificateur, indique une variable globale. |
|
|
|
15 |
-> |
2 |
Indique la classe pour un pointeur sur membre. |
|
|
|
15 |
-> |
2 |
Adresse le membre dont le nom suit le point dans la classe dont le nom précède. |
|
|
|
14 |
<- |
1 |
Change un booléen en son opposé. |
|
|
|
14 |
<- |
1 |
Change tous les bits d’un entier en leur opposé. |
|
|
|
14 |
<- |
1 |
Aucun effet. |
|
|
|
14 |
<- |
1 |
Change un nombre en son opposé. |
|
|
|
14 |
<- |
1 |
Incrémente un entier ou un pointeur. Peut être placé devant ou derrière (actionavant ou après le reste des calculs). |
|
|
|
14 |
<- |
1 |
Décrémente un entier ou un pointeur. Peut être placé devant ou derrière (actionavant ou après le reste des calculs). |
|
|
|
14 |
<- |
1 |
Renvoie un référence sur l’identificateur dont le nom suit. |
|
|
|
14 |
<- |
1 |
Renvoie une référence sur la variable pointée par le pointeur dont le nom suit. |
|
|
|
14 |
<- |
1 |
Change le type de la variable située derrière la parenthèse fermante en celui indiqué entre parenthèses. |
|
|
|
14 |
<- |
1 |
Taille du type donné en argument, ou du type de la variable donnée en argument. |
|
|
|
14 |
<- |
1 |
Crée un bloc mémoire de taille adéquat pour stocker un objet, et renvoie un pointeur sur ce bloc. |
|
|
|
14 |
<- |
1 |
Détruit un bloc créé par |
|
|
|
13 |
-> |
2 |
Appel d’une méthode de classe par l’intermédiaire d’un pointeur de fonction. |
|
|
|
13 |
-> |
2 |
Déréférence le pointeur, puis appelle une méthode de classe par l’intermédiaire d’un pointeur de fonction. |
|
|
|
12 |
-> |
2 |
Multiplie les deux nombres. |
|
|
|
12 |
-> |
2 |
Divise le nombre de gauche par celui de droite. |
|
|
|
12 |
-> |
2 |
Donne le reste de la division euclidienne de l’entier de gauche par celui de droite. |
|
|
|
11 |
-> |
2 |
Additionne les deux nombres. |
|
|
|
11 |
-> |
2 |
Soustrait les deux nombres. |
|
|
|
10 |
-> |
2 |
Décale à droite les bits de l’entier précédent du nombre de bits indiqué par l’entier suivant l’opérateur, divisant ainsi par 2 puissance de cet entier. |
|
|
|
10 |
-> |
2 |
Décale à gauche les bits de l’entier précédent du nombre de bits indiqué par l’entier suivant l’opérateur, multipliant ainsi par 2 puissance de cet entier. |
|
|
|
9 |
-> |
2 |
Renvoie un booléen indiquant si le nombre de gauche est strictement inférieur à celui de droite. |
|
|
|
9 |
-> |
2 |
Renvoie un booléen indiquant si le nombre de gauche est inférieur ou égal à celui de droite. |
|
|
|
9 |
-> |
2 |
Renvoie un booléen indiquant si le nombre de gauche est strictement supérieur à celui de droite. |
|
|
|
9 |
-> |
2 |
Renvoie un booléen indiquant si le nombre de gauche est supérieur ou égal à celui de droite. |
|
|
|
8 |
-> |
2 |
Renvoie un booléen indiquant si les deux termes sont égaux. |
|
|
|
8 |
-> |
2 |
Renvoie un booléen indiquant si les deux termes sont différents. |
|
|
|
7 |
-> |
2 |
Applique l’opération logique ET sur les bits des opérandes entiers. Ne pas utiliser avec
les booléens (voir |
|
|
|
6 |
-> |
2 |
Applique l’opération logique OU EXCLUSIF sur les bits des opérandes entiers. |
|
|
|
5 |
-> |
2 |
Applique l’opération logique OU sur les bits des opérandes entiers. Ne pas utiliser avec
les booléens (voir |
|
|
|
4 |
-> |
2 |
ET logique entre deux booléens. Si le premier opérande est faux, le second n’est pas évalué. |
|
|
|
3 |
-> |
2 |
OU logique entre deux booléens. Si le premier opérande est vrai, le second n’est pas évalué. |
|
|
|
2 |
<- |
3 |
Évalue le booléen situé devant |
|
|
|
1 |
<- |
2 |
Calcule le terme de droite et place la valeur dans la variable désigné à gauche. Renvoie la valeur obtenue. |
|
|
|
1 |
<- |
2 |
Effectue l’opération dont le symbole précède le signe |
|
|
|
0 |
-> |
2 |
Évalue le terme de gauche, puis celui de droite et renvoie ce dernier. |
| Mai 2012 | ||||||||||
| L | M | M | J | V | S | D | ||||
| 1 | 2 | 3 | 4 | 5 | 6 | |||||
| 7 | 8 | 9 | 10 | 11 | 12 | 13 | ||||
| 14 | 15 | 16 | 17 | 18 | 19 | 20 | ||||
| 21 | 22 | 23 | 24 | 25 | 26 | 27 | ||||
| 28 | 29 | 30 | 31 | |||||||
|
||||||||||