1. C# langage phare de la plate forme .NET
Dans ce document nous comparons certaines attitudes de programmation
tenues par C# vis-à-vis de celles de ses parents virtuels Java et
Delphi, afin d'en signaler les apports et surtout les différences,
toutefois la connaissance des langages Delphi et Java n’est pas obligatoire
et en ce sens ce document doit alors être considéré comme
un livre de cours indépendant.
1. C# langage phare de la plate forme .NET
Une nouvelle stratégie de répartition de l'information
et de son traitement est proposée par microsoft, elle porte le nom
de .NET (ou en anglais dot net). La
conception de cette nouvelle architecture s'appuie sur quelques idées
fondatrices que nous énonçons ci-dessous :
L'outil Visual Studio .NET contient l'environnement RAD de développement
pour l'architecture .NET. Visual Studio .NET permet le développement
d'applications classiques Windows ou Internet.
1.1 La plate forme .NET Framework
Elle comporte plusieurs couches les unes abstraites, les autres en code exécutable :
La première couche CLS est composée
des specifications communes communes à tous les langages qui veulent
produire des applications .NET qui soient exécutables dans
cet environnement et les langages eux-même. Le CLS est une sorte de
sous-ensemble minimal de spécifications autorisant une interopérabilité
complète entre tous les langages de .NET les règles
minimales (il y en a en fait 41 ) sont :
Le C# est le langage de base de .NET, il correspond à une synthèse entre Delphi et Java (le concepteur principal de .NET. et de C# est l'ancien chef de projet Turbo pascal puis Delphi de Borland).
Afin de rendre Visual Basic interopérable sur .NET, il a été entièrement reconstruit par microsoft et devient un langage orienté objet dénommé VB.NET.
Les données sont accédées dans le cas des services Web à travers les protocoles qui sont des standards de l'industrie : HTTP, XML et SOAP.
La troisième couche est constituée d'une vaste librairie de plusieurs centaines de classes :
Toutes ces classes sont accessibles telles quelles à tous les langages de .NET et cette librairie peut être étendue par adjonction de nouvelles classes. Cette librairie a la même fonction que la bibliothèque des classes de Java.
La librairie de classe de .NET Framework est organisée en nom d'espace hierarchisés, exemple ci-dessous de quelques espaces de nom de la hiérarchie System :
Un nom complet de classe comporte le "chemin" hiérarchique de
son espace de nom et se termine par le nom de la classe exemples :
La quatrième couche forme l'environnement
d'exécution commun (CLR ou Common Language
Runtime) de tous les programmes s'exécutant dans l'environnement
.NET. Le CLR exécute un bytecode écrit dans un langage
intermédiaire (MSIL ou MicroSoft Intermediate
Language)
Rappelons qu'un ordinateur ne sait exécuter que des programmes écrits en instructions machines compréhensibles par son processeur central. C# comme pascal, C etc... fait partie de la famille des langages évolués (ou langages de haut niveau) qui ne sont pas compréhensibles immédiatement par le processeur de l'ordinateur. Il est donc nécesaire d'effectuer une "traduction" d'un programme écrit en langage évolué afin que le processeur puisse l'exécuter.
Les deux voies utilisées pour exécuter un programme évolué
sont la compilation ou l'interprétation :
Un compilateur du langage X pour un processeur P, est un logiciel qui traduit un programme source écrit en X en un programme cible écrit en instructions machines exécutables par le processeur P. |
Un interpréteur du langage X pour le processeur P, est un logiciel qui ne produit pas de programme cible mais qui effectue lui-même immédiatement les opérations spécifiées par le programme source. |
Un compromis assurant la portabilité d'un langage : une
pseudo-machine
Lorsque le processeur P n'est pas une machine qui existe physiquement mais un logiciel simulant (ou interprétant) une machine on appelle cette machine pseudo-machine ou p-machine. Le programme source est alors traduit par le compilateur en instructions de la pseudo-machine et se dénomme pseudo-code. La p-machine standard peut ainsi être implantée dans n'importe quel ordinateur physique à travers un logiciel qui simule son comportement; un tel logiciel est appelé interpréteur de la p-machine. |
La première p-machine d'un langage évolué a été
construite pour le langage pascal assurant ainsi une large diffusion
de ce langage et de sa version UCSD dans la mesure où le seul effort
d'implementation pour un ordinateur donné était d'écrire
l'interpréteur de p-machine pascal, le reste de l'environnement de
développement (éditeurs, compilateurs,...) étant écrit
en pascal était fourni et fonctionnait dès que la p-machine
était opérationnelle sur la plate-forme cible.
Donc dans le cas d'une p-machine le programme source est compilé, mais le programme cible est exécuté par l'interpréteur de la p-machine. |
Beaucoup de langages possèdent pour une plate-forme fixée
des interpréteurs ou des compilateurs, moins possèdent une
p-machine, Java de Sun est l'un de ces langages. Tous les langages de la
plateforme .NET fonctionnent selon ce principe, C# conçu par
microsoft en est le dernier, un programme C# compilé en p-code,
s'exécute sur la p-machine virtuelle incluse dans le CLR.
Nous décrivons ci-dessous le mode opératoire en C#.
Compilation native
La compilation native consiste en la traduction du source C# (éventuellement préalablement traduit instantanément en code intermédiare) en langage binaire exécutable sur la plate-forme concernée. Ce genre de compilation est équivalent à n'importe quelle compilation d'un langage dépendant de la plate-forme, l'avantage est la rapidité d'exécution des instructions machines par le processeur central. A ce jour la société Microsoft ne fournit pas un tel compilateur.
Programe source C# : xxx.cs
Programe exécutable sous Windows : xxx.exe
(code natif processeur)
Bytecode ou langage intermédiaire
La compilation en bytecode (ou pseudo-code ou p-code ou code intermédiaire) est semblable à l'idée du p-code de N.Wirth pour obtenir un portage multi plate-formes du pascal. Le compilateur C# de .NET Framework fourni par Microsoft, traduit le programme source xxx.cs en un code intermédiaire indépendant de toute machine physique et non exécutable directement, le fichier obtenu se dénomme PE (portable executable) et prend la forme : xxx.exe.
Seule une p-machine (dénommée machine virtuelle .NET) est capable d'exécuter
ce bytecode. Le bytecode est aussi dénommé MSIL. En fait le bytecode MSIL est pris en charge
par le CLR et n'est pas interprété par celui-ci mais traduit
en code natif du processeur et exécuté par le processeur
sous contrôle du CLR..
ATTENTION
Bien que se terminant par le suffixe exe, un programme issu d'une compilation sous .NET n'est pas un exécutable en code natif, mais un bytecode en MSIL; ce qui veut dire que vous ne pourrez pas faire exécuter directement sur un ordinateur qui n'aurait pas la machine virtuelle .NET, un programme PE "xxx.exe" ainsi construit . |
Ci-dessous le schéma d'un programme source Exemple.cs traduit par le compilateur C# sous .NET en un programme cible écrit en bytecode nommé Exemple.exe
Programe source C# : Exemple.cs
Programe exécutable sous .NET : Exemple.exe
(code portable IL )
Rappelons que le CLR (Common Language Runtime) est un environnement
complet d'exécution semblable au JRE de Sun pour Java, il est indépendant
de l'architecture machine sous-jacente. Le CLR prend en charge essentiellement
:
Une fois le programme source C# traduit en bytecode MSIL, la machine virtuelle du CLR se charge de l'exécuter sur la machine physique à travers son système d'exploitation (Windows, Unix,...)
Le CLR intégré dans l'environnement .NET est distribué gratuitement.
L'interprétation et l'exécution du bytecode ligne par ligne
pourrait prendre beaucoup de temps et cela a été semble-t-il
le souci de microsoft qui a adopté une stratégie d'optimisation
de la vitesse d'exécution du code MSIL en utilisant la technique
Just-in-time.
JIT (Just-in-time) est une technique de traduction dynamique durant l'interprétation. La machine virtuelle CLR contient un compilateur optimiseur qui recompile localement le bytecode MSIL afin de n'avoir plus qu'à faire exécuter des instructions machines de base. Le compilateur JIT du CLR compile une méthode en code natif dès qu'elle est appelée dans le code MSIL, le processus recommence à chaque fois qu'un appel de méthode a lieu sur une méthode non déjà compilée en code natif. |
On peut mentalement considérer qu'avec cette technique vous obtenez
un programme C# cible compilé en deux passages :
Tout est objet dans C#, en outre C# est un langage fortement typé.
Comme en Delphi et en Java vous devez déclarer un objet C# ou une
variable C# avec son type avant de l'utiliser. C# dispose de types valeurs
intrinsèques qui sont définis à partir des types
de base du CLS (Common Language Specification).
2.1 Les types valeurs du CLS dans .NET Framework
Les classes encapsulant les types élémentaires dans .NET Framework sont des classes de type valeur du genre structures. Dans le CLS une classe de type valeur est telle que les allocations d'objets de cette classe se font directement dans la pile et non dans le tas, il n'y a donc pas de référence pour un objet de type valeur et lorsqu'un objet de type valeur est passé comme paramètre il est passé par valeur.
Dans .NET Framework les classes-structures de type valeur sont déclarées comme structures et ne sont pas dérivables, les classes de type référence sont déclarées comme des classes classiques et sont dérivables.
Afin d'éclairer le lecteur prenons par exemple un objet x instancié à partir d'une classe de type référence et un objet y instancié à partir d'un classe de type valeur contenant les mêmes membres que la classe par référence. Ci-dessous le schéma d'allocation de chacun des deux objets :
En C# on aurait le genre de syntaxe suivant :
Déclaration de classe-structure
:
struct StructAmoi { |
instanciation :
StructAmoi y = new StructAmoi ( ) ;
|
Déclaration de classe :
class ClassAmoi { |
instanciation :
ClassAmoi x = new ClassAmoi ( ) ; |
public struct Menulang
{
public String MenuTexte;
public String Filtre;
public Menulang(String M, String s)
{
MenuTexte = M;
Filtre = s;
}
}
On instancie alors un objet de type valeur comme un objet de type référence. En reprenant l'exemple de la classe précédente on instancie et on utilise un objet Rec :
Menulang Rec = new Menulang ( Nomlang , FiltreLang );
Rec.MenuTexte = "Entrez" ;
Rec.Filtre = "*.ent" ;
Classe-structure | intervalle de variation | nombre de bits |
Boolean | false , true | 1 bit |
SByte | octet signé -128 ... +127 | 8 bits |
Byte | octet non signé 0 ... 255 | 8 bits |
Char | caractères unicode (valeurs de 0 à 65536) | 16 bits |
Double | Virgule flottante double précision ~ 15 décimales | 64 bits |
Single | Virgule flottante simple précision ~ 7 décimales | 32 bits |
Int16 | entier signé court -2^15-1...+2^15 | 16 bits |
Int32 | entier signé -2^31-1...+2^31 | 32 bits |
Int64 | entier signé long -2^63-1...+2^63 | 64 bits |
UInt16 | entier non signé court 0...+2^16-1 | 16 bits |
UInt32 | entier non signé 0...+2^32-1 | 32 bits |
UInt64 | entier non signé long 0...+2^64-1 | 64 bits |
Decimal | réeel = entier* 10^n (au maximum 28 décimales exactes) | 128 bits |
Compatibilité des types de .NET Framework
Le type System.Int32 qui le type valeur
entier signé sur 32 bits dans le CLS. Voici selon 4 langages de .NET Framework ( VB, C#, C++, J# ) la déclaration syntaxique du type Int32 : [Visual Basic] [C#] [C++] [J#] |
Les trois premières déclarations comportent syntaxiquement
le mot clef struct ou Structure indiquant le mode de gestion
par valeur donc sur la pile des objets de ce type. La dernière
déclaration en J# compatible syntaxiquement avec Java, utilise une
classe qui par contre gère ses objets par référence
dans le tas. C'est le CLR qui va se charger de maintenir une cohérence
interne entre ces différentes variantes; ici on peut raisonnablement
supposer que grâce au mécanisme d'emboîtage (Boxing)
le CLR allouera un objet par référence encapsulant l'objet
par valeur, mais cet objet encapsulé sera marqué comme objet-valeur.
Un type enum est un type valeur qui permet de déclarer un ensemble de constantes de base comme en pascal. En C#, chaque énumération de type enum, possède un type sous-jacent, qui peut être de n'importe quel type entier : byte, sbyte, short, ushort, int, uint, long ou ulong.
Le type int est le type sous-jacent par défaut des éléments
de l'énumération. Par défaut, le premier énumérateur
a la valeur 0, et l'énumérateur de rang n a la valeur
n-1.
Soit par exemple un type énuméré jour :
enum jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche}
par défaut : rang de lundi=0, rang de mardi=1, ... , rang de dimanche=6
1°) Il est possible de déclarer classiquement une variable du type jour comme un objet de type jour, de l'instancier et de l'affecter :
jour unJour = new jour ( );
unJour = jour.lundi ;
int rang = (int)unJour; // rang de la constante dans le type énuméré
System.Console.WriteLine("unJour = "+unJour.ToString()+" , place = '+rang);
Résultat de ces 3 lignes de code affiché sur la console :
unJour = lundi , place = 0
2°) Il est possible de déclarer d'une manière plus courte la même variable du type jour et de l'affecter :
jour unJour ;
unJour = jour.lundi ;int rang = (int)unJour;
System.Console.WriteLine("unJour = "+unJour.ToString()+" , place = '+rang);
Résultat de ces 3 lignes de code affiché sur la console :
unJour = lundi , place = 0
Remarque
C# accepte que des énumérations aient des noms de constantes d'énumérations identiques :
enum jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche}
enum weekEnd { vendredi, samedi, dimanche}
Dans cette éventualité faire attention, la comparaison de deux variables de deux types différents, affectées chacune à une valeur de constante identique dans les deux types, ne conduit pas à l'égalité de ces variables (c'est en fait le rang dans le type énuméré qui est testé). L'exemple ci-dessous illustre cette remarque :
enum jour { lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche}
enum weekEnd { vendredi, samedi, dimanche}
jour unJour ;
weekEnd repos ;
unJour = jour.samedi ;
repos = weekEnd.samedi;
if ( (jour)repos == unJour ) // il faut transtyper l'un des deux si l'on veut les comparer
System.Console.WriteLine("Le même jour");
else
System.Console.WriteLine("Jours différents");
Résultat de ces lignes de code affiché sur la console :
Jours différents
2.2 Syntaxe des types valeurs de C# et transtypage
Les types servent à déterminer la nature du contenu d'une
variable, du résultat d'une opération, d'un retour de résultat
de fonction. Ci-dessous le tableau de correspondance syntaxique entre les
types élémentaires du C# et les classes de .NET Framework
(table appelée aussi, table des alias) :
Types valeurs C# | Classe-structure de .NET Framework | nombre de bits |
bool | Boolean | 1 bit |
sbyte | SByte | 8 bits |
byte | Byte | 8 bits |
char | Char | 16 bits |
double | Double | 64 bits |
float | Single | 32 bits |
short | Int16 | 16 bits |
int | Int32 | 32 bits |
long | Int64 | 64 bits |
ushort | UInt16 | 16 bits |
uint | UInt32 | 32 bits |
ulong | UInt64 | 64 bits |
decimal | Decimal | 128 bits |
Rappellons qu'en C# toute variable qui sert de conteneur à une valeur d'un type élémentaire précis doit préalablement avoir été déclarée sous ce type.
Remarque importante
Une variable de type élémentaire en C# est (pour des raisons de compatibilité CLS) automatiquement un objet de type valeur (Par exemple une variable de type float peut être considérée comme un objet de classe Single). |
Il est possible d'indiquer au compilateur le
type d'une valeur numérique en utilisant un suffixe :
|
Exemples :
45l ou 45L représente la valeur 45 en entier signé sur
64 bits. 45f ou 45F représente la valeur 45 en virgule flottante simple précision sur 32 bits. 45d ou 45D représente la valeur 45 en virgule flottante double précision sur 64 bits. 5.27e-2f ou 5.27e-2F représente la valeur 0.0527 en virgule flottante simple précision sur 32 bits. |
Transtypage opérateur ( )
Les conversions de type en C# sont identiques pour les types numériques aux conversions utilisées dans un langage fortement typé comme Delphi par exemple. Toutefois C# pratique la conversion implicite lorsque celle-ci est possible. Si vous voulez malgré tout, convertir explicitement une valeur immédiate ou une valeur contenue dans une variable il faut utiliser l'opérateur de transtypage noté ( ). Nous nous sommes déjà servi de la fonctionnalité de transtypage explicite au paragraphe précédent dans l'instruction : int rang = (int)unJour; et dans l'instruction if ( (jour)repos == unJour )...
Transtypage implicte en C# :
Transtypage explicite en C# :
int x;
x = (int) y ; signifie que vous demandez de transtyper la valeur contenue dans la variable y en un entier signé 32 bits avant de la mettre dans la variable x.
2.3 Variables, valeurs, constantes
en C#
![]() |
Identificateurs de variables Déclarations et affectation de variables Les constantes en C# Base de représentation des entiers |
Comme en Java, une variable C# peut contenir soit une valeur d'un type élémentaire, soit une référence à un objet. Les variables jouent le même rôle que dans les langages de programmation classiques impératifs, leur visibilité est étudié dans le prochain chapitre.
Les identificateurs de variables en C# se décrivent comme ceux de tous les langages de programmation :
Attention C# fait une différence entre majuscules et minuscules, c'est à dire que la variable BonJour n'est pas la même que la variable bonjour ou encore la variable Bonjour. En plus des lettres, les caractères suivants sont autorisés pour construire une identificateur C# : "$" , "_" , "µ" et les lettres accentuées.
Exemples de déclaration de variables :
int Bonjour ; int µEnumération_fin$;
float Valeur ;
char UnCar ;
boolean Test ;
etc ...
Exemples d'affectation de valeurs à ces variables :
Affectation | Déclaration avec initialisation |
Bonjour = 2587 ; Valeur = -123.5687 UnCar = 'K' ; Test = false ; |
int Bonjour = 2587 ; float Valeur = -123.5687 char UnCar = 'K' ; boolean Test = false ; |
Exemple avec transtypage :
int Valeur ;
char car = '8' ;
Valeur = (int)car - (int)'0';
fonctionnement de l'exemple : |
C# dispose de deux mots clefs pour qualifier des variables dont le contenu ne peut pas être modifié : const et readonly sont des qualificateurs de déclaration qui se rajoutent devant les autres qualificateurs de déclaration..
- Les constantes qualifiées par const doivent être
initialisées lors de leur déclaration. Une variable membre de classe ou une variable locale à une méthode peut
être qualifiée en constante const. Lorsque de telles
variables sont déclarées comme variables membre de classe,
elles sont considérées comme des variables
de classe statiques :
- Les constantes qualifiées par readonly sont uniquement des variables membre
de classes, elles peuvent être initialisées dans le
constructeur de la classe (et uniquement dans le constructeur) :
Base de représentation des entiers
C# peut représenter les entiers dans 2 bases de numération
différentes : décimale (base 10), hexadécimale
(base 16). La détermination de la base de représentation d'une
valeur est d'ordre syntaxique grâce à un préfixe :
|
2.4 Priorité d'opérateurs en C#
Les opérateurs du C# sont très semblables à ceux de Java et donc de C++, ils sont détaillés par famille, plus loin . Ils sont utilisés comme dans tous les langages impératifs pour manipuler, séparer, comparer ou stocker des valeurs. Les opérateurs ont soit un seul opérande, soit deux opérandes, il n'existe en C# qu'un seul opérateur à trois opérandes (comme en Java) l'opérateur conditionnel " ? : ".
Dans le tableau ci-dessous les opérateurs de C# sont classés
par ordre de priorité croissante (0 est le plus haut niveau, 13 le
plus bas niveau). Ceci sert lorsqu'une expression contient plusieurs opérateurs
à indiquer l'ordre dans lequel s'effectueront les opérations.
Tableau général
de toutes les priorités
priorité | tous les opérateurs de C# |
0 | ( ) [ ] . new |
1 | ! ~ ++ -- |
2 | * / % |
3 | + - |
4 | << >> |
5 | < <= > >= is |
6 | = = != |
7 | & |
8 | ^ |
9 | | |
10 | && |
11 | || |
12 | ? : |
13 | = *= /= %= += -= ^= &= <<= >>= >>>= |= |
opérateurs travaillant avec des opérandes à valeur immédiate ou variable
Opérateur | priorité | action | exemples |
+ | 1 | signe positif | +a; +(a-b); +7 (unaire) |
- | 1 | signe négatif | -a; -(a-b); -7 (unaire) |
* | 2 | multiplication | 5*4; 12.7*(-8.31); 5*2.6 |
/ | 2 | division | 5 / 2; 5.0 / 2; 5.0 / 2.0 |
% | 2 | reste | 5 % 2; 5.0 %2; 5.0 % 2.0 |
+ | 3 | addition | a+b; -8.53 + 10; 2+3 |
- | 3 | soustraction | a-b; -8.53 - 10; 2-3 |
Ces opérateurs sont binaires (à deux opérandes) exceptés les opérateurs de signe positif ou négatif. Ils travaillent tous avec des opérandes de types entiers ou réels. Le résultat de l'opération est converti automatiquement en valeur du type des opérandes.
L'opérateur " % " de reste n'est intéressant que pour des calculs sur les entiers longs, courts, signés ou non signés : il renvoie le reste de la division euclidienne de 2 entiers.
Exemples d'utilisation de l'opérateur de division selon les
types des opérandes et du résultat :
programme C# | résultat obtenu | commentaire |
int x = 5 , y ; | x = 5 , y =??? | déclaration |
float a , b = 5 ; | b = 5 , a =??? | déclaration |
y = x / 2 ; | y = 2 // type int | int x et int 2 résultat : int |
y = b / 2 ; | erreur de conversion | conversion implicite
impossible (float b --> int y) |
y = b / 2.0 ; | erreur de conversion | conversion implicite
impossible (float b --> int y) |
a = b / 2 ; | a = 2.5 // type float | float b et int 2 résultat : float |
a = x / 2 ; | a = 2.0 // type float | int x et int 2 résultat : int conversion automatique int 2 --> float 2.0 |
a = x / 2f ; | a = 2.5 // type float | int x et float 2f résultat : float |
Pour l'instruction précédente " y = b / 2 " engendrant une erreur de conversion voici deux corrections possibles utilisant le transtypage explicite :
y = (int)b / 2 ; // b
est converti en int avant la division qui s'effectue sur deux int.
y = (int)(b / 2) ; // c'est le résultat
de la division qui est converti en int.
opérateurs travaillant avec une unique variable comme opérande
Opérateur | priorité | action | exemples |
++ | 1 | post ou pré incrémentation : incrémente de 1 son opérande numérique : short, int, long, char, float, double. |
++a; a++; (unaire) |
-- | 1 | post ou pré décrémentation : décrémente de 1 son opérande numérique : short, int, long, char, float, double. |
--a; a--; (unaire) |
L'objectif de ces opérateurs est l'optimisation de la vitesse d'exécution du bytecode MSIL dans le CLR et surtout la reprise aisée de code source C#et C++.
post-incrémentation : k++
la valeur de k est d'abord utilisée telle quelle dans l'instruction,
puis elle est augmentée de un à la fin. Etudiez bien les exemples
ci-après qui vont vous permettre de bien comprendre le fonctionnement
de cet opérateur.
Nous avons mis à côté de l'instruction C# les résultats des contenus des variables après exécution de l'instruction de déclaration et de la post incrémentation.
Exemple 1 :
int k = 5 , n ; n = k++ ; |
n = 5 | k = 6 |
Exemple 2 :
int k = 5 , n ; n = k++ - k ; |
n = -1 | k = 6 |
Exemple 3 :
int k = 5 , n ; n = k - k++ ; |
n = 0 | k = 6 |
Exemple 4 :Utilisation de l'opérateur de post-incrémentation
en combinaison avec un autre opérateur unaire.
int nbr1, z , t , u , v ;
nbr1 = 10 ; v = nbr1++ |
v = 10 | nbr1 = 11 |
nbr1 = 10 ; z = ~ nbr1 ; |
z = -11 | nbr1 = 10 |
nbr1 = 10 ; t = ~ nbr1 ++ ; |
t = -11 | nbr1 = 11 |
nbr1 = 10 ; u = ~ (nbr1 ++) ; |
u = -11 | nbr1 = 11 |
La notation " (~ nbr1) ++ " est refusée
par C#
remarquons que les expressions "~nbr1 ++ " et "~
(nbr1 ++)" produisent les mêmes effets, ce qui est logique puisque
lorsque deux opérateurs (ici ~ et ++ )ont la même priorité,
l'évaluation a lieu de gauche à droite.
pré-incrémentation : ++k
la valeur de k est d'abord augmentée de un ensuite utilisée
dans l'instruction.
Exemple1 :
int k = 5 , n ;
n = ++k ; | n = 6 | k = 6 |
Exemple 2 :
int k = 5 , n ; n = ++k - k ; |
n = 0 | k = 6 |
Exemple 3 :
int k = 5 , n ; n = k - ++k ; |
n = -1 | k = 6 |
post-décrémentation : k--
la valeur de k est d'abord utilisée telle quelle dans l'instruction,
puis elle est diminuée de un à la fin.
Exemple1 :
int k = 5 , n ;
n = k-- ; | n = 5 | k = 4 |
pré-décrémentation : --k
la valeur de k est d'abord diminuée de un, puis utilisée avec
sa nouvelle valeur.
Exemple1 :
int k = 5 , n ;
n = --k ; | n = 4 | k = 4 |
Reprenez avec l'opérateur - - des exemples semblables à
ceux fournis pour l'opérateur ++ afin d'étudier le fonctionnement
de cet opérateur (étudiez (- -k - k) et (k - - -k)).
Ces opérateurs employés dans une expression renvoient un
résultat de type booléen (false ou true). Nous
en donnons la liste sans autre commentaire car ils sont strictement identiques
à tous les opérateurs classiques de comparaison de n'importe
quel langage algorithmique (C, pascal, etc...). Ce sont des opérateurs
à deux opérandes.
Opérateur | priorité | action | exemples |
< | 5 | strictement inférieur | 5 < 2 ; x+1 < 3 ; y-2 < x*4 |
<= | 5 | inférieur ou égal | -5 <= 2 ; x+1 <= 3 ; etc... |
> | 5 | strictement supérieur | 5 > 2 ; x+1 > 3 ; etc... |
>= | 5 | supérieur ou égal | 5 >= 2 ; etc... |
= = | 6 | égal | 5 = = 2 ; x+1 = = 3 ; etc... |
!= | 6 | différent | 5 != 2 ; x+1 != 3 ; etc... |
Ce sont les opérateurs classiques de l'algèbre de boole { { V, F }, ! , & , | } où { V, F } représente l'ensemble {Vrai,Faux}. Les connecteurs logiques ont pour syntaxe en C# : ! , & , | , ^ :
& : { V, F } x { V,
F } ®{ V,
F } (opérateur binaire qui
se lit " et ")
| : { V, F } x { V,
F } ®{ V,
F } (opérateur binaire qui
se lit " ou ")
! : { V, F } ¾®{ V, F } (opérateur unaire qui se lit " non ")
^
: { V, F } x { V, F } ®{ V, F }
(opérateur binaire qui se lit " ou
exclusif ")
Table de vérité des opérateurs ( p et q étant
des expressions booléennes)
p q ! p p & q p | q p ^ q
V V F V V F
V F F F V V
F V V F V V
F F V F F F
Remarque :
"p
Î { V, F } , "q Î { V,
F } , p &q est toujours évalué en entier (
p et q
sont toujours évalués). "p Î { V, F } , "q Î { V, F } , p |q est toujours évalué en entier ( p et q sont toujours évalués). |
Opérateur et optimisé : &&
Théorème "q Î { V, F } , F &q = F |
Donc si p est faux (p = F) , il est inutile d'évaluer
q car l'expression p
&q est fausse (p &q = F), comme
l'opérateur &
évalue toujours l'expression q, C#
à des fin d'optimisation de la vitesse d'exécution du bytecode
MSIL dans le CLR , propose un opérateur ou noté && qui a la
même table de vérité que l'opérateur & mais qui applique
ce théorème.
"p
Î { V, F } , "qÎ { V, F
} , p &&q = p &q Mais dans p&&q , q n'est évalué que si p = V. |
Opérateur ou optimisé : | |
Théorème "q Î { V, F } , V |q = V |
Donc si p est vrai (p = V) , il est inutile d'évaluer
q car l'expression p
|q est vraie (p |q = V), comme
l'opérateur |
évalue toujours l'expression q, C#
à des fin d'optimisation de la vitesse d'exécution du bytecode
dans la machine virtuelle C# propose un opérateur ou noté
|| qui applique
ce théorème et qui a la même table de vérité
que l'opérateur |
.
"p
Î { V, F } , "q Î { V,
F } , p ||q = p |q Mais dans p||q , q n'est évalué que si p = F. |
En résumé:
Opérateur | priorité | action | exemples |
! | 1 | non booléen | ! (5 < 2) ; !(x+1 < 3) ; etc... |
& | 7 | et booléen complet | (5 = = 2) & (x+1 < 3) ; etc... |
| | 9 | ou booléen complet | (5 != 2) | (x+1 >= 3) ; etc... |
&& | 10 | et booléen optimisé | (5 = = 2) && (x+1 < 3) ; etc... |
|| | 11 | ou booléen optimisé | (5 != 2) || (x+1 >= 3) ; etc... |
Nous allons voir ci-après une autre utilisation des opérateurs
&et | sur des variables ou
des valeurs immédiates en tant qu'opérateur bit-level.
Ce sont des opérateurs de bas niveau en C# dont les opérandes
sont exclusivement l'un des types entiers ou caractère de C#
(short, int, long, char, byte). Ils permettent de manipuler directement
les bits du mot mémoire associé à la donnée.
Opérateur | priorité | action | exemples |
~ | 1 | complémente les bits | ~a; ~(a-b); ~7 (unaire) |
<< | 4 | décalage gauche | x << 3 ; (a+2) << k ; -5 << 2 ; |
>> | 4 | décalage droite avec signe | x >> 3 ; (a+2) >> k ; -5 >> 2 ; |
& | 7 | et booléen bit à bit | x & 3 ; (a+2) & k ; -5 & 2 ; |
^ | 8 | ou exclusif xor bit à bit | x ^ 3 ; (a+2) ^ k ; -5 ^ 2 ; |
| | 9 | ou booléen bit à bit | x | 3 ; (a+2) | k ; -5 | 2 ; |
Les tables de vérités de opérateurs "&", " | " et celle du ou exclusif " ^ " au niveau du bit sont identiques aux tables de verité booléennes ( seule la valeur des constantes V et F change, V est remplacé par le bit 1 et F par le bit 0)
Table de vérité des opérateurs bit level
p | q | ~ p | p & q | p | q | p ^ q |
1 | 1 | 0 | 1 | 1 | 0 |
1 | 0 | 0 | 0 | 1 | 1 |
0 | 1 | 1 | 0 | 1 | 1 |
0 | 0 | 1 | 0 | 0 | 0 |
Afin que le lecteur se familiarise bien avec ces opérateurs de bas niveau nous détaillons un exemple pour chacun d'entre eux. Afin de bien comprendre ces opérateurs, le lecteur doit bien connaître les différents codages des entiers en machine (binaire pur, binaire signé, complément à deux) car les entiers C# sont codés en complément à deux et la manipulation bit à bit necessite une bonne compréhension de ce codage.
Les exemples en 4 instructions C# : |
int i = -14 , j ;
j = ~ i ; // complémentation des bits
de i
--- ~ i --->
Tous les bits 1 sont transformés en 0 et les bits 0 en 1, puis
le résultat est stocké dans j qui contient la valeur 13 (car
000...01101 représente +13 en complément
à deux).
j = i >> 2 ; // décalage avec
signe de 2 bits vers la droite
--- i >> 2 --->
Tous les bits sont décalés de 2 positions vers la droite
(vers le bit de poids faible), le bit de signe (ici 1) est recopié
à partir de la gauche (à partir du bit de poids fort) dans
les emplacements libérés (ici le bit 31 et le bit 30), puis
le résultat est stocké dans j qui contient la valeur -4(car
1111...11100 représente -4 en complément
à deux).
j = i << 2 ; // décalage de 2 bits
vers la gauche
--- i << 2 --->
Tous les bits sont décalés de 2 positions vers la gauche
(vers le bit de poids fort), des 0 sont introduits à partir de la
droite (à partir du bit de poids faible) dans les emplacements libérés
(ici le bit 0 et le bit 1), puis le résultat est stocké dans
j contient la valeur -56(car 11...1001000
représente -56 en complément à deux).