#include <stdio.h>
int main(void)
{
printf("----------------------------------------\n");
printf(" Les opérateurs du C\n");
printf("----------------------------------------\n");
/* Rappel :
Les déclarations et définitions :
(variables, fonctions, types)
déclarent et définissent les objets que pourront manipuler le programme.
exemple : int i
Les expressions :
manipulent les déclarations, via les opérateurs.
exemple : i + 1
Les instructions :
manipulent les expressions pour leur donner une signification particulière.
(instruction simple, test, boucle, saut, ... etc.)
exemple : i = i + 1;
exemple : if (i > 0) printf("i est positif !\n");
Les opérateurs du C permettent de former des expressions.
La priorité (quel opérateur est appliqué avant, en l'absence de parenthèses
explicite) et l'associativité (dans quel ordre sont traités les arguments des
opérateurs ayant la même priorité) sont résumées dans la table suivante (par ordre
décroissant de priorité).
GD gauche vers la droite
DG droite vers la gauche
opérateur arité associativité description
( ) GD parenthésage
() [] . -> GD appel de fonction, index de tableau, membre de
structure, pointe sur membre de structure
! unaire DG négation booléenne
~ unaire DG négation binaire
++ -- unaire DG incrémentation et décrémentation
- unaire DG opposé
(type) unaire DG opérateur de transtypage (cast)
* unaire DG opérateur de déréférençage
& unaire DG opérateur de référençage
sizeof unaire DG fournit la taille en nombre de "char" de l'expression
(souvent en octet mais pas toujours, mais
sizeof(char) == 1 par définition, voir Caractères)
* / % binaire GD multiplication, division, modulo (reste de la
division)
+ - binaire GD addition, soustraction
>> << binaire GD décalages de bits
> >= < <= binaire GD comparaisons
== != binaire GD égalité/différence
& binaire GD et binaire
^ binaire GD ou exclusif binaire
| binaire GD ou inclusif binaire
&& binaire GD et logique avec séquencement
|| binaire GD ou logique avec séquencement
? : ternaire DG si...alors...sinon
= += -= *= /= %= ^= &= |= >>= <<= binaire DG affectation
, binaire GD séquencement
*/
printf("\n----------------------------------------\n");
printf(" Post/pré incrémentation/décrémentation\n");
printf("----------------------------------------\n");
/* L'incrémentation c'est écrire ++i ou i++ à la place de i = i + 1
Utilisé de manière préfixée, l'opérateur retourne la valeur incrémentée, tandis
qu'utilisé de manière postfixée l'opérateur retourne la valeur originale */
int i = 0, resultat;
resultat = ++i;
// resultat vaut 1 et i vaut 1
resultat = i++;
// resultat vaut 1 et i vaut 2
/* À noter que le langage ne garantit que la variable incrémentée n'aura sa nouvelle
valeur qu'une fois le prochain point de séquencement atteint (généralement la fin
d'une instruction). Ainsi, si l'objet sur lequel s'applique un tel opérateur
apparaît plusieurs fois avant un point de séquencement, alors le résultat est
imprévisible. */
i = 2;
i = i++;
/* le code est imprévisible
soit on fait d'abord i++ donc i = 3
puis i = 2 (++ postfixée l'opérateur retourne la valeur originale)
donc i vaut 2
soit on fait d'abord i = i++ soit i = 2
puis i++ soit i = 3
donc i = 3 */
i = 2;
#if 0
j = f(++i, 22, i);
/* idem le code est imprévisible
on fera j = f(3, 22, 2);
ou j = f(3, 22, 3); */
#endif
printf("\n----------------------------------------\n");
printf(" Promotion entière\n");
printf("----------------------------------------\n");
/* La promotion entière, à ne pas confondre avec la conversion automatique de type,
fait que tous les types plus petits ou égaux à int (char, short, champs de bits,
type énuméré) sont convertis (promus) en int ou unsigned int avant toute
opération. */
short sum(short a, short b)
{
return a + b;
/* équivaut à : return (int)a + (int)b;
c'est à dire conversion de a en int + la conversion de b en int */
}
/* Le compilateur peut émettre un avertissement du fait que le résultat int est
converti en short pour être retourné, et cette conversion peut causer une perte
de précision (par exemple si int a une largeur de 32 bits et short de 16 bits).
La promotion se fait vers unsigned int lorsqu'un int ne peut pas représenter le
type promu. */
printf("\n----------------------------------------\n");
printf(" Conversion automatique\n");
printf("----------------------------------------\n");
/* La conversion est un mécanisme qui permet de convertir implicitement les nombres
dans le format le plus grand utilisé dans l'expression. */
/* l'expression '2 / 3.' est de type double et vaudra effectivement deux tiers
(0,666666...) */
printf("2 / 3. = %f\n", 2 / 3.);
/* L'expression '2 * a / 3' calculera les deux tiers de la variable 'a', et
arrondira automatiquement à l'entier par défaut, les calculs ne faisant
intervenir que des instructions sur les entiers (en supposant que 'a' soit un
entier). */
int a = 2;
printf("2 * a / 3 = %d\n", 2 * a / 3);
/* l'expression '2 / 3 * a' vaudra... toujours zéro ! */
printf("2 / 3 * a = %d\n", 2 / 3 * a);
/* l'expression '2 * a / 3.', en supposant toujours que a soit un entier, effectuera
une multiplication entière entre 2 et a, puis promouvra le résultat en réel
(double) puis effectuera la division réelle avec trois, pour obtenir un résultat
réel lui-aussi. */
printf("2 * a / 3. = %f\n", 2 * a / 3.);
/* Enfin un cas où les promotions peuvent surprendre, c'est lorsqu'on mélange des
entiers signés et non-signés, plus particulièrement dans les comparaisons.
Considérez le code suivant : */
unsigned long b = 23;
signed char c = -23;
printf("\nb = %lu\n", b);
printf("c = %hhd\n", c);
printf( "b %c c\n", b < c ? '<' : (b == c ? '=' : '>') );
/* b < c ou b = c ou b > c
b < c ?(alors) '<' :(sinon) (b == c ?(alors) '=' :(sinon) '>') */
/* Ce code contient un effet de bord sibyllin(Enigmatique)
De toute évidence b est supérieur à c dans cet exemple. Et pourtant, si ce code
est exécuté sur certaines architectures, il affichera b < c, justement à cause de
la conversion automatique.
Tout d'abord dans la comparaison b < c, le type de b est « le plus grand », donc
c est promu en unsigned long. C'est en fait ça le problème : -23 est une valeur
négative, et la conversion d'une valeur négative en un type non signé se fait
modulo la valeur maximale représentable par le type non signé + 1. Si la valeur
maximale pour le type unsigned long est 2^32-1, alors -23 est converti modulo
2^32, et devient 4 294 967 273 (soit 2^32-23). Dans ce cas là effectivement
23 < 4 294 967 273, d'où ce résultat surprenant.
*/
// Dans ce cas présent, il faut effectuer soi-même le transtypage(la conversion) :
printf( "b %c c\n", (long)b < c ? '<' : ((long)b == c ? '=' : '>') );
/* on convertit b en long donc c sera lui converti automatiquement aussi en long
long peut contenir des nombres positifs et négatifs
donc -23 restera -23 en long et b sera plus grand que c */
printf("\n----------------------------------------\n");
printf(" Évaluation des expressions booléennes\n");
printf("----------------------------------------\n");
/* Le C ne possèdait pas de type booléen dédié. Dans ce langage, n'importe quelle
valeur différente de zéro est considérée vraie, zéro étant considéré comme faux.
Ce qui veut dire que n'importe quelle expression peut être utilisée à l'intérieur
des tests (entier, réels, pointeurs, tableaux, etc.). */
#if 0
// exemple :
int d;
d = une_fonction();
if (d)
{
// ...
}
// meme exemple en plus clair :
int d;
d = une_fonction();
if (d != 0)
{
// ...
}
// autre exemple :
int a = 0;
int b = 2;
if (a = b)
{
// Le code qui suit sera toujours exécuté ...
}
/* Dans cet exemple, il y a un seul caractère = entre a et b, donc on ne teste pas
si a est égal à b, mais on affecte la valeur de la variable b à la variable a. Le
résultat de l'expression étant 2, elle est donc toujours considérée vraie
Ce genre de raccourci est en général à éviter. En effet, il est facile de faire
une faute de frappe et d'oublier de taper un caratère =. Comme le code résultant
est toujours valide au sens du C, cette erreur peut ne pas être vue
immédiatement. Pour aider les développeurs à détecter ce genre d'erreurs, de
nombreux compilateurs émettent un avertissement quand un tel code leur est
présenté. Une manière de faire taire ces avertissements, une fois qu'on s'est
assuré qu'il ne s'agit pas d'une erreur, est de parenthéser l'affectation: */
if ((a = b))
{
// ...
}
// Ou mieux écrire
a = b;
if (a != 0)
{
// ...
}
/* Une autre technique classique, lorsqu'une comparaison fait intervenir une
constante, est de mettre la constante à gauche. De cette manière, si la
comparaison se transforme par mégarde en affectation, cela provoquera une erreur
à la compilation : */
if (0 == b)
{
// Une instruction "0 = b" ne passerait pas
}
/* Les opérateurs logiques de comparaisons (&& et ||, similaires sémantiquement à
leur équivalent binaire & et |) évaluent leurs opérandes en circuit court. Dans
le cas du ET logique (&&), si l'opérande gauche s'évalue à faux, on sait déjà
que le résultat du ET sera faux et donc ce n'est pas la peine d'évaluer
l'opérande droite. De la même manière si l'opérande gauche d'un OU logique (||)
est évalué à vrai, le résultat sera aussi vrai et donc l'évaluation de
l'opérande droite est inutile. Ceci permet d'écrire des expressions de ce genre,
sans générer d'exception de l'unité arithmétique du processeur : */
if (z != 0 && a / z < 10)
{
printf("Tout va bien\n");
}
#endif
return 0;
}