Nous savons désormais déclarer, affecter et initialiser une variable, mais que diriez-vous d’apprendre à réaliser des opérations dessus ? Il est en effet possible de réaliser des calculs sur nos variables, comme les additionner, les diviser voire des opérations plus complexes. C’est le but de cette sous-partie. Nous allons donc enfin transformer notre ordinateur en grosse calculette programmable !
- Les opérations mathématiques de base
- Raccourcis
- Le type d'une constante
- Le type d'une opération
- Les conversions
- Exercices
Les opérations mathématiques de base
Jusqu’à présent, nous nous sommes contentés d’afficher du texte et de manipuler très légèrement les variables. Voyons à présent comment nous pouvons réaliser quelques opérations de base. Le langage C nous permet d’en réaliser cinq :
- l’addition (opérateur
+
) ; - la soustraction (opérateur
-
) ; - la multiplication (opérateur
*
) ; - la division (opérateur
/
) ; - le modulo (opérateur
%
).
Le langage C fournit bien entendu d’autres fonctions mathématiques et d’autres opérateurs, mais il est encore trop tôt pour vous les présenter.
Division et modulo
Les quatre premières opérations vous sont connues depuis l’école primaire. Cependant, une chose importante doit être précisée concernant la division : quand les deux nombres manipulés sont des entiers, il s’agit d’une division entière. Autrement dit, le quotient sera un entier et il peut y avoir un reste. Par exemple, 15 ÷ 6, ne donnera pas 2,5 (division réelle), mais un quotient de 2, avec un reste de 3.
printf("15 / 6 = %d\n", 15 / 6);
15 / 6 = 2
Le modulo est un peu le complément de la division entière : au lieu de donner le quotient, il renvoie le reste d’une division euclidienne. Par exemple, le modulo de 15 par 6 est 3, car 15 = 2 × 6 + 3.
printf("15 %% 6 = %d\n", 15 % 6);
15 % 6 = 3
Notez que le symbole %
doit être doublé afin de pouvoir être utilisé littéralement.
Avec des flottants, la division se comporte autrement et n’est pas une division avec reste. La division de deux flottants donnera un résultat « exact », avec potentiellement plusieurs chiffres après la virgule.
printf("15 / 6 = %f\n", 15. / 6.); /* En C, ce n’est pas la même chose que 15 / 6 */
15 / 6 = 2.500000
Utilisation
Il est possible d’affecter le résultat d’une opération à une variable, comme lorsque nous affichons leur résultat avec printf()
.
#include <stdio.h>
int main(void)
{
int somme = 5 + 3;
printf("5 + 3 = %d\n", somme);
return 0;
}
5 + 3 = 8
Toute opération peut manipuler :
- des constantes ;
- des variables ;
- les deux à la fois.
Voici un exemple avec des constantes.
#include <stdio.h>
int main(void)
{
printf("2 + 3 = %d\n", 2 + 3);
printf("8 - 12 = %d\n", 8 - 12);
printf("6 x 7 = %d\n", 6 * 7);
printf("11 %% 4 = %d\n", 11 % 4);
return 0;
}
2 + 3 = 5
8 - 12 = -4
6 x 7 = 42
11 % 4 = 3
Un autre avec des variables.
int a = 5;
int b = 3;
int somme = a + b;
printf("%d + %d = %d\n", a, b, somme);
5 + 3 = 8
Et enfin, un exemple qui mélange variables et constantes.
int a = 5;
int b = 65;
printf("%d\n", b / a * 2 + 7 % 2);
27
La priorité des opérateurs
Dans l’exemple précédent, nous avons utilisé plusieurs opérations dans une même ligne de code, une même expression. Dans ces cas-là, faites attention à la priorité des opérateurs ! Comme en mathématiques, certains opérateurs passent avant d’autres : les opérateurs * / %
ont une priorité supérieure par rapport aux opérateurs + -
.
Dans le code ci-dessous, c’est c * 4
qui sera exécuté d’abord, puis b
sera ajouté au résultat. Faites donc attention sous peine d’avoir de mauvaises surprises. Dans le doute, ajoutez des parenthèses.
a = b + c * 4;
Raccourcis
Dans les exemples précédents, nous avons utilisé des affectations pour sauvegarder le résultat d’une opération dans une variable. Les expressions obtenues ainsi sont assez longues et on peut se demander s’il existe des moyens pour écrire moins de code. Hé bien, le langage C fournit des écritures pour se simplifier la vie. Certains cas particuliers peuvent s’écrire avec des raccourcis, du « sucre syntaxique ».
Les opérateurs combinés
Comment vous y prendriez-vous pour multiplier une variable par trois ? La solution qui devrait vous venir à l’esprit serait d’affecter à la variable son ancienne valeur multipliée par trois.
int variable = 3;
variable = variable * 3;
printf("variable * 3 = %d\n", variable);
variable * 3 = 9
Ce qui est parfaitement correct. Cependant, cela implique de devoir écrire deux fois le nom de la variable, ce qui est quelque peu pénible et source d’erreurs. Aussi, il existe des opérateurs combinés qui réalisent une affectation et une opération en même temps.
Opérateur combiné | Équivalent à |
---|---|
variable += nombre | variable = variable + nombre |
variable -= nombre | variable = variable - nombre |
variable *= nombre | variable = variable * nombre |
variable /= nombre | variable = variable / nombre |
variable %= nombre | variable = variable % nombre |
Avec le code précédent, nous obtenons ceci.
int variable = 3;
variable *= 3;
printf("variable * 3 = %d\n", variable);
variable * 3 = 9
L’incrémentation et la décrémentation
L’incrémentation et la décrémentation sont deux opérations qui, respectivement, ajoute ou enlève une unité à une variable. Avec les opérateurs vus précédemment, cela se traduit par le code ci-dessous.
variable += 1; /* Incrémentation */
variable -= 1; /* Décrémentation */
Cependant, ces deux opérations étant très souvent utilisées, elles ont droit chacune à un opérateur spécifique disponible sous deux formes :
- une forme préfixée ;
- une forme suffixée.
La forme préfixée s’écrit comme ceci.
++variable; /* Incrémentation */
--variable; /* Décrémentation */
La forme suffixée s’écrit comme cela.
variable++; /* Incrémentation */
variable--; /* Décrémentation */
Le résultat des deux paires d’opérateurs est le même : la variable variable
est incrémentée ou décrémentée, à une différence près : le résultat de l’opération.
- Dans le cas de l’opérateur préfixé (
--variable
ou++variable
), le résultat sera la valeur de la variable augmentée ou diminuée d’une unité. - Dans le cas de l’opérateur suffixé (
variable--
ouvariable++
), le résultat sera la valeur de la variable.
Illustration !
#include <stdio.h>
int main(void)
{
int x = 1;
int y = 1;
int a = x++;
int b = ++y;
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("x = %d\n", x);
printf("y = %d\n", y);
return 0;
}
a = 1
b = 2
x = 2
y = 2
Comme vous pouvez le constater, le résultat de l’opération x++
est 1
alors que celui de ++y
est 2. Cela étant, dans les deux cas, les variables x
et y
ont bien été incrémentées.
Le type d'une constante
Dans les sections précédentes, nous avons vu plusieurs opérations mathématiques et avons affiché leur résultat. Cependant, peut-être vous êtes-vous posé la question suivante : quel est le type du résultat ?
En effet, nous avons par exemple écrit printf("2 + 3 = %d\n", 2 + 3)
, ce qui laisse supposer que le résultat de 2 + 3
est de type int
, sans finalement en savoir plus.
En fait, le type du résultat d’une opération dépend de celui de ses opérandes. Nous allons voir cela en détail un peu après, mais par exemple si les opérandes sont de type int
, le résultat sera de type int
.
Toutefois, cela amène une autre question : de quel type sont les constantes comme 2
ou 3
?
Les constantes entières
Le type des constantes entières, par défaut, dépend de leur valeur et, de manière plus étonnante, de leur base.
Base | Type |
---|---|
10 |
|
| |
| |
8 ou 16 |
|
| |
| |
| |
| |
|
Ce tableau doit être compris comme suit :
- Pour un nombre en base 10 (comme
2
), le premier type pouvant stocker la valeur entre les typesint
,long
etlong long
sera choisi ; - Pour un nombre en base 8 ou 16 (comme
0777
ou0x16
), le premier type pouvant stocker la valeur entre les typesint
,unsigned
,long
,unsigned long
,long long
etunsigned long long
sera choisi.
Si aucun de ces types ne peut stocker la valeur indiquée, le comportement des compilateurs varie. En général, ils généreront une erreur de compilation.
Ces règles par défaut sont utiles, mais pas forcément pratiques. En effet, rappelez-vous : les valeurs minimale et maximale d’un type varient d’une machine à l’autre (revoyez le chapitre sur les variables si cela ne vous dit rien), ce qui signifie que le type d’une constante peut varier d’une machine à l’autre.
Il serait préférable de se baser sur les minimums garantis par la norme et de pouvoir fixer le type au regard de ceux-ci. Heureusement pour nous, cela est possible à l’aide de suffixes. Il en existe trois pour les constantes entières.
Suffixe | Type |
---|---|
|
|
| |
| |
|
|
| |
|
|
| |
|
|
|
|
Notez que suivant le cas, le suffixe ne fixe pas un type précis, mais une liste de types. Par exemple, le suffixe U
précise que le type sera unsigned
ou unsigned long
ou unsigned long long
. Dans un tel cas, le type effectivement utilisé sera le premier pouvant stocker la valeur de la constante.
Ainsi, la constante 1UL
sera par exemple de type unsigned long
, alors qu’elle aurait été de type int
par défaut.
Nous vous conseillons d’opter pour les lettres majuscules qui ont l’avantage d’être plus lisibles.
Les constantes flottantes
Pour les constantes flottantes, la règle est simple : elles sont par défaut de type double
.
Toutefois, comme pour les constantes entières, il est possible de fixer le type à l’aide de suffixes. Il en existe deux pour les constantes flottantes.
Suffixe | Type |
---|---|
f ou F | float |
l ou L | long double |
Ainsi, la constante 1.L
sera de type long double
alors qu’elle aurait été de type double
par défaut.
Si le type choisi ne peut stocker la valeur indiquée, le comportement des compilateurs varie. En général, ils généreront un avertissement.
N’oubliez pas le .
sans quoi la constante sera considérée comme entière.
Comme pour les constantes entières, nous vous conseillons d’opter pour les lettres majuscules qui ont l’avantage d’être plus lisibles.
Le type d'une opération
Dans la section précédente, nous vous avons dit que le type d’une opération, dit autrement le type du résultat d’une opération, dépendait de celui de ses opérandes. Or, si c’est évident dans le cas où les opérandes ont le même type, cela ne l’est pas dans le cas où leurs types sont différents.
Par exemple, quel est le type de l’opération 2 + 1.2
? Intuitivement on serait tenté de choisir un type flottant pour conserver la partie décimale, mais sans règles spécifiques, c’est impossible à dire.
Heureusement pour nous, la norme a prévu ces différents cas et a fixé des règles1 qui sont appliquées avant que l’opération n’ait lieu :
- si l’un des deux opérandes est de type
long double
, l’autre opérande est converti enlong double
; - si l’un des deux opérandes est de type
double
, l’autre opérande est converti endouble
; - si l’un des deux opérandes est de type
float
, l’autre opérande est converti enfloat
; - si l’un des deux opérandes est de type
unsigned long long
, l’autre opérande est converti enunsigned long long
; - si l’un des deux opérandes est de type
long long
, l’autre opérande est converti enlong long
; - si l’un des deux opérandes est de type
unsigned long
, l’autre opérande est converti enunsigned long
; - si l’un des deux opérandes est de type
long
, l’autre opérande est converti enlong
; - si l’un des deux opérandes est de type
unsigned int
, l’autre opérande est converti enunsigned int
.
Si vous êtes attentifs, vous remarquerez qu’il y a 3 types oubliés dans cette liste : short
, char
et _Bool
. C’est parce que pour ces derniers, une règle supplémentaire s’applique avant celles exposées précédemment : si l’un des opérandes est de type short
(signé ou non), char
(signé ou non), ou _Bool
, il est converti en int
(ou en unsigned int
si le type int
n’a pas une capacité suffisante). Cette règle est appelée la promotion intégrale2 (ou promotion entière).
Dit autrement, si aucune des huit premières règles ne s’applique, les opérandes sont nécessairement de types int
, via la règle de promotion intégrale ou non.
Notez que la promotion intégrale explique pourquoi l’indicateur de conversion de printf()
reste d
(ou u
) pour les types short
, char
et _Bool
.
Voyons un peu tout ça à l’aide d’un exemple.
#include <stdio.h>
int main(void)
{
/* long double + int = long double */
printf("78.56 + 5 = %Lf\n", 78.56L + 5);
/* long + double = double */
printf("5678 + 2.2 = %f\n", 5678L + 2.2);
/* long + unsigned long = unsigned long */
printf("2 + 5 = %lu\n", 2L + 5UL);
/* long long + int = long long */
printf("1 + 1 = %lld\n", 1LL + 1);
/*
* Promotion intégrale de n qui devient de type int.
* Puis int + int = int
*/
signed char n = 3;
printf("n + 2 = %d\n", n + 2);
return 0;
}
78.56 + 5 = 83.560000
5678 + 2.2 = 5680.200000
2 + 5 = 7
1 + 1 = 2
n + 2 = 5
Les conversions
Nous venons de le voir, dans le cadre d’opérations, des conversions peuvent avoir lieu d’un type à l’autre. Ces conversions sont appelées des conversions implicites, car elles sont effectuées automatiquement. Il en existe beaucoup en langage C. Par exemple, lors d’une affectation, il y a également une conversion implicite vers le type de la variable assignée.
int a = 2.; /* Conversion implicite du type double vers le type int. */
Il est toutefois également possible de demander explicitement une conversion. Pour convertir un opérande vers un type donné, il suffit de le préfixer par le type désiré placé entre parenthèses. Le code ci-dessous convertit la constante 2
vers le type double
et l’affiche ensuite.
#include <stdio.h>
int
main(void)
{
printf("%f\n", (double)2);
return 0;
}
2.000000
Une conversion explicite permet également d’imposer le résultat d’une opération en fixant ou en induisant le type de ses opérandes. Ainsi, l’exemple qui suit utilise des conversions explicites pour effectuer une division avec des nombres flottants.
#include <stdio.h>
int
main(void)
{
int a = 5;
int b = 2;
/*
* b est converti en double.
* Comme b est de type double, a est converti implicitement en double.
*/
printf("%f\n", a / (double)b);
/* a et b sont explicitement convertis en double. */
printf("%f\n", (double)a / (double)b);
return 0;
}
2.500000
2.500000
Exercices
Vous êtes prêts pour un exercice ?
Essayez de réaliser une minicalculatrice qui :
- dit « bonjour » ;
- demande deux nombres entiers à l’utilisateur ;
- les additionne, les soustrait, les multiplie et les divise (au millième près) ;
- dit « au revoir ».
Un exemple d’utilisation pourrait être celui-ci.
Bonjour !
Veuillez saisir le premier nombre : 4
Veuillez saisir le deuxième nombre : 7
Calculs :
4 + 7 = 11
4 - 7 = -3
4 * 7 = 28
4 / 7 = 0.571
Au revoir !
Bien, vous avez maintenant toutes les cartes en main, donc : au boulot !
#include <stdio.h>
int main(void)
{
int a;
int b;
printf("Bonjour !\n");
/* Nous demandons deux nombres à l'utilisateur */
printf("Veuillez saisir le premier nombre : ");
scanf("%d", &a);
printf("Veuillez saisir le deuxième nombre : ");
scanf("%d", &b);
/* Puis nous effectuons les calculs */
printf("Calculs :\n");
printf("\t%d + %d = %d\n", a, b, a + b);
printf("\t%d - %d = %d\n", a, b, a - b);
printf("\t%d * %d = %d\n", a, b, a * b);
printf("\t%d / %d = %.3f\n", a, b, a / (double)b);
printf("Au revoir !\n");
return 0;
}
Vous y êtes arrivé sans problèmes ? Bravo ! Dans le cas contraire, ne vous inquiétez pas, ce n’est pas grave. Relisez bien tous les points qui ne vous semblent pas clairs et ça devrait aller mieux.
Dans le chapitre suivant, nous nous pencherons sur les conditions.
En résumé
- Le C fournit des opérateurs pour réaliser les opérations mathématiques de base ;
- Dans le cas où une division est effectuée entre des nombres entiers, le quotient sera également un entier ;
- La priorité des opérateurs est identique à celle décrite en mathématiques ;
- Le C fournit des opérateurs combinés et des opérateurs pour l’incrémentation et la décrémentation afin de gagner en concision ;
- Le type d’une constante peut-être modifié à l’aide de suffixes ;
- En cas de mélange des types lors d’une opération, le C prévoit des règles de conversions ;
- Il est possible de convertir un opérande d’un type vers un autre, certaines conversions sont implicites.