La manipulation de données est le cœur de la programmation. Elle est principalement basée sur des opérateurs (+ - * etc.) définis dans le noyau du langage par des processus fondamentaux qui sont mis en œuvre par l'architecture matérielle de la machine programmée (mémoire, unité arithmétique et logique, etc.) et son système d'exploitation.
En plus des opérateurs, les langages de programmation fournissent également des bibliothèques de fonctions, notamment les fonctions mathématiques, pour effectuer des opérations plus complexes sur des données de toutes sortes (booléennes, scalaires, vectorielles, etc.).
En programmation impérative de haut niveau (cf. chap C1‑I ), donc en particulier avec les langages C et C++, on dispose aussi d'une opération essentielle pour la manipulation des données : l'affectation d'une valeur à une donnée. Et bien entendu, on a la possibilité de coder cette valeur par une expression calculatoire qui sera évaluée par la machine lors de l'exécution de l'expression d'affectation.
Ce chapitre est absolument essentiel dans l'acquisition des éléments de langage de base. Il a pour objectifs :
- de présenter en détail l'opérateur d'affectation en raison de la place primordiale qu'il tient, et aussi de sa complexité insoupçonnée (du moins, pour un débutant) ;
- de dresser un panorama des différents opérateurs élémentaires et structurels, sachant que tous ces opérateurs seront revus en détail dans les parties C3 à C5 du cours ;
- d'introduire quelques fonctions mathématiques utiles pour le codage des premiers programmes, sachant que la notion de fonction sera pleinement étudiée dans la partie C4 du module.
Affectations de valeurs
Principe
Une affectation W – en anglais assignment – consiste à attribuer une valeur à une donnée, que cette dernière soit variable ou constante.
Dans le cas d'une constante, l'affectation ne peut être codée que dans le cadre de sa déclaration. On parle alors spécifiquement d'initialisation.
L'affectation est l'opération cruciale qui caractérise le paradigme de la programmation impérative car toute affectation d'une nouvelle valeur à une variable opère un changement irréversible dans les données du programme :
- certes, cette nouvelle valeur est mémorisée,
- cependant, l'ancienne valeur est définitivement perdue – on dit qu'elle est « écrasée ».
Très commode, ce changement irréversible présente néanmoins l'inconvénient majeur de rendre impossible toute démonstration mathématique du bon fonctionnement d'un programme impératif.
Cette impossibilité explique pourquoi les langages fonctionnels – qui, eux, ne recourent pas à l'affectation – sont parfois préférés aux langages impératifs dans certaines applications qui nécessite une très grande sûreté de fonctionnement.
En langages C et C++, l'affectation possède un mode opératoire complexe avec une particularité syntaxique qui ne manque pas de surprendre des codeurs débutants : le symbole = code un opérateur, au même titre que tout autre opérateur comme l'addition, la soustraction, etc.
Syntaxe – notions de r‑value et l‑value
Une affectation est une expression dont le schéma syntaxique est :
l‑value = r‑value
où les deux expressions l‑value (« l » pour left) et r‑value (« r » pour right) doivent être distinguées car l'opérateur d'affectation = n'est pas commutatif.
Dans ce schéma syntaxique, on peut apporter les précisions suivantes.
- l‑value est une expression adressable dont la valeur est mutable, c'est‑à‑dire dont l'évaluation détermine implicitement une adresse en mémoire susceptible de changer au cours de l'exécution du programme.
- r‑value est une expression évaluable au moment de l'exécution, dont le type doit être compatible avec celui de l‑value ; dans le cas contraire, selon le « degré » d'incompatibilité, leur compilateur peut soit juste émettre un avertissement, soit identifier une erreur et abandonner la compilation.
- L'opérateur d'affectation possède des propriétés particulières (cf. infra pour plus de détails) :
- il a le rang de priorité le plus faible de tous les opérateurs, à l'exception de l'opérateur séquentiel ;
- son associativité procède de droite à gauche.
Pour fixer les idées, en supposant que a est une variable entière préalablement déclarée, on peut coder une affectation comme par exemple dans l'instruction :
a = 5;
En revanche, on ne peut pas coder une instruction comme :
5 = a;
car 5 n'ayant pas d'espace mémoire spécifiquement réservé, n'est pas une l‑value (on ne peut pas affecter une valeur à 5).
- Il faut ne pas confondre une affectation avec une initialisation au sein d'une déclaration de donnée, qui peut sembler similaire. En effet, une initialisation obéit à certaines règles spécifiques (cf. chap. C2‑III ).
- Aux yeux d'un débutant, la notion de l‑value telle qu'elle est décrite supra pourrait sembler abstraite et inutilement complexe. Néanmoins, cette notion présente l'intérêt de s'appliquer à des expressions qui ne sont pas simplement des identificateurs de variable, mais aussi des pointeurs où même des expressions composées à base de pointeurs (cf. chap. C5‑II ).
Évaluation d'une expression d'affectation
L'évaluation d'une expression d'affectation de la forme l‑value = r‑value procède par les étapes listées ci‑dessous, dans l'ordre :
- l'évaluation de l'expression r‑value ;
- l'évaluation de l'expression l‑value ;
- l'enregistrement de la valeur de r‑value dans l'espace mémoire alloué auquel la l‑value s'adresse :
La valeur retournée par l'expression d'affectation est la nouvelle valeur affectée à l‑value.
- L'instruction :
newVoltage = 7.5;
se réduit à la forme la plus simple d'affectation qui soit (une variable à laquelle on donne une nouvelle valeur numérique) : - si
newVoltageest une variable déclarée dans un type décimal commefloatqui permet de stocker en intégralité à la valeur7.5, l'éventuelle conversion implicite est sans conséquence ; - en revanche, si
newVoltageest une variable déclarée dans un type entier, la valeur qui lui est affectée est7par conversion implicite avec perte d'information. - L'instruction :
newVoltage = nominalVoltage / 2;
code une affectation très simple. La r‑value est l'expressionnominalVoltage / 2. Son évaluation est effectuée en premier, la valeur obtenue étant ensuite affectée à la l‑valuenewVoltageselon le même processus que pour l'exemple précédent. - L'instruction :
newVoltage = previousVoltage = 12.0;
code deux affectations enchaînées de droite à gauche, conformément au sens d'associativité de l'opérateur=: - d'abord l'affectation
previousVoltage = 12.0dont l'évaluation rend la valeur12.0(si la variablepreviousVoltageest déclarée de type décimal) ; - puis l'affectation à
newVoltagede cette valeur rendue12.0.
newVoltage = 7.5 (7.5 dans le premier cas, 7 dans le deuxième) n'est pas exploitée. Bonne pratique
En général, l'« astuce » de codage de l'exemple 3) supra n'apporte un gain significatif en lisibilité que pour des variables avec des identificateurs courts. Sinon, elle est à éviter, en vertu du principe :
« une affectation, une instruction (sur une ligne distincte). »
Dans l'exemple 3) supra, il est préférable de coder :
previousVoltage = 12.0; newVoltage = previousVoltage;
Notion générale de l‑value
La notion de l‑value telle que définie supra (une « expression adressable » dont la valeur est mutable) peut sembler inutilement complexe. En effet, dans tous les exemples donnés jusqu'à présent, elle se réduit juste à un identificateur de variable.
Mais la l‑value peut être aussi une expression composée, notamment pour déterminer :
- un élément de tableau, typiquement de la forme
nom de tableau[expression]; - une variable pointée, typiquement de la forme
*nom de variable.
Ces aspects seront revus en détails dans la partie C5 du cours.
Opérateurs
Généralités
En programmation, un opérateur W – en anglais, operator – est une fonction d'usage très courant qui :
- est intégrée au noyau du langage (et non pas dans un module de bibliothèque) ;
- bénéficie le plus souvent d'une syntaxe symbolique facilitant le codage – et donc aussi la lecture – des expressions.
On appelle opérandes les expressions auxquelles un opérateur s'applique (le terme « argument » est préférentiellement employé pour les fonctions).
On dit d'un opérateur qu'il est unaire, binaire ou ternaire s'il admet respectivement 1, 2 ou 3 opérandes.
Faire la somme de deux données numériques a et b est une opération tellement courante qu'il serait fastidieux de coder sum(a, b) pour cela. Comme en calcul algébrique, on code simplement :
a + b
L'opérateur + employé ici est binaire.
Comme pour toute fonction, les principales caractéristiques d'un opérateur sont :
- son arité, c'est‑à‑dire son nombre d'opérandes,
- le type des valeurs qu'il accepte comme opérandes,
- le type des valeurs qu'il rend.
Mais en faisant partie d'un cercle limité de fonctions codées par une syntaxe privilégiée, un opérateur se caractérise aussi par d'autres caractéristiques :
- son rang de priorité au sein des expressions (par rapport aux autres opérateurs),
- son sens d'associativité au sein des expressions par rapport aux opérateurs voisins de même rang de priorité,
Tous ces aspects sont détaillés plus loin.
Opérateurs particuliers
En langages C/C++, on recense trois opérateurs particuliers, parce que très généraux au point qu'un débutant pourrait ne pas les percevoir comme tels, alors qu'ils sont bel et bien des opérateurs. Ce sont :
Opérateurs élémentaires
Comme tous les langages de programmation généralistes, C et C++ disposent des opérateurs usuels de calcul numérique, qu'on peut qualifier d'opérateurs élémentaires, parce qu'ils s'appliquent à des opérandes de types élémentaires (cf. chap. C3‑I ).
Les opérateurs élémentaires sont listés par familles dans le tableau ci‑dessous.
| Famille | Opérandes | Valeurs | Noms et symboles |
|---|---|---|---|
| booléens | tous types | int |
! non
&& et
|| ou
|
| booléens bits à bits |
types entiers |
types entiers |
~ non
& et
| ou ^ ou exclusif
|
| décalage des bits |
types entiers |
types entiers |
>> n décalage n rangs à droite << n décalage n rangs à gauche
|
| comparaison | tous types |
int |
== égal
!= différent >
>= supérieur (ou égal) <
<= inférieur (ou égal) <=> opérateur « 3 voies » (en C++)
|
| arithmétique algèbre |
tous types |
au moins int ou double |
+ plus
- moins unaires + plus
- moins binaires * fois
/ division
% modulo
|
- Certains opérateurs sont tellement usuels en calcul qu'ils ne sont plus à présenter (opérateurs algébriques, de comparaison…). Néanmoins, en programmation, ils sont quelques particularités :
- il faut veiller à ne pas confondre l'opérateur
-unaire (pour former un nombre négatif) et l'opérateur-binaire (la soustraction) ; on verra ci‑après qu'ils n'ont pas les mêmes propriétés (ordre de priorité, sens de l'associativité) ; - il faut veiller à ne pas confondre l'opérateur de test d'égalité
==avec l'opérateur d'affectation=; une erreur classique consécutive à une telle confusion est donnée en exemple au chap. C2‑V - l'opérateur de comparaison trois voies W, peu connu, tient une place à part dans sa famille, car il peut rendre une valeur négative (il est intégré à la norme C++20 seulement).
- Les opérateurs de décalages de bits ainsi aue l'opérateur modulo
%– qui donne le reste de la division euclidienne – sont peu usités en calcul mental mais sont cruciaux en programmation. Ils seront approfondis dans la partie C3 du cours.
Opérateurs structurels
En plus des opérateurs élémentaires ci‑dessus, les langages C et C++ disposent d'opérateurs structurels, c'est‑à‑dire pour mettre en œuvre des opérations sur les aspects structurels des programmes et des données.
Ils sont listés dans le tableau ci‑dessous.
| Symbole | Désignation |
|---|---|
(type) |
transtypage (ou cast) |
() |
appel de fonction |
[] |
indexation de tableau ou de pointeur |
. |
sélection de champ de structures hétérogènes |
-> |
déréférencement structurel de structures hétérogènes |
* |
déréférencement de pointeur |
& |
opérateur d'adresse de donnée |
sizeof |
opérateur de taille de donnée |
_Alignof alignof |
opérateur de contrainte d'alignement |
? : |
opérateur conditionnel |
Pour un codeur débutant, cette multitude d'opérateurs constitue une vraie difficulté, d'autant plus qu'ils utilisent des symboles déjà attribués à des opérateurs élémentaires !
En particulier, il faut ne pas confondre les différentes interprétations des parenthèses :
- en tant qu'opérateur unaire d'élévation de rang de priorité (comme en mathématiques) ;
- en tant qu'opérateur binaire d'appel de fonction dont :
- le 1er opérande est l'identificateur (nom) de la fonction, codé à gauche des parenthèses ;
- le 2e opérande est la liste des arguments passés à la fonction, codée dans les parenthèses ;
- en tant qu'opérateur binaire de conversion explicite (cast) dont :
- le 1er opérande est le descripteur de type, dans les parenthèses ;
- le 2e opérande est l'expression à convertir, codée à droite des parenthèses.
L'objectif de présenter les opérateurs structurels dans ce chapitre n'est pas d'expliquer en détail le rôle de chacun, mais :
- de prendre conscience de leur existence ;
- d'inviter à d'être particulièrement vigilant quant au respect de la syntaxe des opérateurs élémentaires.
En effet, une « petite » confusion peut passer inaperçue à la compilation parce qu'elle est syntaxiquement reconnue par le compilateur comme une expression valide, mais pour un autre opérateur que celui auquel le codeur pensait, avec à la clé un résultat d'exécution forcément différent de celui attendu.
Les opérateurs structurels seront revus de façon approfondie dans le cadre spécifique de l'étude des fonctions et des structures de données (parties C4 et C5 du cours).
Types des opérandes
Syntaxiquement, les opérandes auxquels s'appliquent les opérateurs sont des expressions avec certaines restrictions quant au type des valeurs qu'elles peuvent prendre.
Cas général
Dans le domaine des mathématiques, en règle générale, les opérateurs sont des lois internes sur des ensembles. Leurs opérandes doivent donc être nécessairement de même type, mais cela ne pose pas de problème parce que les grands ensembles de nombres sont inclus les uns dans les autres (en particulier, on a ℤ ⊂ ℝ). Ainsi, une opération telle que 2 × π ne choque personne même si 2 est entier et π réel, puisqu'on a 2 ∊ ℝ, donc cette opération est définie dans ℝ.
En programmation, la question de l'homogénéité des types d'une opération n'est pas si simple, car les ensembles de valeurs des différents types ne sont pas inclus les uns dans les autres. En particulier, pas toutes les valeurs du type int ne sont codables dans le type float – et réciproquement, bien évidemment.
Ainsi, certains langages de programmation comme Ada imposent une contrainte d'homogénéité stricte au sein des opérations. Il faut alors recourir à une opération de conversion explicite (cast) pour effectuer la moindre opération hétérogène comme une multiplication entre un entier et un décimal.
En langages C et C++, pour un codage simplifié des expressions, les opérations hétérogènes sont acceptées, c'est‑à‑dire appliquées à des données de types différents, en procédant à des conversions implicites de types.
En C/C++, une expression comme 0.5 * 10 est compilable, alors qu'elle hétérogène. En effet, la constante littérale 0.5 est de type décimal et que la constante littérale 10 est de type entier.
- Lors de la compilation, pour garantir un résultat conforme aux règles usuelles de calcul, c'est évidemment la constante
10qui est ajustée dans un type décimal (et non pas0.5dans un type entier). - Le résultat final
5, bien qu'entier dans ce cas particulier, est encodé dans un type décimal.
Cas des opérateurs sur les bits
La seule restriction de typage concerne les opérateurs sur les bits (booléens et de décalage), qui sont spécifiquement réservés aux types entiers.
L'expression 0.2 << 1 n'est pas compilable, parce que l'opérande 0.2 n'est pas de type entier.
En revanche, l'expression 2 << 1 est compilable et vaut 4 (cf. chap. C3‑III ).
Cas des opérateurs booléens
Quant aux opérateurs booléens généraux (non bits à bits), ils peuvent être appliqués à des expressions à valeurs numériques et non pas booléennes, mais alors :
- la valeur booléenne
0(équivalente àfalse, c'est‑à‑dire, faux) est attribuée à toute valeur nulle ; - la valeur booléenne
1(équivalente àtrue, c'est‑à‑dire, vrai) est attribuée à toute valeur non nulle.
De plus, une expression composée avec des opérateurs booléens ou de comparaison ne peut prendre que les valeurs 0 ou 1 selon qu'elle est évaluée respectivement fausse ou vraie.
- Le code :
if (newVoltage)…
est équivalent à :
if (newVoltage != 0.0)…
cette deuxième variante étant préférable en termes de lisibilité, car elle explicite la condition « si la tension est non nulle… ». - L'instruction :
ageAdmittance = (userAge > 18);
affecte à la variableageAdmittancela valeur booléenne0ou1prise par l'expression :
userAge > 18
laquelle dépend de la valeur entière prise par la variableuserAgelors de l'exécution ; par exemple, siuserAgevaut16, alors :
userAge > 18
vaut0(faux), doncageAdmittancevaut0(faux).
Opérateurs à affectation composée
Les langages C/C++ fournissent également des opérateurs composés (compound assignment operators) qui, chacun, combine un opérateur élémentaire avec l'opérateur d'affectation =.
S'ils peuvent s'apparenter à des « raccourcis syntaxiques », ils constituent des opérateurs au même titre que les autres, avec un rang de priorité et un sens d'associativité spécifiques (cf. infra).
Ces opérateurs sont particulièrement utiles pour coder des opérations de modifications de variables, surtout si ces dernières ont des identificateurs longs, d'autant plus qu'ils diminuent les risques d'éventuelles erreurs de saisies de ces identificateurs.
Incrémentation unitaire
Si a est l'identificateur d'une variable, ou plus généralement une expression qui est une l‑value mutable (cf. supra ), alors :
- l'expression
++aeffectue une incrémentation unitaire dea, c'est‑à‑dire une augmentation de 1 juste avant l'évaluation dea; - l'expression
a++procède de même mais juste après l'évaluation dea.
En préfixe, ++ est l'opérateur de pré‑incrémentation.
En suffixe, ++ est l'opérateur de post‑incrémentation.
Si a est l'identificateur d'une variable valant 0, alors l'instruction :
printf("%d", a++);
affiche sur 0 sur le terminal d'exécution, alors que l'instruction :
printf("%d", ++a);
affiche 1 sur le terminal d'exécution.
Dans les deux cas, la variable a possède la valeur 1 après exécution de l'instruction.
Décrémentation unitaire
Le principe exposé supra s'applique aussi à l'opérateur de soustraction - pour former les opérateurs de pré‑ et de post‑décrémentation unitaire :
- l'expression
--aeffectue une diminution de 1 juste avant l'évaluation dea; etc. - l'expression
a--effectue une diminution de 1 juste après l'évaluation dea.
Incrémentations et décrémentations multiples
Plus généralement, on peut opérer une incrémentation ou une décrémentation multiple d'une variable avec les opérateurs à affectation combinée dont les symboles sont respectivement += et -=.
Si a est l'identificateur d'une variable, ou plus généralement une expression qui est une l‑value mutable, et si b est une expression à valeur numérique quelconque, alors :
- l'expression
a += b« abrège »a = a + b, - l'expression
a -= b« abrège »a = a - b.
Si lastBatteryVoltage est une variable valant 10.0 et voltageDrop une variable valant 2.0, alors l'instruction :
lastBatteryVoltage += voltageDrop;
affecte à lastBatteryVoltage la valeur 8.
Sans recours à l'opérateur +=, l'instruction aurait été plus longue :
lastBatteryVoltage = lastBatteryVoltage + voltageDrop;
En langage C, ni ++a, ni a++ ne sont des l‑value.
Mais en langage C++ :
- l'expression
++aest elle‑même une l‑value, alors quea++ne l'est pas ; ainsi, l'instruction++a += b;est compilable, mais pasa++ += b;; - les instructions
expr_1 += expr_2;etexpr_1 = expr_1 + expr_2;ne sont pas tout à fait équivalentes ; en effet, si expr_1 est elle‑même une expression de la forme++i, elle est évaluée : - une seule fois dans la forme abrégé, et alors la variable
iest augmentée de1; - deux fois dans la forme non abrégé, et alors la variable
iest augmentée de2.
Ces précisions sont importantes pour comprendre le codage de certaines routines dans la bibliothèque standard du langage C, mais elles ne doivent pas encombrer l'esprit des programmeurs débutants, à qui il est vivement déconseillé de coder de telles combinaisons d'opérateurs.
Autres opérateurs à affectation composée
Le principe de composition d'un opérateur avec celui d'affectation, exposé supra pour l'addition et la soustraction, s'applique aussi aux opérateurs suivants :
*
/
%
&
|
^
>>
<<
Si a est un identificateur de variable, ou plus généralement une expression qui est une l‑value, et si b est une expression à valeur numérique quelconque (entière ou décimale), alors :
- l'expression
a *= b« abrège »a = a * b, - l'expression
a /= b« abrège »a = a / b, - l'expression
a %= b« abrège »a = a % b, etc.
Pour les opérateurs booléens bits à bits et de décalages, des exemples sont donnés au chap. C3‑III .
Rangs de priorité des opérateurs
Comme en mathématiques, il existe en programmation une hiérarchie entre les opérateurs en termes de priorités d'exécution au sein d'une expression.
De plus, pour changer l'ordre de traitement d'une expression, il suffit d'encapsuler la partie à traiter en priorité dans des parenthèses supplémentaires (). Ces dernières constituent un opérateur hors catégorie en terme de priorité, c'est‑à‑dire de rang « 0 ».
Le tableau ci‑dessous donne le rang de priorité respectif de chaque opérateur du langage C. Quant aux opérateurs spécifiques du langage C++ (notamment ceux relatifs à la programmation orientée objet), ils seront abordés dans un autre module de formation.
Pour une présentation plus complète de ce tableau, on peut consulter les pages de référence suivantes C C++.
| 1 |
fonction() []
. -> ++ -- (suffixes)
|
6 |
< <= > >=
|
11 |
&&
|
|---|---|---|---|---|---|
| 2 |
! ~
++ -- (préfixes)
+ - (unaires) * (pointeur)
& (adresse) (type) (cast)sizeof _Alignof alignof
|
7 |
== !=
|
12 |
||
|
| 3 |
* / %
|
8 |
&
|
13 |
? :
|
| 4 |
+ - (binaires)
|
9 |
^
|
14 |
= += -= *= /= %= &= |= ^= >>= <<=
|
| 5 |
>>
<<
|
10 |
|
|
15 |
,
|
Comme en mathématiques, l'expression 1 + 2 * 3 sera évaluée implicitement comme 1 + (2 * 3) donnant la valeur 7, et non pas dans l'ordre de lecture (1 + 2) * 3.
Remarque : si l'on souhaitait rendre l'addition prioritaire, il suffirait de coder (1 + 2) * 3 et on obtiendrait alors la valeur 9.
Sens de l'associativité des opérateurs
En mathématiques, on dit d'un opérateur binaire qu'il est associatif si l'on peut le composer plusieurs fois de suite et obtenir le même résultat numérique indépendamment de l'ordre de composition. Ainsi, l'addition est associative, parce que la proposition :
(a + b) + c = a + (b + c)
est toujours vraie, quelles que soient les valeurs de a, b et c.
En programmation, lorsque qu'une sous-expression est composée de plusieurs opérateurs de même rang de priorité, l'ordre de traitement est par défaut et en principe, celui du sens de lecture des langues occidentales, c'est‑à‑dire de gauche à droite (→).
L'expression 1 + 2 + 3 sera donc a priori évaluée par la machine comme (1 + 2) + 3.
Mais cet l'ordre de traitement peut varier selon le compilateur. C'est pourquoi il est recommandé de coder des parenthèses pour lever toute ambiguïté.
Par ailleurs, conformément à la norme, certains opérateurs sont toujours traités de droite à gauche (←) :
- la plupart des opérateurs unaires, c'est‑à‑dire :
!~-++ --(en préfixes)
mais pas tous, et notamment pas++ --(suffixes), ni l'opérateur séquentiel,; - tous les opérateurs d'affectation, c'est‑à‑dire :
=+=-=*=/=%=etc.
(Le sens d'associativité de=est essentiel pour comprendre comment sont opérés les enchaînements d'affectation comme dans l'expressiona = b = 5.)
Pour une présentation détaillée du sens de l'associativité des opérateurs, on peut consulter les mêmes pages de référence que pour les rangs de priorité C C++.
Si a est l'identificateur d'une variable valant 2, alors l'expression - ++a sera a priori évaluée -3. En effet, elle est équivalente à -(++a), donc à l'exécution, la machine incrémente a (ce qui donne 3) avant d'inverser son signe.
Notion d'effet de bord
On dit d'une opération qu'elle est à effet de bord W sur une expression – en anglais, on parle de side effect operation – si elle modifie le contexte d'évaluation de cette expression, c'est‑à‑dire qu'elle change la valeur d'au moins une des variables qui la constitue.
En langages C et C++, les opérateurs à effet de bord sont l'affectation et plus généralement tous les opérateurs à affectation composée :
++
--
+=
-=
*= …
Ces opérateurs doivent être toujours employés avec précaution, en particulier dans des pseudo‑fonctions (cf. chap. C4‑III ), en ayant conscience de l'effet de bord qu'ils occasionnent, pour éviter des erreurs d'exécution difficiles à anticiper par la suite, lorsque le code devient volumineux.
Fonctions et constantes mathématiques
Éléments des bibliothèques standards de C et C++
En langages C/C++, il n'existe pas d'opérateur d'exponentiation pour coder une expression mathématique de la forme ab.
En revanche, toutes les fonctions mathématiques usuelles sont définies dans un module de la bibliothèque standard du langage.
En langage C, on doit préalablement coder la directive #include <math.h> (<cmath> en C++) pour pouvoir exploiter ce module.
De plus, sous Linux, pour être opérationnelle, les commandes de compilation gcc et g++ (cf. chap. C1‑II ) requièrent alors l'usage de l'option -lm (pour library math) saisie après le nom du fichier source, par exemple à la fin de la ligne de commande comme ci‑dessous :
gccfichier source-ofichier exécutable-lm
Dans le module mathématique, on trouve notamment les fonctions suivantes :
| identificateur | description |
|---|---|
round floor
ceil
|
fonctions d'arrondi entier au plus près d'un décimal, par valeurs inférieurs (floor), par valeurs supérieures (ceil) |
pow
sqrt
|
fonctions puissance (power) et racine carrée (square root) |
exp log
log10
|
fonctions exponentielle et logarithmiques |
sin
cos
tan
|
fonctions trigonométriques |
Pour connaître la liste exhaustive des fonctions déclarées dans le fichier math.h, on peut consulter ce lien C.
L'expression mathématique ab se code pow(a, b) où a et b sont deux expressions à valeurs numériques (entières ou décimales, signées ou non).
- Le module
cmath(cf. C++) du langage C++ est plus fourni en fonctions mathématiques que celui du langage C. - Certaines fonctions mathématiques très générales ne sont pas déclarées dans fichier
math.hmais dans le fichierstdlib.h(en C++,cstdlib). C'est notamment le cas deabs(valeur absolue) ourand(génération d'un nombre pseudo-aléatoire). Cette dernière est détaillée au chap. C3‑II .
Constantes mathématiques
Certaines implémentations définissent dans le fichier d'en-tête math.h de la bibliothèque standard du langage C (cmath en C++) des pseudo‑constantes qui donnent une approximation décimale des constantes mathématiques les plus usuelles.
On trouve notamment :
-
M_PI,M_PI_2etM_PI_4respectivement pour π, π/2 et π/4 ; -
M_Epour le nombre e (dit nombre d'Euler ou constante de Neper W) ; -
M_SQRT2pour le nombre √2…
Le lien ci‑après donne la liste exhaustive de ces pseudo‑constantes pour les implémentations basées sur les chaînes de compilation GCC C.
Les pseudo‑constantes mathématiques M_PI, M_E, etc. ne sont pas définies dans les implémentations du langage C basées sur les chaînes de compilation mingw pour les systèmes Windows – alors qu'elles le sont pour le langage C++ !
Pour pallier cette lacune, rien n'interdit de coder soi‑même, par autant de directives au préprocesseur, les pseudo‑constantes requises par un programme. Par exemple, en langage C, on peut très bien ajouter la directive de compilation conditionnelle (cf. chap. C4‑III codée aux lignes nº 2 à 4 :
#include <math.h> #ifndef M_PI #define M_PI 3.14159265358979323 #endif // ...
Cela rend le programme portable aussi bien pour une machine cible Linux que Windows.
Éléments de la bibliothèque Arduino
Comme expliqué au chapitre C2‑I , le module principal de la bibliothèque Arduino contient lui‑même des directives d'inclusion de modules de la bibliothèque standard du langage C, notamment du fichier d'en-tête math.h.
Il n'est donc pas nécessaire de coder à nouveau une telle directive dans un programme Arduino.
De plus, le fichier Arduino.h G définit ou redéfinit un certain nombre de pseudo‑fonctions (ou macro‑définitions – cf. chap. C4‑III ), notamment :
| identificateur | description |
|---|---|
sq
|
pseudo‑fonction carré (square) |
abs
|
pseudo‑fonction valeur absolue |
min
max
|
pseudo‑fonction binaires minimum A et maximum A |
ainsi que quelques pseudo‑constantes, notamment PI (π) et EULER (e).
Parmi ces définitions, la fonction map et la pseudo‑fonction constrain sont particulièrement utiles :
- Un appel de la fonction
mapde la forme :
map(value, fromLow, fromHigh, toLow, toHigh)
prend des arguments tous de type entier (long) et retourne une valeur entière (également de typelong) proportionnellement située entre les bornes toLow et toHigh comme l'est value entre les bornes fromLow et fromHigh. - Une invocation de la pseudo‑fonction
constrainde la forme :
constrain(value, min, max)
prend des arguments de tous types numériques (entiers, décimaux) et est remplacée par la valeur : - value si min ⩽ value ⩽ max,
- min si value < min ,
- max si value > max.
- si
noteSur20vaut13, alorsnoteHarmoniseeprend la valeur15.6(13 × 1,2) ; - si
noteSur20vaut18, alorsnoteHarmoniseeprend seulement la valeur20et non pas21,6(18 × 1,2) puisque 21,6 > 20.
Pour ramener sur 20 une note scolaire de 10/15 et la stocker dans une variable entière déclarée noteSur20, on pourrait coder l'instruction :
noteSur20 = map(10, 0, 15, 0, 20);
en prenant garde au fait que le résultat obtenu est forcément entier (la variable noteSur20 prendrait ici la valeur 13 et non pas 13,3333 en théorie).
Si l'on multiplie une note scolaire par un coefficient d'harmonisation, aucune valeur ne doit pour autant dépasser la valeur maximale (typiquement, 20/20). La fonction constrain peut servir à coder cette contrainte :
noteHarmonisee = constrain(noteSur20 * 1.2, 0, 20);
sachant qu'ici, la valeur obtenue est de type décimal. Donc :
La fonction map et la pseudo‑fonction constrain peuvent très facilement s'utiliser hors du framework Arduino – c'est‑à‑dire dans un programme en C ou en C++. Il suffit de copier‑coller leur code au début du programme. On le trouve respectivement dans le fichier WMath.cpp G (lignes n° 52 à 55) et Arduino.h G (ligne n° 95).
Emploi des fonctions mathématiques
En programmation, une fonction mathématique effectue comme seule action de retourner une valeur au moment de son appel. Elles ne produisent aucun affichage !
Les fonctions mathématiques sont usuellement employées dans :
- des expressions logiques pour tester des valeurs calculées ;
- des affectations pour mémoriser des valeurs retournées.
if (sin(alpha) > 0.5)… ou alpha est une donnée déclarée.
y = sin(alpha);
Insistons sur le fait qu'un appel de fonction mathématique ne peut constituer à lui seul une instruction.
Par exemple, certains grands débutants codent parfois une instruction comme :
max(x, 10);
sans comprendre qu'elle n'a aucun effet ! Lors de l'exécution, la machine aura beau déterminer le maximum entre la valeur courante de la variable x et le nombre 10, ce maximum n'étant pas mémorisé dans une variable, il sera perdu.
En revanche, il suffit de coder, par exemple :
int y = max(x, 10);
pour remédier au problème. La valeur retournée par l'appel de la fonction max étant mémorisée dans la variable y, on peut ensuite l'exploiter.