Programmation C source/opérateurs

Programmation C source
Programmation C++
Programmation C++
Sommaire
Modifier ce modèle
#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;
}