Chapitre 4 – Constantes et variables▲
4-1. Types de données▲
En programmation, les données manipulées peuvent être de natures différentes : nombres entiers, nombres réels, chaînes de caractères, booléens… Ces différentes natures de données sont appelées types de données.
Le langage Caml dispose d'un certain nombre de types prédéfinis.
4-1-1. Les booléens▲
Le type bool
est le type des expressions booléennes permettant d'écrire les conditions des instructions if et while. Les données de ce type ne peuvent prendre que deux valeurs.
Nom : bool
Ensemble des valeurs : true
, false
Littéraux : true
et false
Opérateurs : &&, || , not
Exemple 1 :
2.
3.
4.
5.
6.
# true
;;
-
: bool
=
true
# not
(true
);;
-
: bool
=
false
# true
|| false
;;
-
: bool
=
true
4-1-2. Les nombres entiers▲
Le type int
est le type des expressions numériques entières.
Nom : int
Ensemble des valeurs : le type int
est limité aux entiers appartenant à un intervalle [[emin,emax]], et il y a donc un plus petit et un plus grand entier. Cet intervalle dépend de l'architecture du processeur de l'ordinateur utilisé. Sur des architectures 32 bits, on a
[[emin, emax]] = [[-230 , 230 - 1]] = [[−1 073 741 824, 1 073 741 823]]. |
Sur des architectures 64 bits, on a
[[emin, emax]] = [[-262, 262 - 1]] = [[−4 611 686 018 427 387 904, 4 611 686 018 427 387 903]]. |
Littéraux : les entiers dans leur forme écrite usuelle. Par exemple : 24
.
Constantes prédéfinies : les constantes min_int
et max_int
donnent les valeurs du plus petit (emin) et du plus grand (emax) entier.
Opérateurs arithmétiques : +
, -
, *
, /
, mod
Opérateurs de comparaison : =
, <>
, <
, <=
, >
, >=
Remarque : Les calculs arithmétiques sur les nombres de type int
s'effectuent modulo emax - emin + 1.
Exemple 2 :
La session ci-dessous est effectuée sur une machine 32 bits.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
# (1
+
2
) *
3
;;
-
: int
=
9
# 9
/
2
;;
-
: int
=
4
# 9
mod
5
;;
-
: int
=
4
# max_int
;;
-
: int
=
1073741823
# min_int
;;
-
: int
=
-
1073741824
# max_int
+
1
;;
-
: int
=
-
1073741824
# min_int
-
1
;;
-
: int
=
1073741823
4-1-3. Les nombres flottants▲
Les nombres flottants sont définis avec le mot clé : float
4-1-4. Types définis par le module Cartes▲
Le module Cartes définit deux types couleur et numero_tas(11).
Nom : couleur.
Ensemble des valeurs : Les données de ce type ne peuvent prendre que quatre valeurs notées : CARREAU, TREFLE, PIQUE et COEUR.
Nom : numero_tas.
Ensemble des valeurs : Les données de ce type ne peuvent prendre que quatre valeurs : 1, 2, 3 et 4.
4-2. Variables▲
4-2-1. Notion de variable▲
Les variables servent à nommer des valeurs.
Elles sont caractérisées par leur nom ou identificateur, leur type et leur valeur.
4-2-2. Déclaration simple de variables▲
La déclaration des variables se fait à l'aide du mot réservé (ou mot-clé) let.
let
<
identificateur >
=
<
expression >
où <
identificateur>
est le nom de la variable, et <
expression>
l'expression qui définit sa valeur.
Exemple 3 :
Voici la déclaration d'une variable nommée a, de type int
et de valeur 12
.
2.
3.
4.
5.
6.
# let
a =
12
;;
val
a : int
=
12
# a;;
-
: int
=
12
# 2
*
a;;
-
: int
=
24
L'expression qui définit la valeur d'une variable peut faire référence à d'autres variables à condition que celles-ci aient été préalablement déclarées.
Exemple 4 :
À la suite de la déclaration de la variable a qui précède, on peut déclarer une nouvelle variable b comme ci-dessous
2.
# let
b =
2
*
a +
1
;;
val
b : int
=
25
4-2-3. Identificateurs de variables▲
Les variables sont nommées par un identificateur. En Caml, les identificateurs de variables doivent obligatoirement commencer par une lettre minuscule ou un blanc souligné (_) et peuvent être complétés par n'importe quelle quantité de lettres minuscules ou majuscules non accentuées, ou chiffres, ou blancs soulignés.
Exemple 5 :
Le tableau ci-dessous montre quelques exemples d'identificateurs de variables corrects et incorrects.
Identificateurs corrects |
Identificateurs incorrects |
---|---|
a |
A |
a1 |
1a |
a_b |
a |
_a |
|
deplacer_sommet |
deplacer |
En plus de ces règles, certains mots ne peuvent pas être utilisés comme identificateurs : ce sont les mots réservés ou mots-clés du langage (cf annexe C.2C.2 Mots-clés du langage).
4-2-4. Environnement▲
L'ensemble des variables définies dans un contexte de calcul s'appelle un environnement.
Lorsque l’on déclare une variable, on augmente l'environnement courant de cette nouvelle variable. Ainsi, si dans un certain environnement E0 on déclare la variable
2.
# let
a =
12
;;
val
a : int
=
12
l'environnement qui suit cette déclaration est augmenté d'une variable nommée a et de valeur 12, et l'environnement est maintenant
E1 =< a, 12 >: E0. |
Après la déclaration
2.
# let
b =
5
;;
val
b : int
=
5
l'environnement est
E2 = < b, 5 >: E1 = < b, 5 >:< a, 12 >: E0. |
On voit donc que la déclaration de variables peut augmenter l'environnement. Mais rien ne peut faire diminuer l'environnement : une variable déclarée le reste pour toute la durée de la session.
4-2-5. Évaluation d'une expression▲
Lorsqu'une expression contient une ou plusieurs variables, pour chacune de ces variables on cherche dans l'environnement courant la variable de même nom la plus récemment déclarée, et on la remplace par la valeur correspondante.
Par exemple dans l'environnement E2 précédemment décrit, l'expression a*
b est transformée en 12
*
5
qui finalement donne la valeur 60.
a∗b ⇒ 12∗5 ⇒ 60. |
4-2-6. Masquage d'une variable▲
Nous l'avons vu un environnement ne peut qu'augmenter. Que se passe-t-il lorsque nous déclarons une variable dont le nom est identique à celui d'une variable déjà déclarée ? Que se passe-t-il si l’on déclare une nouvelle variable nommée a dans l'environnement E2 ?
Et bien, rien de neuf. Une nouvelle variable est ajoutée à l'environnement. Par exemple, si dans l'environnement E2 on fait la déclaration
2.
# let
a =
9
;;
val
a : int
=
9
l'environnement devient
E3 = < a, 9 >: E2 = < a, 9 >:< b, 5 >:< a, 12 >: E0. |
On constate que l'environnement E2 contient deux variables de même nom a : l'une valant 12 qui dans le temps a été la première à être déclarée, l'autre valant 9 qui est la plus récente.
Quelle est alors la valeur de l'expression a*
b dans l'environnement E3 ? Dans l'expression, on remplace la variable b par sa valeur 5, mais qu'en est-il pour a ? 9 ou 12 ? La règle d'évaluation dit de choisir la valeur de la variable la plus récemment déclarée, autrement dit 9. Par conséquent, dans l'environnement E3 l'expression a*
b vaut 45.
a∗b ⇒ 9∗5 ⇒ 45. |
Dans l'environnement E3, toute référence à la variable a est une référence à la variable <
a, 9
>
. L'autre variable <
a, 12
>
est masquée par <
a, 9
>
.
4-2-7. Déclaration simultanée de variables▲
Plusieurs variables peuvent être définies simultanément dans la même phrase let en utilisant le mot-clé and.
2.
3.
4.
let
<
identificateur 1
>
=
<
expression 1
>
and
<
identificateur 2
>
=
<
expression 2
>
...
and
<
identificateur n >
=
<
expression n >
Les expressions <expressioni> sont alors toutes évaluées dans l'environnement dans lequel la phrase de déclaration est exprimée.
Exemple 6 :
2.
3.
4.
5.
6.
# let
a =
1
and
b =
2
;;
val
a : int
=
1
val
b : int
=
2
# a +
b ;;
-
: int
=
3
La déclaration simultanée de plusieurs variables n'est pas équivalente à la déclaration séquentielle. Par exemple, on peut tout à fait effectuer les deux déclarations successives suivantes
2.
3.
4.
# let
c =
1
. ;;
val
c : float
=
1
.
# let
d =
c +
. 2
. ;;
val
d : float
=
3
.
mais il n'est pas possible de demander la déclaration simultanée qui suit si aucune déclaration préalable d'une variable e de type float
n'a été faite.
2.
3.
# let
e =
1
.
and
f =
e +
. 2
. ;;
Unbound value e
En effet dans l'environnement d'évaluation de cette déclaration, la variable e n'existe pas, et il n'est donc pas possible d'évaluer l'expression e +
. 2
..
Mais si une déclaration préalable a été faite il n'y a aucun problème.
2.
3.
4.
5.
6.
# let
e =
-
3
.;;
val
e : float
=
-
3
.
# let
e =
1
.
and
f =
e +
. 2
. ;;
val
e : float
=
1
.
val
f : float
=
-
1
.
À noter que dans l'environnement obtenu après ces déclarations, la variable e vaut 1
., et f vaut -
1
., autrement dit f a été calculée avec la valeur de e dans l'environnement précédent.
4-2-8. Variables locales à une expression▲
Nous l'avons vu, l'environnement courant des variables déclarées ne peut que croître. Cela peut poser deux problèmes :
- un accroissement inutile de l'environnement avec des variables qui ne servent pas, et qui peut provoquer un encombrement, voire une saturation de la mémoire ;
- un masquage de certaines variables lorsqu'on utilise un même nom.
Bien souvent, on éprouve le besoin de déclarer une variable pour faciliter l'écriture d'une expression et/ou optimiser son évaluation.
Par exemple, si nous souhaitons calculer l'expression
kitxmlcodelatexdvp\sqrt{a^2+b^2} \times \frac{1+\sqrt{a^2+b^2}}{2+\sqrt{a^2+b^2}}finkitxmlcodelatexdvpon pourrait écrire (en supposant que dans l'environnement courant il y ait deux variables a et b de valeurs flottantes)
2.
3.
sqrt
(a**
2
. +
. b**
2
.) *
.
(1
. +
. sqrt
(a**
2
. +
. b**
2
.)) /
.
(2
. +
. sqrt
(a**
2
. +
. b**
2
.))
mais cela n'est évidemment pas satisfaisant parce que d'une part l'expression n'est pas facilement lisible, et d'autre part dans son évaluation, la sous-expression kitxmlcodeinlinelatexdvp\sqrt{a^2+b^2}finkitxmlcodeinlinelatexdvp va être calculée trois fois.
Pour mener des calculs, il arrive souvent que, pour ne pas noyer le propos par de longues formules, on utilise des notations pour les simplifier. On « pose » que tel symbole vaut temporairement telle valeur, et ensuite on utilise ce symbole en lieu et place de celle-ci.
Sur notre exemple cela pourrait donner : soit kitxmlcodeinlinelatexdvp\alpha = \sqrt{a^2+b^2}finkitxmlcodeinlinelatexdvp dans l'expression
kitxmlcodelatexdvp\alpha \times \frac{1+\alpha}{2+\alpha}finkitxmlcodelatexdvpdont une traduction possible en Objective Caml est
2.
let
alpha =
sqrt
(a**
2
. +
. b**
2
.)
alpha *
. (1
. +
. alpha) /
. (2
. +
. alpha)
Cette façon de mener le calcul remédie aux deux défauts signalés ci-dessus :
- le code est beaucoup plus lisible ;
- la sous-expression kitxmlcodeinlinelatexdvp\sqrt{a^2+b^2}finkitxmlcodeinlinelatexdvp n'est calculée qu'une seule fois, et donc on gagne en efficacité.
Cependant, en procédant de la sorte on a introduit dans l'environnement courant une nouvelle variable dont l'intérêt est limité au seul calcul de notre expression. Par la suite la variable kitxmlcodeinlinelatexdvp\alphafinkitxmlcodeinlinelatexdvp ne sera probablement plus utilisée mais persistera dans l'environnement : c'est une pollution.
Il existe une solution pour éviter la pollution de l'environnement par des variables d'intérêt temporaire : c'est la notion de variable locale à une expression.
Ce qui manque dans notre exemple c'est la traduction de « posons kitxmlcodeinlinelatexdvp\alpha = \dotsfinkitxmlcodeinlinelatexdvp dans l'expression… ». Il ne faut pas déclarer une variable, puis calculer une expression, mais déclarer une variable à utiliser dans une expression. En Objective Caml, cela se traduit avec la forme let
… in
…. Notre exemple devient alors
2.
3.
let
alpha =
sqrt
(a**
2
. +
. b**
2
.)
in
alpha *
. (1
. +
. alpha) /
. (2
. +
. alpha)
Avec une telle déclaration, la variable alpha ne vient s'ajouter à l'environnement courant que pour la durée de l'évaluation de l'expression qui suit. Sitôt cette évaluation terminée, elle est supprimée, et n'existe donc pas dans l'environnement courant à l'issue du calcul. Il n'y a aucune différence entre l'environnement avant et après évaluation. Le problème de pollution d'environnement est réglé.
La syntaxe générale de déclaration de variables locales est donnée ci-dessous.
2.
3.
4.
5.
let
<
identificateur 1
>
=
<
expression 1
>
and
<
identificateur 2
>
=
<
expression 2
>
...
in
<
expression >
Les variables non locales déclarées par une simple forme let
sont appelées variables globales par opposition à locales.
4-2-9. Remarque sur la forme let▲
En Objective Caml, la forme let
… n'est pas une expression et a fortiori n'est pas une instruction.
Utilisée dans une séquence d'instructions elle ne donne pas l'effet auquel on pourrait penser. Dans la séquence qui suit
2.
3.
4.
# let
a =
1
;
a +
1
;;
Warning S: this expression should have type
unit.
Unbound value a
on constate un avertissement sur le fait que la première partie n'a pas le type unit, et un message d'erreur montre que dans l'environnement courant la variable a n'est liée à aucune valeur, et ceci malgré la forme de la déclaration qui précède.
Utilisée dans une expression conditionnelle, elle provoque un message d'erreur de syntaxe.
2.
3.
4.
5.
# if
true
then
begin
let
a =
1
;
print_string
("Fini"
)
end
;;
Syntax error
De même dans les instructions répétées (boucle while ou for).
En conclusion, une déclaration globale de variable ne peut être encapsulée dans une séquence d'instructions, dans une expression conditionnelle ou dans une boucle.
En revanche, la forme let
… in
… est une expression. À ce titre elle peut être encapsulée.
2.
3.
4.
5.
6.
7.
8.
9.
# let
a =
1
in
print_int
a ;
print_newline
() ;;
1
-
: unit =
()
# if
true
then
begin
let
a =
1
in
print_int
(a+
1
)
end
;;
2
-
: unit =
()
4-3. Variables mutables▲
4-3-1. Motivation▲
Un problème
- État initial : Tas 1 un nombre quelconque de cartes, les autres tas sont vides.
- État final : peu importe.
- But : afficher le nombre de cartes situées initialement sur le tas 1.
Algorithme
Une solution du problème consiste à déplacer (une à une) toutes les cartes du tas 1 vers un autre tas (le tas 2 par exemple), et à compter chaque déplacement effectué.
Algorithme 4.1 Compter les cartes du tas 1 |
mise à zéro du compteur |
Comme on peut le voir, dans cet algorithme la valeur du compteur évolue durant son exécution.
Une variable dont la valeur peut évoluer durant l'exécution d'un programme est appelée variable mutable. Par opposition, nous nommerons parfois les variables non mutables des constantes.
4-3-2. Déclaration d'une variable mutable▲
La déclaration d'une variable mutable est analogue à celle de toute variable, si ce n'est l'emploi du mot-clé ref.
let
<
identificateur >
=
ref
<
expression >
Exemple 7 :
Déclaration d'une variable mutable compteur initialisée à 0.
2.
# let
compteur =
ref
0
;;
val
compteur : int
ref
=
{contents
=
0
}
Comme on peut le voir, cette variable n'est pas de type int
, mais de type int
ref
. Et sa valeur est {contents
=
0
}.
2.
# compteur ;;
-
: int
ref
=
{contents
=
0
}
Comme compteur n'est pas de type int
, on ne peut pas l'utiliser tel quel dans le contexte d'une expression où l’on attend un int
.
2.
# compteur +
1
;;
This expression has type
int
ref
but is here used with
type
4-3-3. Référence à la valeur d'une variable mutable▲
Pour faire référence à la valeur d'une variable mutable dans une expression, on utilise le préfixe ! devant le nom de la variable.
Exemple 8 :
2.
3.
4.
5.
6.
# !compteur ;;
-
: int
=
0
# !compteur +
1
;;
-
: int
=
1
# !compteur;;
-
: int
=
0
Notez qu'après l'évaluation de la deuxième expression, la variable compteur a gardé la valeur 0.
Pour modifier la valeur d'une variable mutable, il faut utiliser l'instruction d'affectation.
4-3-4. Affectation▲
L'affectation est une instruction qui permet de changer la valeur d'une variable mutable.
La syntaxe de cette instruction est
<
identificateur >
:=
<
expression >
Le symbole :=
signifie que la valeur v de l'expression <
expression >
est affectée à la variable désignée par l'identificateur <
identificateur >
. Après l'exécution de cette instruction la variable <
identificateur >
vaut v.
Comme toute instruction, le résultat d'une affectation est la valeur () de type unit.
Exemples :
-
Pour mettre à zéro la variable compteur précédemment déclarée ;
Sélectionnez1.
2.
3.(* compteur = xxx *)
compteur:=
0
;(* compteur = 0 *)
-
Ajouter 1 au compteur ;
Sélectionnez1.
2.
3.(* compteur = n *)
compteur:=
!compteur+
1
;(* compteur = n + 1 *)
Remarques :
- Attention à ne pas confondre le symbole
:=
utilisé en Caml pour l'instruction d'affectation, avec le symbole = utilisé comme opérateur de comparaison, ainsi que dans la déclaration de variables. -
En programmation, on est souvent amené à utiliser des instructions du type
Sélectionnez1.a
:=
!a+
1
qui augmentent de 1 la valeur d'une variable (mutable). Cette opération s'appelle incrémentation et on dit qu'on incrémente une variable.
La décrémentation est la diminution de la valeur d'une variable.
Sélectionnez1.a
:=
!a-
1
-
On peut penser que le code suivant est équivalent à une incrémentation.
Sélectionnez1.let
a=
a+
1
Il n'en est rien du tout ! Il s'agit d'une déclaration d'une nouvelle variable a qui vient masquer la précédente. De plus, on ne peut pas utiliser cette forme let dans une boucle.
Par exemple, on peut écrire
Sélectionnez1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.#
let
a=
ref
0
;;val
a :int
ref
=
{contents
=
0
} #while
!a<
5
do
a:=
!a+
1
;print_int
!a;print_newline
()done
;;1
2
3
4
5
-
: unit=
()mais on ne peut pas écrire
Sélectionnez1.
2.
3.
4.
5.
6.
7.
8.#
let
a=
0
;;val
a :int
ref
=
{contents
=
0
} #while
a<
5
do
let
a=
a+
1
;print_int
(a);print_newline
() )done
;; Syntax error -
L'affectation est une opération autorisée sur les variables mutables uniquement. Elle ne l'est pas sur les variables non mutables (ou constantes).
Sélectionnez1.
2.
3.
4.#
let
a=
1
;;val
a :int
=
1
# a:=
2
;; This expression hastype
int
but is here usedwith
type
'aref
4-4. Afficher des données▲
En Caml, lorsqu'on veut afficher des données, on utilise les instructions print_int
pour les entiers, print_float
pour les flottants.
Exemple 9 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
# print_int
(1234
);;
1234
-
: unit =
()
# let
a =
1234
;;
val
a : int
=
1234
# print_int
(a*
2
);;
2468
-
: unit =
()
# print_int
(1234
.);;
This expression has type
float
but is here used with
type
int
# print_float
(1234
.);;
1234
.-
: unit =
()
Comme on peut le voir ces instructions impriment la valeur du paramètre sans passer à la ligne. Cela peut poser problème lorsqu'on veut afficher deux nombres successifs.
Exemple 10 :
2.
3.
4.
5.
# begin
print_int
(1
);
print_int
(2
)
end
;;
12
-
: unit =
()
L'instruction sans paramètre print_newline
provoque un passage à la ligne.
Exemple 11 :
2.
3.
4.
5.
6.
7.
8.
9.
# begin
print_int
(1
);
print_newline
();
print_int
(2
);
print_newline
()
end
;;
1
2
-
: unit =
()
L'instruction print_string
imprime une chaîne de caractères.
Exemple 12 :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
# let
a =
12
and
b =
21
in
begin
print_int
(a);
print_string
(" + "
);
print_int
(b);
print_string
(" = "
);
print_int
(a+
b);
print_newline
()
end
;;
12
+
21
=
33
-
: unit =
()
4-5. Le programme solution du problème 4.3.1▲
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
(* compter le nombre de cartes sur le tas 1 *)
let
cpt =
ref
0
in
begin
init_tas(1
,"[T]"
);
init_tas(2
,""
);
init_tas(3
,""
);
init_tas(4
,""
);
while
tas_non_vide(1
) do
deplacersommet(1
,2
);
cpt :=
!cpt +
1
done
;
print_string
("Nombre de cartes initialement sur le tas 1 : "
);
print_int
(!cpt);
print_newline
()
end
4-6. Exercices▲
Exercice 4-1 Déclarations de variables
Décrivez les valeurs des variables déclarées à l'issue des deux sessions suivantes :
Sélectionnez 1. 2. 3.
|
Sélectionnez 1. 2. 3.
|
Exercice 4-2 Échange de variables
Donnez une séquence d'instructions qui échange les valeurs de deux variables mutables de type int
.
Exercice 4-3 Calcul de la valeur d'une fonction polynomiale
Soit kitxmlcodeinlinelatexdvpf(x) =x^4 - 3x^3 - 2x^2 + x + 1finkitxmlcodeinlinelatexdvp
Réalisez un programme qui affiche la valeur de kitxmlcodeinlinelatexdvpf(x)finkitxmlcodeinlinelatexdvp lorsque kitxmlcodeinlinelatexdvpx = 13finkitxmlcodeinlinelatexdvp. Concevez votre programme pour qu'il soit facile de le modifier pour le calcul de kitxmlcodeinlinelatexdvpf(x)finkitxmlcodeinlinelatexdvp pour d'autres valeurs de kitxmlcodeinlinelatexdvpxfinkitxmlcodeinlinelatexdvp.
Cette dernière solution suit le schéma de Horner d'évaluation d'un polynôme.
Exercice 4-4 Avec les cartes
Réalisez des programmes qui, à partir de la situation initiale
Situation initiale :
Tas 1 : "[K+T+P+C]"
Tas 2 : ""
Tas 3 : ""
Tas 4 : ""
- calcule le nombre de trèfles ;
- calcule le nombre de cartes de couleur noire ;
- répartit équitablement les cartes rouges sur les tas 3 et 4, et les cartes noires sur les tas 1 et 2.