Vous êtes sur la page 1sur 68

PROGRAMMATION EN

VISUAL BASIC

RAKOTOARISOA Tahiana
Décembre 2006
Je dédie ce livre à
la mémoire de mon arrière–grand–père
Nicolas Chantereau
dit « le bourru »

petit paysan sans propriété


dans un obscur hameau du Limousin
homme sans diplôme
ni reconnaissance officielle de son savoir
et connu pourtant dans tout le canton
pour le savoir–faire universel
qu’il manifestait au service de tous.
(Michel GAUTHIER – ADA, Un apprentissage)

2
INTRODUCTION

Entendre ou lire sans réfléchir est une occupation vaine,


réfléchir sans livre ni maître est dangereux
(CONFUCIUS – Analectes, 2 : 15)

Ce manuel est un résumé de la syntaxe du langage Visual Basic. Nous utilisons le VB 6.0 de
Microsoft sur Windows XP Professionnel. Dans la présentation de la syntaxe de ce langage
(son grammaire), nous utilisons les conventions suivantes
– les textes qui doivent être écrits tels quels (terminaux) sont en courier new gras
– les noms qui doivent être remplacés par la définition correspondante sont en courier
new normal
– la barre verticale | dénote le choix
– la virgule , dénote la concaténation
– les éléments entre crochets [ et ] sont obligatoires
– les éléments entre parenthèses ( et ) sont facultatifs
– les éléments précédés d’un étoile * peuvent être répétés 0, 1 ou plusieurs fois

Le corps de texte est en Times New Roman normal. Les nouveaux termes et les mises en
garde sont en Times New Roman gras. Les programmes et les noms de variables sont en
Courier New normal noir. Les commentaires dans les programmes sont en Courier New
normal gris. Les textes affichés par l’ordinateur sont en Courier New gras noir. Dans la
présentation des programmes et fonctions prédéfinis, nous n’indiquons pas les noms des
paramètres pour être plus concis. Les opérateurs sont aussi placés avant ou entre les
paramètres.

sub ( bool = bool )

Certaines fonctions ou programmes, comme l’affectation ci–dessus, ne sont pas définies


formellement ainsi.

3
1 ère PARTIE. STRUCTURE D’UN ORDINATEUR
Numération

Le problème de la numération est celui de l’écriture de tous les nombres avec un ensemble
fini de symboles appelés chiffres 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C,… Le nombre x noté 
unun-1...u1u0,u-1u-2... qui est une suite de chiffres représente un polynôme en a, a étant
la base de numération

x = un an + un-1 an-1 + ... u1 a + u0 + u-1 a-1 + u-2 a-2 + ...

Par exemple, dans le système décimal (base 10)

42,785 = 4*101 + 2*100 + 7*10-1 + 8*10-2 + 5*10-3


= 4*10 + 2*1 + 7*0,1 + 8*0,01 + 5*0,001
= 40 + 2 + 0,7 + 0,08 + 0,005

Le nombre 43,12 écrit en base 5 est égal à

43,125 = 4*51 + 3*50 + 1*5-1 + 2*5-2 + 5*5-3


= 4*5 + 3*1 + 1*0,2 + 2*0,04 + 5*0,008
= 20 + 3 + 0,2 + 0,08 + 0,04
= 23,32

Le nombre 453,7 est écrit en base en 10. Pour l’écrire en base 7, calculons le nombre x tel
que 7x ≤ 453,7 < 7x+1 : x est la partie entière du logarithme base 7 de 453,7

log7 453,7 = log 453,7 / log 7 ≈ 3,143 x = 3

Divisons alors 453,7 par 73, puis le reste de cette division par 72, etc.

453,7 ÷ 73 = 1 reste 110,7


2
110,7 ÷ 7 = 2 reste 12,7
1
12,7 ÷ 7 = 1 reste 5,7
0
5,7 ÷ 7 = 5 reste 0,7
-1
0,7 ÷ 7 = 4 reste 0,128
-2
0,128 ÷ 7 = 6 reste 6,122.10-3
6,122.10-3 ÷ 7-3 = 2 reste 2,915.10-4 etc.

453,71010 = 1215,46277

Booléen

Une variable binaire (ou booléenne, de George Bool, logicien et mathématicien britannique)
est une variable qui peut prendre 2 valeurs seulement, notées { 0 ; 1 } ou { faux ;
vrai } ou { non ; oui }. Il existe 4 fonctions booléennes avec un seul paramètre booléen
c’est–à–dire de type

4
f : B → B où B = { 0 ; 1 }
x → y

Ces fonctions sont

x 0 1
f0(x) 0 0 constante 0
f1(x) 0 1 fonction identité
f2(x) 1 0 ¬ x, non x
f3(x) 1 1 constante 1

Plus généralement, il existe (card B)card A


fonctions de type

f : A → B

où card A est le nombre d’éléments de l’ensemble A et

card ( A × B ) = card A * card B

où × désigne le produit cartésien de 2 ensembles. Il existe alors 16 (22*2) fonctions de type

f : B × B → B
(x1,x2)→ y

x1 0 0 1 1 x1 0 0 1 1
x2 0 1 0 1 x2 0 1 0 1
f0(x1,x2) 0 0 0 0 constante 0 fF(x1,x2) 1 1 1 1 constante 1
f1(x1,x2) 0 0 0 1 x1 et x2, x1*x2 fE(x1,x2) 1 1 1 0 ¬(x1 et x2)
f2(x1,x2) 0 0 1 0 x1 > x2 (0 = non) fD(x1,x2) 1 1 0 1 x1 ≤ x2, x1 x2
f3(x1,x2) 0 0 1 1 x1 fC(x1,x2) 1 1 0 0 non x1
f4(x1,x2) 0 1 0 0 x1 < x2 fB(x1,x2) 1 0 1 1 x1 ≥ x2
f5(x1,x2) 0 1 0 1 x2 fA(x1,x2) 1 0 1 0 non x2
f6(x1,x2) 0 1 1 0 x1 ≠ x2 f9(x1,x2) 1 0 0 1 x1 = x2, x1 x2
f7(x1,x2) 0 1 1 1 x1 ou x2, x1+x2 f8(x1,x2) 1 0 0 0 ¬(x1 ou x2)

En VB, un booléen est représenté sur 16 bits

boolean ' 16 bits, 0 ou -1

Un nom sert à distinguer une variable. Un nom est formé d’une lettre suivi éventuellement
par des lettres, des chiffres ou des _ et ne doivent pas contenir des mots–clés (255 caractères
au maximum)

cNom = lettre,*(lettre|_|chiffre)
lettre := a | b | c | d | e | f | g | h | i | j |
k | l | m | n | o | p | q | r | s | t |
u | v | w | x | y | z
A | B | C | D | E | F | G | H | I | J |
K | L | M | N | O | P | Q | R | S | T |
U | V | W | X | Y | Z

5
chiffre := 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9

Pour créer une variable, il suffit d’écrire dim, son nom, as et son type

dim b9 as boolean

Un littéral booléen est formé par false ou true.

cbit = false | true

Le programme

sub ( boolean = boolean )

copie la valeur d’un booléen à un autre booléen.

b9 = 0

VB n’est pas sensible aux majuscules et minuscules

dim b_5 as bOoLean


B_5 = 0

Lorsque d'autres types de données numériques sont convertis en valeurs de type boolean, 0
devient false et toutes les autres valeurs deviennent true. Lorsque des valeurs de type
boolean sont converties en d'autres types de données, false devient 0 et true devient -1.
Les fonction suivantes sont définies sur les booléens

function not ( boolean ) as boolean '¬ x


function ( boolean and boolean ) as boolean 'x1 et x2
function ( boolean or boolean ) as boolean 'x1 ou x2
function ( boolean xor boolean ) as boolean 'x1 ≠ x2
function ( boolean eqv boolean ) as boolean 'x1 = x2
function ( boolean imp boolean ) as boolean 'x1 ≤ x2

Les fonctions

function cStr ( boolean ) as string


function cBool ( string ) as boolean

convertissent un booléen en texte (Faux ou Vrai si Windows est en français) et vice versa.
cBool accepte faux, vrai, false et true et même des littéraux nombres.

Entier naturel

La majorité des ordinateurs numériques électroniques utilisent le système de numération en


base 2 (système binaire) car il est facile de distinguer deux valeurs seulement. Par exemple, 0
est représenté par l’absence de courant et 1 par la présence de courant.

D’autre part, puisque les circuits de calculs sont « gravés » une fois pour toutes sur
les « puces », il faut également utiliser un nombre fini de chiffres binaires ou bit (de l’anglais
« binary digit »). Avec 3 bits, on peut manipuler les nombres allant de 0 à 7
0 0 0 0

6
0 0 1 1
0 1 0 2
0 1 1 3
1 0 0 4
1 0 1 5
1 1 0 6
1 1 1 7

Avec n bits, on peut manipuler des nombres allant de 0 à 2n-1. VB possède le type

byte ' 8 bits, 0 à 255

Les textes après ' ou rem jusqu’à la fin de ligne sont des commentaires et ils sont ignorés par
le compilateur. Exemple : pour créer un entier naturel sur 8 bits, nous écrivons :

dim n as byte

Un littéral entier naturelen base 10 est formé par une suite de chiffres 0 à 9. Un littéral entier
relatif en base 8 commence par un &O ou &o suivi par une suite de chiffres 0 à 7. Un littéral
entier relatif en base 16 commence par un &H ou &h suivi par une suite de chiffres 0 à 9 ou de
lettres A à F ou a à f.

cNat = cDec,*cDec
| &o,cOct,*cOct
| &h,cHex,*cHex
cDec = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
cOct = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
cHex = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
a | b | c | d | e | f | A | B | C | D | E | F

Les programmes

sub ( byte = byte )

copient la valeur d’un entier relatif à un autre entier relatif. Les instructions suivantes sont
équivalentes

n = 123
n = &o173
n = &h7B

Les fonctions suivantes sont définies sur les entiers relatifs

'comparaison :
function ( byte = byte ) as boolean 'égal
function ( byte <> byte ) as boolean 'différent
function ( byte < byte ) as boolean 'inférieur
function ( byte > byte ) as boolean 'inférieur ou égal
function ( byte <= byte ) as boolean 'inférieur ou égal
function ( byte >= byte ) as boolean 'supérieur ou égal
'arithmétique :
function ( byte + byte ) as byte 'addition
function ( byte - byte ) as byte 'soustraction
function ( byte * byte ) as byte 'multiplication

7
function ( byte / byte ) as byte 'division
function ( byte \ byte ) as byte 'quotient entier
function ( byte mod byte ) as byte 'reste de la division entière
function ( byte ^ byte ) as byte 'puissance

Nous utilisons une étoile * pour la multiplication pour ne pas confondre le croix × avec la
lettre x. Les comparaisons sont évaluées après les fonctions arithmétiques. Les additions et
soustractions sont évaluées après les multiplications et divisions, qui sont évaluées après la
puissance. Les instructions suivantes sont équivalentes

n = 0 = 1 + 2 * 3 ^ 4
n = ( 0 = ( 1 + ( 2 * ( 3 ^ 4 ) ) ) )

Les fonctions

function cStr ( byte ) as string


function oct ( byte ) as string 'conv. en base 8
function hex ( byte ) as string 'conv. en base 16
function cByte ( string ) as byte

convertissent un octet en son équivalent texte et vice–versa. oct renvoie un long entre
200 0000 0000 et 177 7777 7777 sans le préfixe &o, hex renvoie un long entre 8000 0000
et 7FFF FFFF sans le préfixe &h. cByte accepte des littéraux réels dans le texte écrits avec la
syntaxe de Windows. Si vous avez spécifié dans Panneau de configuration\Options
régionales et lingistique puis sur le bouton Personnaliser que le symbole décimal
est la virgule et que symbole de groupement des chiffres est l’espace, alors il faut utiliser une
virgule (au lieu d’un point) et on peut entrer des espaces n’importe où dans la mantisse. Mais
si vous entrer des littéraux octaux ou héxadécimaux, alors il faut suivre la syntaxe de VB (pas
d’espace).

Entier relatif

Pour représenter des nombres négatifs, nous ajoutons un bit supplémentaire pour le signe

0 1 1 0 1 0 0 0
valeur absolue
signe

Si les circuits d’addition pour les entiers naturels sont également utilisés pour les entiers
négatifs, le résultat est faux

1 1
0 1 1 0 6
+ 1 0 1 0 + (-2)

= 1 0 0 0 0 0 ?
? 0 ?

Une solution consiste à calculer la négation de chaque bit, y compris le signe, pour
représenter l’opposé d’un nombre donné

8
1
2 0 0 1 0 0 1 1 0 6

(-2) 1 1 0 1 + 1 1 0 1 + (-2)
3 ?
= 1 0 0 1 1
+ 3 ? ?

Il faudrait ajouter la retenue 1 au résultat. Plus facilement, nous ajoutons ce 1 lors du calcul
de l’opposé plutôt que lors de l’addition et la retenue est alors ignorée

1 1
2 0 0 1 0 0 1 1 0 6

(-2) 1 1 1 0 + 1 1 1 0 + (-2)

= 1 0 1 1 0 4
+ 4 ?

Il existe deux types entier relatif en VB

integer ' 16 bits, -32 768 à 32 767


long ' 32 bits, -2 147 483 648 à 2 147 483 647

Exemple : pour créer un entier relatif sur 16 bits, nous écrivons :

dim n as integer

Un littéral entier relatif en base 10 est formé par un signe + ou – (éventuellement), une suite
de chiffres 0 à 9. Un littéral entier relatif en base 8 commence par un &O ou &o suivi par une
suite de chiffres 0 à 7 (il n’y a pas de signe!). Un littéral entier relatif en base 16 commence
par un &H ou &h suivi par une suite de chiffres 0 à 9 ou de lettres A à F ou a à f (il n’y a pas
de signe!).

cEnt = (+|-),cDec,*cDec
| &o,cOct,*cOct
| &h,cHex,*cHex

Si le signe est +, il peut être omis. Les programmes

sub ( integer = integer )


sub ( long = long )

copient la valeur d’un entier relatif à un autre entier relatif. Les instructions suivantes sont
équivalentes

n = -123456
n = &o37777416700
n = &hFFFE1DC0

On peut affecter des entiers relatifs de précisions différentes

9
sub ( long = integer )
sub ( integer  = long ) 'etc

Les fonctions suivantes sont définies sur les entiers relatifs

'comparaison :
function ( integer = integer ) as boolean 'égal
function ( integer <> integer ) as boolean 'différent
function ( integer < integer ) as boolean 'inférieur
function ( integer > integer ) as boolean 'inférieur ou égal
function ( integer <= integer ) as boolean 'inférieur ou égal
function ( integer >= integer ) as boolean 'supérieur ou égal
'arithmétique :
function ( integer + integer ) as integer 'addition
function ( integer - integer ) as integer 'soustraction
function ( integer * integer ) as integer 'multiplication
function ( integer / integer ) as integer 'division
function ( integer \ integer ) as integer 'quotient entier
function ( integer mod integer ) as integer 'reste
function ( integer ^ integer ) as integer 'puissance

'comparaison :
function ( long = long ) as boolean 'égal
function ( long <> long ) as boolean 'différent
function ( long < long ) as boolean 'inférieur
function ( long > long ) as boolean 'inférieur ou égal
function ( long <= long ) as boolean 'inférieur ou égal
function ( long >= long ) as boolean 'supérieur ou égal
'arithmétique :
function ( long + long ) as long 'addition
function ( long - long ) as long 'soustraction
function ( long * long ) as long 'multiplication
function ( long / long ) as long 'division
function ( long \ long ) as long 'quotient entier
function ( long mod long ) as long 'reste de la division entière
function ( long ^ long ) as long 'puissance

On peut additionner, soustraire, etc. des entiers de précisions différentes, celui de plus faible
précision sera converti à la plus grande précision. Les fonctions

function cStr ( integer ) as string


function oct ( integer ) as string 'conv. en base 8
function hex ( integer ) as string 'conv. en base 16
function cInt ( string ) as integer

function cStr ( long ) as string


function oct ( long ) as string 'conv. en base 8
function hex ( long ) as string 'conv. en base 16
function cLng ( string ) as long

convertissent un entier relatif en son équivalent texte et vice–versa. oct renvoie jusqu’à 11
caractères octaux sans le préfixe &o, hex renvoie jusqu’à 8 caractères héxadécimaux sans le
préfixe &h. cInt et cLng acceptent des littéraux réels dans le texte et même des espaces.

10
Réel fixe

Si les circuits d’addition pour les entiers sont également utilisés pour les réels, alors il suffit
de fixer, par pure convention, la position de la virgule. On obtient ainsi des nombres en
virgule fixe

0 1 1 0 1 0 0 1 1 0 1 0 0 0 0 0
;
signe partie entière partie fractionnaire

110 1001 1010 00002 * 2-8 = 27 040 * 2-8 = 105,625

Dans la pratique, on utilise


- 32 bits avec 1 bit pour le signe, 15 pour la partie entière et 16 pour la fraction
- 32 bits avec 1 bit pour le signe, 1 pour la partie entière et 30 pour la fraction
VB possède le type

currency '64 bits, -922 337 203 685 477,5808 à


' 922 337 203 685 477,5807

qui utilise 64 bits. En fait, un currency est mémorisé dans un entier 64 bits, et il suffit de le
multiplier par 10 000 lors de la conversion en entier et le diviser par 10 000 lors de la
conversion en texte. Exemple : pour créer un réel fixe sur 64 bits, nous écrivons :

dim c as currency

Un littéral réel fixe est formé par un signe + ou – (éventuellement), une suite de chiffres 0 à
9, un point (éventuellement), une suite de chiffres 0 à 9 (éventuellement). Si les chiffres
avant ou après le point sont nuls, ils peuvent être omis

cFixe = (+|-),cDec,*cDec,.,cDec,*cDec
| (+|-),cDec,*cDec,.
| (+|-),.,cDec,*cDec

VB, qui est a été inventé par des américains, utilise des points là où les français utilisent des
virgules et vice–versa ! De plus, les américains n’aiment pas mettre un zéro avant le point. Le
programme

sub ( currency = currency )

copie un réel fixe à un autre réel fixe

c = .123

Les fonctions suivantes sont définies sur les réels fixes

'comparaison :
function ( currency = currency ) as boolean 'égal
function ( currency <> currency ) as boolean 'différent
function ( currency < currency ) as boolean 'inférieur
function ( currency > currency ) as boolean 'inférieur ou égal
function ( currency <= currency ) as boolean 'inférieur ou égal

11
function ( currency >= currency ) as boolean 'supérieur ou égal
'arithmétique :
function ( currency + currency ) as currency 'addition
function ( currency - currency ) as currency 'soustraction
function ( currency * currency ) as currency 'multiplication
function ( currency / currency ) as currency 'division
function ( currency \ currency ) as currency 'quotient entier
function ( currency mod currency ) as currency 'reste
function ( currency ^ currency ) as currency 'puissance

\ et mod convertissent d’abord les nombres en entiers avant de calculer le quotient ou le reste.

Réel flottant

Un réel fixe ne permet pas de manipuler les très grands nombres, car il faut toujours utiliser
un nombre fini de bits. La solution consiste à ne conserver que les chiffres les plus
significatifs

110 1001 1010 00002 = 110 10012 * 28 = 1,1010 012 * 214

Ces chiffres les plus significatifs s’appellent la mantisse, et la puissance de 2 (qu’il faut aussi
conserver) s’appelle l’exposant. On obtient ainsi des nombres à virgule flottante

0 1 1 0 1 0 0 1 0 1 1 1 0
;
mantisse exposant

Il existe deux types réel flottant en VB

single ' 32 bits, -3.4*1038 à 3.4*1038


double ' 64 bits, -1.7*10308 à 1.7*10308

Exemple : pour créer un réel flottant sur 64 bits, nous écrivons :

dim x64 as double

Un littéral réel flottant est formé par un signe + ou – (éventuellement), une suite de chiffres 0
à 9, un point (éventuellement), une suite de chiffres 0 à 9 (éventuellement), une lettre e ou E
(éventuellement), un signe + ou – (éventuellement), une suite de chiffres 0 à 9

cReel = (+|-),*cDec,(.),*cDec,(e|E,(+|-),*cDec)

Si le signe de la mantisse ou de l’exposant est +, il peut être omis. Si les chiffres avant ou
après le point sont tous nuls, ces chiffres peuvent être omis. Les programmes

sub ( single = single )


sub ( double = double )

copient la valeur d’un réel flottant à un autre réel flottant. Les instructions suivantes sont
équivalentes

n = +123456E-6

12
n = +123456.e-6
n = +.123456
n = .123456
n = 0.123456
n = 0.0123456e+1
n = 0.0123456e1

On peut affecter des réels flottants de précisions différentes

sub ( single = double )


sub ( double  = single )

On peut affecter des entiers à des réels flottants et vice–versa

sub ( integer = double )


sub ( double  = integer ) 'etc

Les fonctions suivantes sont définies sur les réels flottants

'comparaison :
function ( single = single ) as boolean 'égal
function ( single <> single ) as boolean 'différent
function ( single < single ) as boolean 'inférieur
function ( single > single ) as boolean 'inférieur ou égal
function ( single <= single ) as boolean 'inférieur ou égal
function ( single >= single ) as boolean 'supérieur ou égal
'arithmétique :
function ( single + single ) as single 'addition
function ( single - single ) as single 'soustraction
function ( single * single ) as single 'multiplication
function ( single / single ) as single 'division
function ( single \ single ) as single 'quotient entier
function ( single mod single ) as single 'reste
function ( single ^ single ) as single 'puissance

'comparaison :
function ( double = double ) as boolean 'égal
function ( double <> double ) as boolean 'différent
function ( double < double ) as boolean 'inférieur
function ( double > double ) as boolean 'inférieur ou égal
function ( double <= double ) as boolean 'inférieur ou égal
function ( double >= double ) as boolean 'supérieur ou égal
'arithmétique :
function ( double + double ) as double 'addition
function ( double - double ) as double 'soustraction
function ( double * double ) as double 'multiplication
function ( double / double ) as double 'division
function ( double \ double ) as double 'quotient entier
function ( double mod double ) as double 'reste
function ( double ^ double ) as double 'puissance
function sqr ( double ) as double ' racine carrée
function exp ( double ) as double ' exponentielle
function log ( double ) as double ' logarithme népérien
function sin ( double ) as double ' angle en radian
function cos ( double ) as double ' angle en radian
function tan ( double ) as double ' angle en radian
function atn ( double ) as double ' résultat entre ]-/2../2[ radian

13
On peut additionner, soustraire, etc. des réels de précisions différentes, celui de plus faible
précision sera converti à la plus grande précision.

Les fonctions

function cStr ( single ) as string


function cStr ( double ) as string
function cSng ( string ) as single
function cDbl ( string ) as double

convertissent un réel flottant en texte et vice–versa.

Caractère

Pour entrer un texte dans un ordinateur, il faudrait d’abord le remplacer par des 0 et des 1. Un
caractère est une lettre, un chiffre ou un symbole ( +, -, *, /, ., ; , ; , ?, etc.). Il faut au
minimum 6 ou 7 bits pour représenter un caractère puisque 26 (lettres) + 10 (chiffres) = 36 et
26 = 64. Ainsi l’ASCII ( ou « American Standard Code for Information Interchange ») utilise
8 bits dont les 128 premiers caractères sont

0 1 2 3 4 5 6 7 8 9 A B C D E F

0 NUL SOH STX ETX EOT ENQ ACQ BEL BS HT LF VT FF CR SO SI

1 DLE DC1 DC2 DC3 DC4 NAK SYN ETB CAN EM SUB ESC FS GS RS US

2 SP ! " # $ % & ’ ( ) * + , - . /

3 0 1 2 3 4 5 6 7 8 9 : ; < = > ?

4 @ A B C D E F G H I J K L M N O

5 P Q R S T U V W X Y Z [ \ ] ^ °

6 ` a b c d e f g h i j k l m n o

7 p q r s t u v w x y z { | } _ DEL

14
NUL Null DLE Data link escape
SOH Start of heading (début d’en–tête) DC1 Direct control 1
STX Start of text NAK Negative acknowledge
ETX End of text SYN Synchronous idle
EOT End of transmission ETB End of transmission block
ENQ Enquiry (demande) CAN Cancel
ACQ Acknowledge (accusé de récep°) EM End of medium (support)
BEL Bell (sonnerie) SUB Substitute
BS Backspace (retour arrière) ESC Escape
HT Horizontal tabulation FS File separator (sépar. de fichier)
LF Line feed (nouvelle ligne) GS Group separator
VT Vertical tabulation RS Record separator
FF Form feed (nouvelle page) US Unit separator (sous–article)
CR Carriage return (retour chariot) SP Space (espace)
SO Shift Out (hors code) DEL Delete (suppression)
SI Shift in (en code)

Exemple : le code du J est 4A en base 16 et 74 en base 10. Il n’y a pas de type caractère en
VB.

Opérateur et/ou

Il existe un moyen simple pour transformer n’importe quelle fonction binaire en n’utilisant
que les opérateurs et, ou et non. Considérons d’abord la table donnant les résultats de la
fonction (sa « table de vérité »)

0 1 2 3 4 5 6 7
a 0 0 0 0 1 1 1 1
b 0 0 1 1 0 0 1 1
c 0 1 0 1 0 1 0 1
f(a,b,c) 1 0 1 0 0 0 1 0

Prenons alors les colonnes pour lesquelles f(a;b;c) est égal à 1 (si les 1 sont plus nombreux
que les 0, il suffit de prendre les colonnes dont f(a;b;c) est égal à 0 et inverser ensuite la
formule finale). La colonne 2 signifie par exemple que lorsque

si a = 0 et b = 1 et c = 0 alors f(a,b,c)=1

pour que a*b*c = 1 il faut que ces 3 variables soient tous égal à 1. Il suffit alors de prendre
la négation des variables qui sont égales à 0 et la colonne 2 est égale à

(¬a)*b*(¬c) = (¬0)*1*(¬0) = 1

Nous faisons de même pour les colonnes 0 et 6, et la formule recherchée est la « somme » de
ces produits

f( a,b,c ) = (¬a)*(¬b)*(¬c) + (¬a)*b*(¬c) + a*b*(¬c)

en supposant que * est plus prioritaire que +.

15
Ordinateur câblé

Dans un ordinateur numérique câblé, chaque variable est véhiculée par un fil électrique et ne
peut prendre que 2 valeurs, par exemple 5 V et 0 V (présence et absence de courant). Pour
réaliser l’opérateur non, supposons que la variable peut actionner un électro–aimant : si un
courant passe, l’électroaimant peut repousser un interrupteur

a a

a
non a

non a

a = 1 ; non a = 0 a = 1 ; non a = 0
Nous simplifions ce schéma par

Pour réaliser l’opérateur et, il suffit de mettre en série les deux variables

a b
a et b

Enfin, l’opérateur ou est obtenu en mettant en parallèle les deux variables


a

a ou b
b

Exemple : le schéma de la fonction (¬a)*(¬b)*(¬c) + (¬a)*b*(¬c) + a*b*(¬c) est

a b c

a b c

a b c

En reliant ces 3 opérateurs, nous pouvons obtenir des fonctions plus complexes. Mais si nous
voulons faire d’autres calculs, il faut refaire tout le câblage.

16
Ordinateur programmé

Un ordinateur programmé est constitué par un ensemble fini d’opérateur (addition,


multiplication, comparaison, etc.) et ne peut réaliser qu’une seule opération à la fois. Chaque
opérateur possède un code pour l’identifier (addition = 0, multiplication = 1, etc.). Ce code
est utilisé pour envoyer les données vers le circuit de calcul approprié
bus d’
instruction

¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬

et et et et

données

+ * - /

résultat
0000 0001 0010 0011

Dans la pratique, le code d’instruction est constitué de 8 ou 16 bits. Les opérations à exécuter
et les résultats intermédiaires sont conservés dans une « mémoire » qui est constitué par des
bits placés côte à côte. Chaque élément possède un numéro (une adresse) pour l’identifier

bus d’
adresse

¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬ ¬

et et et et

bus de
données
processeur 1 0 0 1

0000 0001 0010 0011

Le processeur désigne l’ensemble des circuits de calculs. Dans la pratique, une adresse est
constituée de 16, 24 ou 32 bits et le bus de données est formé de 8, 16, 32 ou 64 bits : le
processeur peut donc lire ou écrire 8, 16, 32 ou 64 bits à la fois. Une instruction complète est
formée par un code (8 bits minimum) et, éventuellement, une donnée (64 bits maximum). Les
instructions plus larges que le bus de données sont lues en plusieurs étapes.

Instruction en langage machine

Certains processeurs possèdent une mémoire spéciale appelée registre accumulateur dans
lequel sont conservés un opérande et le résultat. Ce type de processeur peut exécuter les
instructions suivantes :
– charger x : mettre la donnée x dans l’accumulateur
– charger_adr a : mettre la donnée située à l’adresse a dans l’accumulateur

17
– add x : calculer la somme de x et de l’accumulateur et mettre le résultat dans
l’accumulateur
– add_adr a : additionner la donnée d’adresse a au contenu de l’accumulateur
– ranger a : copier le contenu de l’accumulateur dans la mémoire d’adresse a
En fait, il faut aussi indiquer si l’on doit lire un octet ( 8 bits) ou un entier(16, 32 ou 64 bits)
ou un réel (32 ou 64 bits), etc. Il existe alors plusieurs instructions charger :
– charger_octet x
– charger_ent16 x
– charger_ent32 x
– charger_octet_adr a
– charger_ent16_adr a
Comme un ordinateur ne sait manipuler que des 0 et des 1, une instruction doit être formée
par des 0 et des 1 également. Mais au lieu de manipuler réellement des 0 et des 1, nous
utilisons plutôt une représentation en base 16. Par exemple charger_ent16_adr 275 devient
A1 0000 0113 : cette instruction est composée de 5 octets (1 pour le code et 4 pour l’adresse)

A1 00 00 01 13
code paramètre

Pour faciliter encore l’entrée d’une instruction, nous utilisons des mots plus simples à retenir
(« mnémoniques ») et des nombres en base 10 : un programme appelé « assembleur » se
charge alors de les transformer en une suite de 0 et de 1.

Programme en langage machine

Un programme est formé par des instructions placées côte à côte dans la mémoire. Le
processeur possède un registre appelé pointeur d’instruction qui contient l’adresse de
l’instruction qu’il doit exécuter. Au démarrage de l’ordinateur, ce pointeur est égal à 0 : le
processeur commence toujours par exécuter l’instruction d’adresse 0. Après avoir exécuté
une instruction, le processeur augmente ce pointeur selon la longueur de l’instruction qu’il
vient d’exécuter. Par exemple, pour calculer la somme des entiers 16 bits situés à l’adresse
966 et 4205 et mettre le résultat à l’adresse 747

0 lire_ent16_adr ( 966 ) ;
5 add_ent16_adr ( 4205 ) ;
10 écrire_ent16 ;

Les nombres à gauche indiquent l’adresse de chaque instruction. Le processeur possède aussi
un registre de 8 ou 16 bits permettant de garder le résultat des comparaisons (pour conserver
l’accumulateur en vue d’une autre opération) ou de signaler un dépassement de capacité, etc.
– comp x : comparer x et l’accumulateur et mettre le résultat dans un indicateur
– saut_inf a : sauter à l’instruction d’adresse a si l’indicateur est égal à « inférieur »
sinon exécuter l’instruction après ce saut_inf (saut conditionnel)
– aller_en a : sauter à l’instruction d’adresse a (saut inconditionnel, « GOTO »)
Pour calculer la valeur absolue de l’entier à l’adresse 966 et mettre le résultat à l’adresse 747

0 lire_ent16_adr ( 966 ) ;
5 comp_ent16 ( 0 ) ;
10 saut_sup ( 16 ) ;
15 opposé ;
16 écrire_ent16 ( 747 ) ;

18
Comme nous pouvons le constater, l’indication des adresses des données ou des instructions
est très fastidieuse. Un « gestionnaire de mémoire » doit trouver un espace libre pour mettre
les instructions et les données. Les adresses sont remplacées par des mots : notre but est de
pouvoir écrire des formules telles qu’on utilise habituellement en mathématique en se servant
du clavier d’une machine à écrire. Le « traducteur » se charge alors de décomposer ces
instructions de « haut niveau ». Il existe deux types de traducteurs :
– un compilateur traduit le programme « source » en entier avant son exécution
– un interpréteur traduit chaque ligne que l’utilisateur a introduit et l’exécute
immédiatement sans mémoriser la forme exécutable
Un interpréteur est plus rapide si l’on apporte des modifications au programme mais un
compilateur est plus efficace s’il a des instructions répétées fréquemment. Le programme
source est un simple fichier texte avec une extension .cpp pour C++. Un programme peut
utiliser des programmes mémorisés dans d’autres fichiers, en général les fichiers d’en–tête
(header file, extension .h). Les programmes sources sont donc transformés par le compilateur
en une forme intermédiaire, appelé fichier objet (extension .o) où les adresses ne sont pas
encore fixées. Puis, un « éditeur de lien » et un « chargeur » assemble tous les fichiers objet
et calcule l’adresse de chaque instruction pour produire le fichier exécutable (extension .exe
sous Windows, sans extension sous Unix).

Source : En–tête :
xy_dist.cpp xy.h

Compilateur

Objet : Objet :
xy_dist.o xy.o

Éditeur de
liens

Exécutable :
xy_dist.exe

Structures de contrôle

Comme un processeur est constitué d’un nombre fini de circuits, il faut des moyens
permettant de combiner ses fonctions (ou instructions) élémentaires :

– L’enchaînement : nous séparons une instruction avec celle qui doit être exécutée ensuite par
un retour chariot

dim x as double ' x  R x

x = 9+0.1234*2 ' x = 9+0,123.4×2 x 9,2468

19
ou par un deux–point, si les instructions tiennent sur une seule ligne

dim x as double : x = 9+0.1234*2


Si une instruction est plus longue qu’une seule ligne, il faut terminer la ligne par un espace et
un caractère de soulignement _

dim _
x as double

– La sélection : pour calculer la valeur absolue d’un nombre x

if x <= 0 then ' x ≤ 0


y = (x)
else
y = x
end if

Les décalages à droite (indentation) permettent d’éviter les erreurs. S’il n’y rien à exécuter
dans la branche else, alors on peut la supprimer

y = x
if x <= 0 then ' x ≤ 0
y = (x)
end if

end if est omis si l’instruction tient sur une seule ligne

y = x
if x <= 0 then y = (-x)

On peut aussi utiliser plusieurs conditions. Exemple : nombre de solutions d’une équation
réelle du 2nd degré

d = b*b - 4*a*c
if d < 0 then
nb = 0
elseIf d = 0 then
nb = 1
x1 = (-b)/(2*a)
else ' d > 0
nb = 2
x1 = (-b-sqr(d))/(2*a)
x2 = (-b+sqr(d))/(2*a)
end if

– La répétition : pour calculer la puissance entière d’un nombre a

y = 1 : i = 1 ' y = ab


do
if i > b then exit do
y = y*a : i = i+1
loop

20
Après les initialisations, i est comparé à b. Si i est supérieur à n alors, on sort de la
« boucle », c’est–à–dire que l’on saute à l’instruction après le loop (ici, il n’y a rien et le
calcul s’arrête). Sinon, y est multiplié par a, puis i est incrémenté de 1 et on revient au début
de la boucle, c’est–à–dire au test i > b. On peut mettre plusieurs instructions exit do dans
une boucle. La condition de sortie étant la première instruction, on peut écrire
y = 1 : i = 1 ' y = ab
while i > b
y = y*a : i = i+1
wend

De plus, comme i augmente régulièrement par pas de 1, nous pouvons écrire

y = 1
for i = 1 to b step 1 ' i  [1,b] faire ...
y = y*a
next

Le pas étant égal à 1, il peut être omis

y = 1
for i = 1 to b ' i  [1,b] faire ...
y = y*a
next

Fonction

Une fonction définie en mathématique par

tangente : R → R
x → sin x / cos x

s’écrit

function tangente ( x as double ) as double


tangente = sin(x)/cos(x)
end function

Nous avons groupé la déclaration et l’initialisation de la fonction. Pour les fonctions qui ne
peuvent pas être définies par une seule formule

function absolue( x as double ) {


if x < 0 then
absolue = (-x)
else
absolue = x
end if
end function

Attention ! Les déclarations de variables locales doivent apparaître avant les


instructions exécutables.

integer f(integer x ) {
integer a
a = 1
integer b ; ' etc.

21
}

provoquera une erreur. Les variables locales sont détruites à la fin de la fonction. Une
fonction peut faire appel à elle–même (fonction récursive). Exemple : calcul de la factorielle

function fac ( n as integer ) as double


' double pour les grandes valeurs
if ( x <= 1 ) then
fac = 1
else
fac = n*fac(n-1)
end if
end function

Après avoir défini une fonction, nous pouvons l’utiliser dans une formule (ou expression)

t = 1 + tan(3.14)*2

Concrètement, une fonction est une liste d’instructions en langage machine, c’est–à–dire un
pointeur. En utilisant des aller_en et des sauts conditionnels, ces instructions ne sont pas
forcément mises côte à côte dans la mémoire.

Programme

Si un paramètre est à la fois donnée et résultat, s’il n’y a que des résultats, ou s’il n’y a aucun
paramètre, nous utilisons alors des programmes. Pour indiquer qu’un paramètre est une
donnée, il faut précéder son nom par byVal

sub add ( x as double, byVal a as double )


x = x + a
end sub

Après avoir défini un programme, nous pouvons l’utiliser (l’appeler) dans une instruction.

dim w as double
w = 6
add ( w, 1 )

Adresse

Une adresse (ou une référence ou un pointeur) est un numéro permettant de distinguer chaque
élément (bit ou octet) de la mémoire. Si la mémoire est composée de n éléments, alors une
adresse doit être composée au minimum de log2 n bits. Dans la pratique, on utilise un
multiple de 8 tel que 16, 24, 32 ou 64 bits. En C++, la fonction new cherche un endroit libre
capable de contenir le type de données spécifié dans la mémoire centrale. La fonction delete
libère la mémoire pointée par une adresse. La fonction & retourne l’adresse d’une variable

22
double *a, *b ; a

a = new double ; a

a 1.5
*a = 1.5 ;
a 1.5 x 1.5
x = *a ;

b = a ; a 1.5 x 1.5

delete [] a ; a x 1.5

b
x
a = &x ; a 1.5

b « pointe » maintenant sur une partie de la mémoire qui est déjà libérée. On dit que c’est une
référence folle et une telle situation doit être évitée. Il n’y a pas de type adresse en VB.

Article

Les multiplets (au sens mathématique du terme) sont représentés par des articles (ou des
enregistrements). Les parties (ou champs) sont placées côte à côte dans la mémoire. Il n’y a
pas de littéral article. Il faut accéder aux champs un par un. On ne peut pas tester l’égalité de
deux articles (x1 = x2). De même, l’affichage d’un article doit se faire champ par champ

type ent signe absolue


signe as string*1 x y
absolue as byte
end type
dim y as byte
dim x as ent x - 4 y
x.signe = "-"
x.absolue = 4
x.signe = "+" x + 4 y

x.absolue = 252 x + 252 y

y = x.absolue x + 252 y 252

Répertoire

Un répertoire est un article spécial permettant de grouper sur des « unités » (disquette, disque
dur, CD, DVD, flash disk, etc.), les objets (fichiers et répertoires) créés par l’utilisateur. À
l’installation de Windows, il détecte automatiquement les unités et partitions (un disque peut
être divisé en plusieurs partitions) existantes sur l’ordinateur et leur attribue une lettre pour
les identifier (a: pour le premier lecteur de disquette, b: pour le deuxième, c: pour le

23
premier disque dur, d: pour le lecteur CD, etc. : le nom d’une unité est toujours suivi d’un
deux–points). Il crée aussi un répertoire nommé Documents and Settings dans le disque c:
(un nom en Windows peut contenir un espace ). Puis, il crée un répertoire pour chaque
utilisateur (p. ex Tah) dans ce c:\Documents and Settings. (Windows utilise un
backslash \ pour accéder aux « champ » d’un répertoire et non pas un point comme C++). Au
démarrage de l’ordinateur, l’unité courante est celle qui contient Windows (p. ex c:). Quant
l’utilisateur Tah entre une session sur l’ordinateur, le répertoire courant est

c:\Documents and Settings\Tah

Administrateur
Documents and Settings
Tah
...
a: BC5
c: Program Files Office
Microsoft Office
Template
...
addins
Windows
AppPatch
...
tel qu’il est écrit dans la fenêtre de l’Invite de commande (Démarrer\Tous les
programmes\Accessoires\Invite de commande). Pour changer le répertoire courant, nous
utilisons la commande cd ou chdir (change directory)

cd c:\Program Files
C:\Program Files>_

(La plupart des programmes sont installés dans le répertoire c:\Program Files). Windows
n’est pas sensible aux minuscules et majuscules. On aurait put écrire

cd C:\pRogram files

Pour aller au répertoire racine (dans le disque c:)

cd \
C:\>_

Pour créer un répertoire, nous utilisons la commande md ou mkdir (make directory)

md Tah

Administrateur
Documents and Settings
Tah
...
a: Program Files BC5
Office
c: Microsoft Office
Template
Tah ...
addins
Windows
AppPatch
...
Pour supprimer un répertoire, nous utilisons la commande rd ou rmdir (remove directory)

24
rd Tah

Pour afficher le contenu d’un répertoire, nous utilisons la commande dir (directory)

dir \

Le volume dans le lecteur C s'appelle DISQUE DUR


Le numéro de série du volume est 1574-0BF7

Répertoire de C:\

2006-02-16 14:53 <REP> Documents and Settings


2006-08-10 03:36 <REP> Program Files
2006-08-10 04:38 <REP> Tah
2006-08-10 03:03 <REP> WINDOWS
0 fichier(s) 0 octets
4 Rép(s) 655 175 680 octets libres

dir sans aucun paramètre affiche le contenu du répertoire courant. La notion de répertoire
courant est très importante. Tout fichier ou répertoire en dehors du répertoire courant est
invisible pour Windows sauf si vous précisez son nom complet. Exemple :

cd c:\Documents and Settings\Tah


dir Program Files

provoquera

Fichier introuvable

Pour les programmes (fichier exécutables .exe, .com, etc.), nous pouvons encore indiquer
dans quel répertoire, Windows doit les chercher s’ils ne se trouvent pas dans le répertoire
courant via la commande path

path c:\tah ; %path%

ajoute le répertoire c:\tah aux répertoire de recherches existants. path sans paramètres
affiche les répertoires de recherches actuels. Mais lorsque vous fermez l’invite de
commandes, les répertoires de recherche reviennent à leurs états précédents. Il vaut mieux
utiliser Démarrer\Panneau de configuration\Système\Avancé\Variables d’environn
ement, sélectionner Path, puis ajouter le répertoire dans Valeur de la variable. Les deux
Path de Variables utilisateur et Variables système forment le Path de l’invite de
commande.

Tapez help pour obtenir la liste de toutes les commandes Windows disponibles. Tapez help
suivi du nom de la commande pour obtenir plus de détails sur l’utilisation d’une commande
donnée. Exemple :

help prompt

25
Programme principal

Un programme « source » en C++ est un fichier texte ASCII avec l’extension .cpp. Ce
fichier doit contenir un programme spécial appelé main. Si ce programme utilise des
programmes, des types, des constantes, etc. contenus dans d’autres fichiers appelés fichiers
d’en–tête (header files), le nom de ces fichiers devront être indiqués dans le fichier .cpp.
Exemple : pour calculer 1+2

' fichier c_12.cpp


#include <stdio.h>
void main() {
printf ( "%u", 1+2 )
}

Nous supposons que ce fichier s’appele c_12.cpp et se trouve dans le répertoire c:\tah\c\
debug\source\cours. Le programme printf se trouve dans le fichier d’en–tête stdio.h
qui se trouve dans le répertoire d:\bc5\include.Ce fichier source produira un fichier objet
c_12.obj que nous allons mettre dans c:\tah\c\debug\object et un fichier exécutable
c_12.exe, à mettre dans c:\tah\c\debug\exe. Il faut alors indiquer au compilateur tous
ces répertoires via le menu Option\Project\Directories

Source Directories :
Include: d:\bc5\include ; c:\tah\c\lib\source
Library: d:\bc5\lib
Source:
Output Directories :
Intermediate: c:\tah\c\debug\object
Final: c:\tah\c\debug\exe

(Nous allons mettre les types et les programmes qui les manipulent dans c:\tah\c\lib\
source). Ensuite nous lançons la compilation par le menu Project\Build all ou par le
bouton Build project. Si vous lancez le fichier exécutable via le bouton Run, vous verrez
une fenêtre qui s’affiche pendant un dixième de secondes ! Il faut alors ouvrir une fenêtre
Invite de commande puis taper le nom du fichier exécutable

C:\Documents and Settings\Tah> c_12


3
C:\Documents and Settings\Tah>_

Classe

Une classe est un article spécial : une classe peut avoir comme paramètre une autre classe ou
un objet quelconque (entier, etc.). Le type de ces paramètres sont listés après le mot
template avant la liste des champs. Par exemple, pour créer le type point xy en 2 dimensions
en coordonnées cartésiennes (dont les cordonnées peuvent être des entiers ou des réels)

' fichier xy.h


template<class item>
class xy { public :
item x
item y
}

26
Il faut déclarer que les champs x et y sont public, sinon ils ne seront pas accessibles aux
programmes définis en dehors de la classe xy : en plus de champs, une classe peut aussi
contenir des programmes. Pour créer un programme sur une classe paramétrée, il faut aussi
précéder la déclaration du programme par le mot template et la liste des classes susceptibles
d’être utilisées. Exemple : calcul du distance entre deux points

' suite fichier xy.h


template <class item>
item dist ( xy<item> a, xy<item> b ) {
item dx, dy
dx = b.x-a.x
dy = b.y-a.y
return sqrt((dx*dx)+(dy*dy))
}

Pour calculer la distance de deux points, nous écrivons dans un fichier appelé xy_dist.cpp

' fichier xy_dist.cpp


#include <xy.h>
#include <number.h>
void main() {
xy<double> a, b, c
a.x = 1 ; a.y = 2 ; b.x = 3 ; b.y = 4
put ( dist ( a, b ) )
} ;

Nous supposons que le fichier number.h contient les programmes d’affichages des nombres

' fichier number.h


#include <stdio.h>

void put ( double x ) {


printf ( "%.15g", x ) ; }

Le nom put vient du nom du programme d’affichage en Ada : nous avons commencé à écrire
des programmes en Ada avant de basculer en C++, en utilisant intensivement le fameux
copier–coller ! De même, le nom item est issu du Manuel de Référence de Ada 95.

27
2ème PARTIE. STRUCTURES DE DONNÉES
Tableau

Un vecteur (ou un tableau à une dimension) est une fonction, au sens mathématique du terme,
dont l’ensemble de départ est un intervalle d’entiers (ou de réels en virgule fixe ou même de
caractères)

v : [1..7]*ent ;

Comme une fonction v peut être définie par l’ensemble des couples (i;v(i)) nous pouvons
écrire

v = ((1;5);(2;1);(3;2);(4;5);(5;0);(6;6);(7;3)) ;

Ces couples peuvent être mis côte à côte dans la mémoire dans l’ordre ci–dessus. v est
l’adresse du premier couple

v 47 1 5 2 1 3 2 4 5 5 0 6 6 7 3

Il devient inutile de mémoriser aussi les éléments de départ (les indices) puisque l’on peut
calculer facilement l’adresse des images

V = ( 5 ; 1 ; 2 ; 5 ; 0 ; 6 ; 7 )
1 2 3 4 5 6 7 i
v 47 5 1 2 5 0 6 3 v(i)
47 49 51 53 55 57 59 adresse
v : [a..b]*t ;
adr v ( i ) = v + ( i – a ) * taille t ;

où taille t est le nombre d’octets (ou de bits) occupés par un objet de type t.

taille ent = 2 ; adr v(5) = 47 + ( 5 – 1 ) * 2 = 55

En C, l’ensemble de départ commence toujours par 0, et nous indiquons le nombre


d’éléments du tableau au lieu de l’intervalle de départ. Il n’y a pas de littéral tableau sauf lors
de la déclaration et l’initialisation combinée

integer v[7] = {5, 1, 2, 5, 0, 6, 3}

Nous pouvons maintenant lire ou modifier l’image d’un élément donné


1 2 3 4 5 6 7
x = v [ 3 ] ; v 5 1 2 5 0 6 3 x 2

v [ 4 ] = 9 ; v 5 1 2 9 0 6 3 x 2

u = v * 2 ; u 10 2 4 18 0 12 6

v = v + u ; v 15 3 6 27 0 18 9

28
Attention ! La fonction sizeof est applicable dans le programme où le tableau a été
déclaré mais elle n’est plus utilisable si le tableau est passé en paramètre ! Il faudrait
alors mémoriser cette taille dans un champ

template <class t>


class vec { public :
t* info
integer card
}

Exercices :

- affichage d’un vecteur


- somme des éléments d’un vecteur de nombres (le résultat est un nombre)
- somme de deux vecteurs (le résultat est un vecteur)
- produit de deux vecteurs (le résultat est un nombre)
- produit d’un vecteur et d’un nombre (le résultat est un vecteur)

Une matrice (ou tableau à deux dimensions) est un vecteur de vecteur

integer m[3][7] =
1 2 3 4 5 6 7
{ { 5, 1, 2, 5, 0, 6, 3 },
5 { 110,22, 54, 018, 60, 12,106 },
2 -1 18 1
0 12 65 1 2 5 0 6 3
{ 0, 0, 0, 0, 0, 0, 0 } } ; 2 10 2 4 18 0 12 6
3 0 0 0 0 0 0 0
1 2 3 4 5 6 7
m [ 2 ][ 3 ] = (-1) ; 1
5 1 2 5 0 6 3
2
10 2 -1 18 0 12 6
3
0 0 0 0 0 0 0
Pour mémoriser une matrice nous pouvons le conserver ligne par ligne

ou colonne par colonne

5 10 0 1 2 0 2 -1 0 5 18 0

Exercices :

- affichage d’une matrice


- somme de deux matrices
- produit de deux matrices
- produit d’une matrice et d’un vecteur
- transposée d’une matrice

Liste chaînée

Une liste est un groupe d’élément de même type tel que chaque élément possède une valeur,
un seul successeur (sauf le dernier) et un seul prédécesseur (sauf le premier). Le successeur
(ou le prédécesseur) d’un élément est différent de celui d’un autre élément.

29
5 1 2 5 9

Dans une liste chaînée, chaque élément possède l’adresse de l’élément suivant (et,
éventuellement, celle de l’élément précédent). La liste proprement dite est l’adresse du
premier élément

template <class t> ;


class liste { public :
t info ; liste<t> *alt ;
} ;

liste<integer> *l ; l

l = NULL ; /*liste vide*/ l


info alt
ins1(2,l); ins1(1,l) l 5 1 2
ins1(5,l);

x = l ; l 5 1 2
y = x->info ;
x y 5

x = x->alt ; l 5 1 2
y = x->info ;
x y 1

On aurait aimé écrire quelque chose du genre

template <class t> ;


class liste { public :
t info ; liste_<t> alt }*

liste<integer> l

ce qui aurait évité d’écrire un * à chaque déclaration d’une liste. Malheureusement, C++ ne
permet pas d’utiliser un pointeur sur une classe. La seule solution serait d’écrire une classe
avec un champ unique

template <class t>


class cell { public :
t info ; cell<t>* alt
}
template <class t>
class liste { public :
cell<t>* tete
}

Exemple : insertion d’un élément en tête de liste

30
template<class t> info alt
void ins1(t x ; liste<t>* l ){ l 5 1 2
liste<t>* p ; t
p
liste<t>* p an
l t
pp->info
= new liste<t> 5
aih 1 2
= x; ; an
aT
p 0
aih
,"i
aTt
l 5
an 1 2
p->alt = l ; an
,"i
p = new liste<t> p a")
0
aih
an
;tt
aT
a")
l 5 1 2
l=p;}; an
,"i
;t
1ltemplate<class t> p 0
aih
an
void ins1(t x ; liste<t>* l ){ aTt
a")
liste<t>* p ; an
,"i
;t
p = new liste<t> ; aih
an
aT
a")
,"i
;t
an
a")
;t
Exercices :

- affichage d’une liste


- somme des éléments d’une liste de nombres
- modification de la valeur du kème élément
- suppression du premier élément d’une liste
- insertion d’un élément à la fin d’une liste
- suppression du dernier élément d’une liste
- insertion d’un élément à la kème position
- suppression du kème élément
- suppression de la première occurrence d’une valeur donnée
- suppression de toutes les occurrences d’une valeur donnée en début d’une liste
- suppression de toutes les occurrences d’une valeur donnée à la fin d’une liste
- suppression de toutes les occurrences d’une valeur donnée
- copie d’une liste dans une autre liste
- suppression de tous les éléments d’une liste
- insertion d’un élément x avant la première occurrence d’une valeur donnée v
- insertion d’un élément x avant la dernière occurrence d’une valeur donnée v
- insertion d’un élément x avant toutes les occurrences d’une valeur donnée v
- insertion d’un élément x après la première occurrence d’une valeur donnée v
- insertion d’un élément x après la dernière occurrence d’une valeur donnée v
- insertion d’un élément x après toutes les occurrences d’une valeur donnée v
- suppression de l’élément avant la première occurrence d’une valeur donnée v
- suppression de l’élément avant la dernière occurrence d’une valeur donnée v
- suppression de l’élément avant toutes les occurrences d’une valeur donnée v
- suppression de l’élément après la première occurrence d’une valeur donnée v
- suppression de l’élément après la dernière occurrence d’une valeur donnée v
- suppression de l’élément après toutes les occurrences d’une valeur donnée v

31
Liste contiguë

Si le nombre maximum d’éléments d’une liste est connu d’avance, alors cette liste peut être
mémorisée dans un tableau

template <integer n, class t>


class liste { public :
t val[n] ; integer queue }

Mais C++ impose que le paramètre n soit une constante. Nous n’utilisons donc pas ce
paramètre mais indiquons la taille du tableau lors de l’initialisation

template <class t>


class liste_ { public :
t* val ; integer queue ; integer card }

L’insertion d’un élément en tête de liste entraîne le décalage vers la droite de tous les
éléments de la liste
0 1 2 3
void ins1( t x ; liste_<t> l ) { A H
for( i=l.queue ; i>=0 ; i-- ) {
A H
l.val[i+1] = l.val[i] ; }
l.val(1) = x ; } T A H

Notez que nous utilisons le même nom liste pour identifier une liste chaînée ou une
contiguë. Mais le type liste contiguë possède deux paramètres contre un seul pour le type liste
chaînée, ce qui permet de les distinguer. On dit que le nom liste est surchargé ou
polymorphe. La surcharge permet d’éviter une profusion de nom ( liste_chainee,
liste_contiguë, etc.). Si le nombre d’argument est le même, nous pouvons encore définir
tan(double) et tan(xy<t>) par exemple.

Exercices : même exercices que sur les listes chaînées.

Si le langage utilisé ne possède pas de pointeurs, une liste peut être aussi mémorisée dans un
tableau de couples info et alt. Le champ alt contient l’indice de l’élément suivant

l = { "C", "D", "Q", "S" } ;

info alt
0
1 D 3
2
3 Q 6
4 C 1 début
5
6 S -1 fin
7

32
Texte

Un texte est une liste de caractère mais un littéral texte commence et se termine par des
guillemets et les éléments de la liste ne sont pas séparés par une virgule. Un texte peut être
mémorisé dans une liste chaînée
liste<char>* t t

t = "Tah" t T a h

Si nous connaissons le nombre maximum de caractères d’un texte, nous pouvons utiliser une
liste contiguë
liste_cont<char> t t
init ( t, 10 ) ;
1 2 3 4 5 6 7 8 9 10
t = "alpha" ; t 5 a l p h a

t = "" ; /*texte vide*/ t 0 a l p h a


Si le texte doit contenir un guillemet, il faudra le remplacer par un anti–slash \ et un guillemet

t = "x=\"a 50\"" ; t 8 x = " a 5 0 "


1 2 3 4 5 6 7 8 9 10
t.val[4] = 'b' ; t 8 x = " b 5 0 "

y = t.val[7] ; t x="b 50" y 0

y = conc( "123", y ) ; t x="b 50" y 1230

Cette dernière fonction est appelée concaténation de 2 textes. On peut aussi utiliser un
caractère spécial qui ne doit jamais apparaître dans un texte pour marquer la fin de la liste. Le
langage C utilise le caractère NUL en ASCII (caractère '\0') et il n’y a pas de type texte mais
seulement un tableau de caractères
0 1 2 3 4 5 6 7 8 9 10
t
char t[10] = "alpha";
t a l p h a

Comme un texte est un tableau, l’emploi de littéral texte est limité à l’initialisation lors de la
déclaration. Un texte peut aussi être mémorisé dans un pointeur sur un tableau qui sera
redimensionné s’il devient trop petit ou trop grand. Dans ce cas, on peut utiliser un littéral
texte

char* t ; t

t = "Tah" ; t T a h
t = "Tahiana" ; t T a h i a n a
Les programmes

' #include <string.h>


char *strcpy(char *dest, const char *src)
wchar_t *wcscpy(wchar_t *dest, const wchar_t *src)

33
' #include <mbstring.h>
unsigned char *_mbscpy(unsigned char *dest,const unsigned char *src)

copient chaque caractère d’un texte dans un autre. Pour afficher un texte, nous n’avons plus
besoin de texte de formatage

printf ( tc )
printf ("Tah")

Les programmes

' #include <string.h>


integer strcmp(const char *s1, const char *s2)
integer wcscmp(const wchar_t *s1, const wchar_t *s2)
' #include <mbstring.h>
integer _mbscmp(const unsigned char *s1, const unsigned char *s2)

comparent deux textes. Les deux textes sont comparés caractère par caractère jusqu’à ce que
l’un des deux textes soit terminé ou que les deux caractères soient différents. Le texte plus
court est plus petit. Le résultat est négatif si s1<s2, nul s’ils sont égaux, positif si s1>s2.

Attention ! Utiliser char*=char*, char*<char*, etc. revient à comparer deux adresses !

Les fonctions

' #include <string.h>


char *strcat(char *dest, const char *src)
wchar_t *wcscat(wchar_t *dest, const wchar_t *src)
' #include <mbstring.h>
unsigned char *_mbscat(unsigned char *dest,const unsigned char *src)

concatènent deux textes. Les fonctions

' #include <string.h>


size_t strlen(const char *s)
size_t wcslen(const wchar_t *s)

retournent le nombre de caractères d’un texte, non compris le caractère '\0'. Exemple :
comparaison de 2 textes. Nous comparons chaque caractère des 2 textes jusqu’à ce que l’un
des 2 textes soit terminé ou que les 2 caractères soient différents

template < class c >


bool operator < ( liste_<c> t1, liste_<c> t2 ) {
bool y
i = 1 ;
while ( true ) {
if ( i > t1.queue ) break ; /*longueur de t1*/
if ( i > t2.queue ) break
if ( t1(i) <> t2(i) ) break
i = i + 1 ; }
if ( i > t1.queue ) {
if ( i > t2.queue ) {
y = false ; /* t1 et t2 de même longueur*/
} else {
y = true ; /*t1 plus court*/
} else {

34
if ( i > t2.queue ) {
y = false ; /*t2 plus court*/
} else {
if ( t1(i) < t2(i) ) {
y = true
} else {
y = false
}
}
} ; return y
}

Exercices :

- concaténation de 2 textes
- suppression du premier caractère d’un texte
- calcul du texte inverse d’un texte donné
- déterminer si un texte est égal à son inverse (texte palindrome)
- suppression des blancs (espaces) en début d’un texte
- suppression des blancs en début et à la fin d’un texte et transformation d’une suite de
blancs à l’intérieur du texte en un seul

Programme principal paramétré

Le programme principal peut avoir des paramètres qui sont des littéraux textes avec ou sans
guillemets séparés par des espaces. Les guillemets sont obligatoires si le paramètre possède
des espaces. Ces paramètres sont mémorisés dans un tableau spécial appelé argv. Le nombre
de paramètres entrés par l’utilisateur (y compris le nom du fichier) est mémorisé dans un
entier spécial appelé argc. Exemple : pour calculer la distance entre deux points quelconque

' fichier xy_dist_arg.cpp


#include <xy.h>
#include <number.h>
void main(integer argc, char* argv[]) {
xy<double> a, b, c
a.x = asDouble(argv[1])
a.y = asDouble(argv[2])
b.x = asDouble(argv[3])
b.y = asDouble(argv[4])
put ( dist ( a, b ) )
}

Nous supposons que le fichier number.h contient les fonctions de conversions de texte en
nombre réels

' suite fichier number.h


double asDouble ( char* s ) {
char* e
return strtod ( s, &e )
}

Pour lancer le fichier xy_dist_arg.exe, nous écrivons son nom et les paramètres séparés par
des espaces

C:\Documents and Settings\Tah> xy_dist_arg 1 2 3 4

35
2.82842712474619
C:\Documents and Settings\Tah>_

Fichier

Un fichier est un ensemble d’octets mémorisés sur un disque. En général, un disque est divisé
en « clusters » composés de 128, 256, 512, 1024, 2048 ou 4096 octets. Si un fichier est plus
petit qu’un cluster, il y a bien entendu des pertes de places. Si un fichier est plus grand qu’un
cluster, il est mémorisé sur plusieurs clusters qui ne sont pas forcément placés côte à côte sur
le disque. Grosso modo, on peut considérer un fichier comme un tableau d’adresses de
clusters (accès direct) ou une liste chaînée de clusters (accès séquentiel). De plus, il existe
deux types principaux de fichiers : les fichiers binaires sur lesquels les nombres sont
mémorisés en base 2 et les fichiers textes sur lesquels les nombres sont mémorisés sous
forme de littéral texte 1.234E-5. Pour créer ou ouvrir un fichier binaire appelé fich.bin,
nous écrivons

' #include <stdio.h>


FILE* fs
fs = fopen ( "fich.bin", "wb" )

le paramètre "wb" indique que le fichier sera ouvert en mode écriture (« write ») et en mode
binaire. Pour ajouter des octets à la fin du fichier, on utilise "ab" (« append »). Une fois
ouvert, un fichier possède un pointeur positionné sur le premier octet du premier cluster. À
chaque fois que l’on écrit, en mode séquentiel, des octets sur le fichier, ce pointeur est avancé
selon le nombre d’octets écrits. Pour écrire un nombre (entier ou réel) en mode binaire nous
écrivons

' #include <stdio.h>


void write ( FILE* fs, double a ) {
fwrite (&a, sizeof(a), 1, fs )
}

&a est l’adresse en mémoire centrale du nombre, sizeof(a) donne le nombre d’octets à
écrire, le 1 est le nombre de réels à écrire. L’écriture d’un texte ou d’une liste chaînée est un
peu plus compliquée : il faut d’abord écrire la longueur du texte, puis les caractères qui le
compose

' #include <stdio.h>


void write ( FILE* fs, char* a ) {
integer n
n = strlen(a)
fwrite (&n, sizeof(n), 1, fs )
fwrite (a, n+1, 1, fs)
}

Le n+1 est dû au caractère '\0' qui termine les textes en C++. Comme un texte est une
adresse, nous n’écrivons plus &a. La lecture d’un nombre se fait par

void read ( FILE* fs, double &a ) {


fread (&a, sizeof(a), 1, fs )
}

La lecture d’un texte se fait via

36
void read ( FILE* fs, char* &a ) {
integer n
fread (&n, sizeof(n), 1, fs )
a = new char[n]
fread (a, n+1, 1, fs)
}

Pour créer ou ouvrir un fichier appelé fich.txt en mode texte nous écrivons

' #include <stdio.h>


FILE* fs
fs = fopen ( "fich.txt", "wt" )

L’écriture en mode texte d’un nombre ou d’un texte se fait via fprintf qui utilise les mêmes
chaînes de formatage que printf

void put ( FILE* fs, double a ) {


fprintf (fs,"%.18g",a)
}
void put ( FILE* fs, char *a ) {
fprintf (fs,a)
}

Liste triée

Une liste triée est une liste telle que chaque élément est inférieur (ou supérieur) ou égal à son
successeur. L’insertion dans une liste triée est

void ins ( t x ; liste_triee<t>* l ) {


liste_triee<t>* p
if ( l = NULL ) {
inst ( x, l )
} else {
if ( x <= l->info ) {
ins1 ( x, l )
} else {
p = l ;
while ( true ) {
if ( p->alt = 0 ) break
if ( x <= p->alt->info ) break
p = p->alt
}
ins1 ( x, p->alt )
}
}
}

La suppression d’une valeur dans une liste triée, si elle s’y trouve, est

void del ( t x ; liste_triee<t>* l ) {


liste_triee<t>* p
if ( l <> NULL ) {
if ( l->info = x ) {
delt ( l )
} else if ( l.info < x ) {
p = l ;
while ( true ) {

37
if ( p->alt = NULL ) break
if ( x <= p->alt->info ) break
p = p->alt
}
if ( p->alt <> NULL ) {
p->alt->info = x
del1 ( p->alt )
}
}
}
}

Exercices :

- fusion de deux listes triées (la liste obtenue doit être triée)
- intersection de deux listes triées
- recherche de l’adresse de la 1ère occurrence d’une valeur donnée dans une liste chaînée
triée (NULL si elle ne s’y trouve pas)
- recherche de l’adresse d’une occurrence d’une valeur donnée dans une liste contiguë
triée

Liste bidirectionnelle

Dans une liste chaînée bidirectionnelle, nous mémorisons aussi l’adresse du prédécesseur de
chaque élément. La liste elle-même est constituée par l’adresse du premier élément et celle du
dernier pour accélérer les insertions en fin de liste ou les parcours en ordre inverse

template <class t>


class cel_2 { public :
cel_2<t>* prec ;
t info ;
cel_2<t>* alt
}
template <class t>
class liste_2 { public :
cel_2<t>* tete
cel_2<t>* queue
} ;

Exemple : insertion en fin de liste

template <class t>


void insfin ( t x ; liste_2<t> l ) {
cel_2<t>* p ; p = new cel_2<t> ; tete queue
p->info = x ; p->alt = NULL ;
if ( l->tete = NULL ) {
l->tete = p ;
l->queue = p ;
p->prec = NULL
} else {
p->prec = l->queue ;
l->queue->alt = p ;
l->queue = p
} p
} ;

Exercices : même exercices que sur les listes chaînées mono–directionnelles.

38
Pile

Une pile est une liste telle que le dernier élément inséré sera le premier supprimé (Last In –
First Out ou LIFO)
double x ;

pile<double> p ; p = NULL ; p

ins ( 5 ; p ) ; p 5

ins ( 1 ; p ) ; p 1 5

ins ( 2 ; p ) ; p 2 1 5

del ( x ; p ) ; p 1 5 x 2

ins ( 9 ; p ) ; p 9 1 5 x 2

del ( x ; p ) ; p 1 5 x 9

Exemple : traitement des éléments d’une liste chaînée dans l’ordre inverse :

ins1(3,l) ; ins1(2,l) ; ins1(1,l) ; ' l = {1, 2, 3}


put_inv ( l )

donnera

3 2 1

On parcourt d’abord la liste de « gauche à droite » tout en mettant les éléments rencontrés
dans une pile. Il suffit ensuite de dépiler ces éléments, un par un

template <class t>


void put_inv ( liste<t>* l ) {
t x
pile<t>* p
p = NULL
for ( liste<t>* a = l ; a <> NULL ; a = a->alt ) {
ins ( a->info, p )
}
while ( p <> NULL ) {
del ( x, p ) ; put ( x )
}
}

Le parcours en sens inverse en utilisant un programme récursif est plus simple (un
programme récursif est un programme qui contient un appel à lui–même)

template <class t>


void put_inv ( liste<t>* l ) {
if ( l <> NULL ) {

39
put_inv ( l->alt )
put ( l->info )
}
}

Attention ! Pour une liste de grande taille, ce programme déclenche une mémoire
insuffisante parce que Windows semble réserver un espace limité pour chaque
application.

Arbre

Un arbre est un groupe d’éléments de même type tel que chaque nœud possède plusieurs
successeurs (sauf les feuilles) et un seul prédécesseur (sauf la racine). Les fils (ou le père)
d’un élément sont différents de celui d’un autre élément. Chaque nœud dispose de la liste de
ses successeurs

template <class t>


class arbre { public :
t info
liste<arbre<t>*>* suiv
}

Exemple : une expression préfixée est composée d’


– un nom : x
– un littéral : 9
– un nom et une expression préfixée entre parenthèse : f ( x )
– un opérateur et deux expressions préfixées entre parenthèse : / ( y, 5 )

L’expression « infixée »

f ( x ) * ( 3 / 5 ) + g ( 5 )

est équivalente à l’expression préfixée, en tenant compte de la priorité de * sur +

+ ( * ( f ( x ), / ( y, 5 ) ), g ( 5 ) )

a + * f x

/ y

info suiv
info g 5
alt

Une forêt est une liste d’arbre (comme d’habitude, on devrait écrire

template <class t>


class foret { public :

40
liste<arbre<t>*>* tete ; }

41
a + * f x

/ y

info suiv
info g 5
alt

f g 3

* 8

File

Une file est une liste telle que le premier élément inséré sera également le premier supprimé
(First In – First Out ou FIFO)
integer x ;
file<integer> f ; f = NULL ;

ins ( 5 ; f ) ; f 5

ins ( 1 ; f ) ; f 5 1

ins ( 2 ; f ) ; f 5 1 2

del ( x ; f ) ; f 1 2 x 5

ins ( 9 ; f ) ; f 1 2 9 x 5

del ( x ; f ) ; f 2 9 x 1

Une file peut être représentée par une liste chaînée, mais on mémorise également l’adresse de
la dernière cellule pour faciliter les insertions

42
tete queue
f

5 1 2

template <class t>


class file { public :
liste<t>* tete
liste<t>* queue
} ;

Exemple : parcours en largeur d’une forêt :


- traitement des racines
- parcours des nœuds de rang 1, puis de rang 2, etc.
Le rang d’un nœud est le nombre minimum de liens qui le sépare des racines

void put_large ( liste<arbre<t> >* a ) {


file<liste<arbre<t> >*> f = NULL ;
liste<arbre<t> >* r = a ;
while ( true ) {
for ( liste<arbre<t> >* x = r ; x <> NULL ; x = x->alt ) {
put ( x->info ) ;
if ( x->suiv <> NULL ) {
ins ( x->suiv ; f )
}
}
if ( f = NULL ) break
del ( r ; f )
}
}

La hauteur d’un nœud est le rang de sa feuille la plus éloignée.

File contiguë

Le meilleur moyen serait d’utiliser un vecteur « circulaire » : lorsque la queue (ou la tête) de
la file atteint la fin du vecteur, elle repart au début

t q
f 2 5 4 7
ins ( 8 ; f ) ; q t
f 8 2 5 4 7

template <class t>


class file_ { public :
t* val ; integer tete ; integer queue ; integer card
}
template <class t>
void init ( file_<t> &f, integer n ) {
f.val = new t[n]
f.tete = -1 ; f.queue = -1 ; f.card = n
}
template <class t>
void ins ( t x ; file_<t> &f ) {
if ( f.tete = -1 ) {

43
f.queue = 0 ; f.tete = 0
} else {
if ( f.queue = f.card ) {
f.queue = 0
} else {
f.queue = f.queue + 1
}
}
f.val[f.queue] = x
}
template <class t>
void del ( t &x ; file_<t> &f ) {
x = f.val[f.tete] ;
if ( f.queue = f.tete ) {
f.tete = -1 ; f.queue = -1
} else {
if ( f.tete = f.card ) {
f.tete = 0
} else {
f.tete = f.tete + 1
}
}
}

Anneau

Un anneau est une liste telle que le successeur du «dernier» élément est le «premier» élément

5 1 2 5 9

Exemple : calcul d’une fonction récurrente

f ( n ) = f(n-1) + f(n-2) +...+f(n-a) si n > a


f ( n ) = v(n-1) + v(n-2) +...+v(1) si n ≤ a
f ( n ) = 0 si n ≤ 0

v étant un vecteur donné

template <class t>


t f ( integer n ; vec<t> v ) {
anneau<t>* f
t y
init ( f, v.card ) ;
y = 0 ;
for ( integer i = 0 ; i < v.card ; i++ ) {
insq ( v.info[i] ; f )
}
for ( integer i = 1 ; i <= n ; i++ ) {
y = y + f->info
f->info = y ; f = f->alt
}
return y
} ;

Pour un anneau chaîné, si la notion de « premier » et de « dernier » élément est importante,


l’anneau proprement dit est l’adresse du dernier élément pour faciliter les insertions en fin de
liste

44
l

5 1 2

template <class t>


void insz ( t x, anneau<t>* &l ) {
anneau<t>* p
p = new anneau<t>
p->info = x
if ( l = NULL ) {
p->alt = p
} else {
p->alt = l->alt ; l->alt = p
}
l = p
} ;

Arbre binaire

Un arbre n–aire est un arbre tel que chaque nœud possède au plus n successeurs. Un arbre 2–
aire est aussi appelé arbre binaire. Nous pouvons « intégrer » la liste des successeurs dans le
nœud

template < class t >


class arbre2 { public :
t info ;
arbre2<t>* s1 ;
arbre2<t>* s2 ; } ;

2
1
0
a 5

7 5
5

Exemple : parcours post fixé d’un arbre binaire :


- parcours du 1er sous–arbre
- parcours du 2e sous–arbre
- traitement du nœud

template <class t>


class ab { public :

45
arbre2<t>* val ; bool ter ; }
template <class t>
put_post ( arbre2<t>* a ) {
pile<ab<t> >* p = NULL ;
arbre2<t>* x = a
ab<t> w ;
while ( true ) {
while ( x <> NULL ) {
w.val = x ; w.ter = false
ins ( w ; p ) ;
x = x->s1
} else {
while ( true ) {
if ( p = NULL ) return
if ( sommet(p).ter = 0 ) break ;
del ( p ; w ) ;
put ( w.val->info )
}
p->info.ter = 1 ;
x = p->info.val->s2
}
}
}

La fonction sommet donne l’élément au sommet de la pile.

Récursion

L’exécution d’un programme récursif peut être considérée comme le parcours d’un arbre.

void p(t x) {
if (c(x)) {
a0(x)
} else {
a1(x) ; p(f1(x)) ;
a2(x) ; p(f2(x)) ;
'...
am(x) ; p(fm(x)) ;
am1(x)
}

où c(x) est une certaine condition, a0(x), a1(x), … am1(x) sont des actions
éventuellement complexes, mais n’entraînant pas d’appel récursif de p, et f1(x), f2(x),…
fm(x) sont des expressions dépendant de x.

46
f1 a0
a1
f2
a2 a0
am f1 a0
fm a1
f1 am f2
a2 a0
1
am fm
a0
am
a1
f2 1 f1 a0
a2 a0 a1
am a2 f2 a0
am a0 f1 am fm
fm f1 a0
1 am
a1 a1
f2 f2 1
a2 a2 a0
am am fm
am fm am a0
1 1
a0

Nous devons mémoriser dans une pile le paramètre x et le numéro i du prochain « fils » à
visiter

p = NULL ;
while ( true ) {
if (!c(x)) {
a1(x) ; ins(p,(x,2)) ; x=f1(x)
} else {
a0(x) ;
{
if ( p = NULL ) return
del(p ; (x ; i)) ;
if ( i < m+1 ) break ; am1(x) ; }
if ( i = 2 ) {
a2(x) ; ins(p,(x,3)) ; x=f2(x)
...
} else if ( i = m ) {
am(x) ; ins(p,(x,m+1)) ; x=fm(x)
}
}
}

Arbre binaire équilibré

Un arbre binaire ordonné est un arbre binaire tel que l’information d’un nœud est supérieure
ou égale à celle de son 1er fils et inférieur à celle de 2ème fils (s’ils existent). Un arbre binaire
ordonné peut être dégénéré : la plupart des nœuds n’ont qu’un seul fils

a 9 4
7 3
6 2
1

Un arbre binaire équilibré ou arbre AVL (dû à Adelson, Velskij et Landis) est un arbre
ordonné tel que toutes les feuilles soient situées à un rang log2n ou log2n+1 où n est le

47
nombre d’éléments de l’arbre. Si le déséquilibre est dû au 1er sous–arbre du 1er sous–arbre
d’un nœud

template <class ic, class ia>


class avl { public :
char haut
ic cle
ia attr
avl<ic, ia> *s1
avl<ic, ia> *s2
} ;

h+1 h+1
1 1
b1 2 b1 2
3 3 3 3
b 4 b 4
5 h 5 h
b2 6 b2 6
7 7
a 8 a 8
9 h 9 h
a2 10 a2 10
11 11
11 11

Si le déséquilibre est dû au 2ème sous–arbre du 1er sous arbre d’un nœud

h h
b1 1 b1 1
2 2
b 3 b 3
4 h+1 ou h 4
c1 4 c1 4
5 c 5
c
6 7 6
7
c2 c2
a 8 8
a
9 h
9 h
a2 9
a2 9
11
11
12
12

Tri rapide

Ce petit problème n’entraîne pas la création d’une structure de donnée mais sa généralisation
nous a forcé sa présentation dans cet ouvrage. Soit un vecteur v de n éléments
« ordonnables » (des nombres ou des textes par exemples). Le problème consiste à trier ce
vecteur : une méthode simple serait de choisir un élément quelconque appelé pivot, mettre

48
tous les éléments inférieurs à ce pivot à gauche et tous les éléments supérieurs à droite, puis
recommencer avec ces deux sous–listes.

trier( vec<t> &v, integer n ) {


trier( v, 1, n ) ; }
trier( vec<t> &v, integer a, integer b ) { integer p ;
partitionner( v, a, b, p) ;
trier( v, a, p ) ;
trier( v, p+1, b ) ; }
partitionner( vec<t> &v, integer a, integer b, integer p ) {
t vp ; integer g ; integer d ; integer i ; ta vi
i b
p = a ; 5 3
if ( a < b ) { g d
vp = v(p) ;
g = a ; d = b+1 ;
i = 1 ; while ( true ) { a i b
if ( v(i) <= vp ) { 5 3 9 6
g = g+1 ; g d
i = i+1
} else {
d = d-1 ; a i b
vi = v(i) ;
5 3 6 9
v(i) = v(d) ;
v(d) = vi g d
}
if ( i = d ) break
}
p = g
}

Lorsque la taille d’une sous-liste est assez faible (15 ou 16 éléments [Meyer 78]), il faut
arrêter la partition parce que les appels récursifs sont trop lourds, puis lancer un tri par
insertion qui est très efficace sur un vecteur presque ordonné.

Exercices :

- le kème plus petit : sans trier le vecteur v, chercher l’indice k tel que les éléments
v(1)..v(k) soient inférieurs ou égaux à v(k)
- le drapeau français : soit un vecteur composé de n éléments bleu, blanc ou rouge.
Écrire le programme qui met tous les éléments bleus à gauche, les blancs au milieu et
les rouges à droite du vecteur

Tas contigu

Un arbre binaire complet est un arbre tel que chaque nœud (sauf les feuilles) possède 2 fils.
Un arbre complet peut être mis dans un vecteur. Si on remplit le vecteur en faisant un
parcours en largeur de l’arbre alors

s1 i = i * 2 ;
s2 i = i * 2 + 1 ;
père i = i ÷ 2 ; /* 9 ÷ 2 = 4 */

Un tas contigu est un arbre binaire complet tel que l’information d’un nœud est supérieure
(ou inférieur) ou égal à celles de ses fils et que certaines feuilles (les dernières dans le vecteur
le représentant) peuvent être absentes

49
4
7
5
8
3
6
2
9
0
1

1 2 3 4 5 6 7 8 9 10 11 12 13 14
9 8 9 7 6 1 3 4 5 3 2 0

L’insertion dans un tas contigu consiste à mettre l’élément à insérer à la fin du vecteur, puis à
l’échanger avec son père si nécessaire, puis avec son grand-père, etc.

template <class t>


class tas_ { public :
t* info ; integer dern ; integer card } ;
template <class t>
ins ( t x, tas_<t> &a ) {
integer i, p ;
a.dern = a.dern + 1 ; i = dern a ;
while ( true ) { p = i / 2
if ( p < 2 ) break
if ( a.info [ p ] <= x ) break
a.info [ i ] = a.info [ p ]
i = p
}
a.info [ i ] = x
} ;

La suppression de la racine d’un tas contigu consiste à mettre le dernier élément à cette 1 ère
position puis à l’échanger éventuellement avec le plus grand de ses fils : un tas est une file
triée dont les insertions et les suppressions se font en log(2 ; n) si n est le nombre
d’éléments du tas.

Alternantsuccesseur

Une forêt est une liste d’arbre. Un alternant–successeur est une forêt telle que chaque nœud
dispose de l’adresse de son « fils aîné » et de son « frère »

template <class t>


class alt_suc { public :
t info ; alt_suc<t>* alt ; alt_suc<t>* suiv } ;

50
a 5 1 2 9

5 3

suiv
info 3
alt

9 5

4 3 7

Exemple : parcours post fixé d’un alternant–successeur :


- parcours des successeurs
- traitement du nœud

void put_post ( alt_suc<t>* &a ) {


alt_suc<t>* p = NULL ;
x = a ;
while ( true ) {
while ( true ) {
if ( x = NULL ) break
ins ( p, x ) ;
x = x->suiv
} ;
if ( p = NULL ) break
del ( p, x ) ;
put ( x->info ) ;
x = x.alt
}
} ;

Dictionnaire

Un dictionnaire peut être mémorisé dans un alternant–successeur trié : les informations sont
des caractères et chaque « mot » est terminé par un caractère inférieur à toutes les lettres (par
exemple, un espace )

template <class char_, class def_>


class dico { public :
char_ info
dico<char_, def_>* alt
union {
def_ attr
dico<char_, def_>* suc
} suiv ; } ;

51
a I R È N E Sainte du 5 avril

N I Négation

R E I N Viscères doubles

E Souveraine

Exemple : insertion d’un mot dans un dictionnaire. Le mot est terminé par un nil

template <class char_, class def_>


dico<char_,def_>* adr ( char_ a_info,
dico<char_,def_>* a_alt, dico<char_,def_>* a_suc ) {
dico<char_,def_>* p
p = new dico<char_,def_>
p->info = a_info
p->alt = a_alt
p->suiv.suc = a_suc
return p
}
template <class char_, class def_>
dico<char_,def_>* adr ( char_ a_info,
dico<char_,def_>* a_alt, def_ a_attr ) {
dico<char_,def_>* p
p = new dico<char_,def_>
p->info = a_info
p->alt = a_alt
p->suiv.attr = a_attr
return p
}
template <class char_, class def_>
dico<char_,def_>* reste ( char_* m, def_ d ) {
dico<char_,def_>* p, *pa
pa = 0
if ( zero(*m) ) {
p = adr ( *m, pa, d )
} else {
p = adr ( *m, pa, reste ( m+1, d ) )
}
return p
}
bool zero ( char c ) {
return ( c = '\0' )
}
template <class char_, class def_>
void ins ( char_* m, def_ d, dico<char_, def_>* &a ) {
if ( a = 0 ) {
a = reste ( m, d )
} else {
if ( zero( *m ) ) {
a = adr ( *m, a, d )
} else if ( *m < a->info ) {

52
a = adr ( *m, a, reste ( m+1, d ) )
} else if ( *m = a->info ) {
ins ( m+1, d, a->suiv.suc )
} else {
ins ( m, d, a->alt )
}
}
}

Arbre basique

Si les « mots » d’un dictionnaire sont des nombres en base quelconque, alors il peut être
mémorisé dans une forêt contiguë : les successeurs d’un nœud sont placés dans un vecteur et
le champ info disparaît. Si les nombres sont de longueurs différentes (chiffres après virgule),
il faudrait un bit supplémentaire pour distinguer la fin d’un « mot »

001
002

01

0 020
1
2 022

210
211
212

Table

Une table est une fonction (ou relation, au sens mathématique du terme) qui à une
information appelée clé associe une autre information appelée attribut

particip°
Tana
70.4
Maj
52.1
Fianar
49.6
Diégo
65.7
Tam
53.8
Tul
p1 attributs
clés

53
Toutes les clés doivent être différentes. On peut mémoriser ( « indexer » ) les clés dans un
arbre ordonné ou mettre la table dans une simple liste de clés et d’attributs

table < char*, double > t ;


nom part
set ( t, "Tana", 70.4 ) ; t Tana 70.4
set ( t, "Fianar", 70.4 ) ; Fianar 70.4

ins ( "Maj", 52.1, t ) ; t Tana 70.4


set ( t, "Fianar", 49.6 ) ; Fianar 49.6 x
x = get ( t, "Fianar" ) ; Maj 52.1 49.6

Exemple : calcul de la moyenne de la participation (représentation par une liste)

s = 0 ;
for ( x = l ; x <> 0 ; x = x->alt ) {
s = s + x->info ;
}
m = s / card t ;

Graphe

Un graphe est un groupe d’éléments tel que chaque nœud possède plusieurs successeurs (sauf
les points de sortie) et plusieurs prédécesseurs (sauf les points d’entrée)

template <class t>


class noeud { public :
t info ; liste<noeud<t>*>* suiv
}
template <class t>
class graphe { public :
liste<noeud<t>*>* entree
} ;

3
info suiv
g 2

info
alt 2

Le moyen le plus simple pour entrer un graphe serait un logiciel comme l’outil Dessin de
Excel. Sinon il faudrait donner un identificateur (par exemple un texte) pour chaque nœud et
mettre dans une table la liste des identificateurs de ses successeurs

54
template<class id, class tn>
class attr_n { public :
tn info_n
liste<avl<id,attr_n>*>* suiv ; }
template<class id, class tn>
class graphe { public :
avl<id,attr<id,tn> >* val
liste<avl<id,attr_n<id,tn> >*>* entree ; }
F
graphe<char*,integer> r C
init ( r ) ; 2 K
1
ins("A","C",r);ins("A","D",r) 9
ins("B","G",r);ins("B","E",r) A I
ins("C","F",r);ins("C","J",r) 5 D 3
ins("D","G",r)
ins("E","G",r);ins("E","H",r) 8 L
ins("F","K",r);ins("F","I",r) B G 6
ins("G","H",r); 0 7 J
ins("H","J",r);ins("H","M",r); E 5
ins("I","K",r);ins("I","L",r)
2 M
ins("I","I",r)
ins("J","L",r);ins("J","M",r) 5 4
H

Exemple : calcul du rang de chaque nœud. Le rang d’un nœud est le plus petit nombre d’arcs
qui le sépare des points d’entrée. Pour calculer les points d’entrées, nous détectons les nœuds
dont le nombre de pères est égal à zéro. Nous mettons le résultat dans info_n. Il suffit de
balayer l’AVL et d’ajouter un 1 au nombre de père des successeurs d’un nœud

void init_nb ( avl<id,attr_n<id,tn> > &g ) {


if ( g <> 0 ) {
g->attr.info_n = 0
init_nb ( g->s1 ) ; init_nb ( g->s2 ) ;
}
}
void nbp ( avl<id,attr_n<id,tn> > &g ) {
liste<avl<id,attr_n<id,tn> >* >* x
if ( g <> 0 ) {
for ( x = g->suiv ; x <> 0 ; x = x->alt ) {
x->info->attr.info_n ++
}
nbp ( g->s1 ) ; nbp ( g->s2 ) ;
}
}
void entr ( lavl<id,attr_n<id,tn> > &g ) {
if ( g <> 0 ) {
if ( g->attr.info_n = 0 ) {
ins1 ( g, l )
}
entr ( g->s1 ) ; entr ( g->s2 ) ;
}
} ;
void init_entree ( graphe<id,tn> &g ) {
init_nb ( g.val )
nbp ( g.val )
init ( g.entree )
entr ( g.entree, g.val )
}

55
y : par ( ti * ent ) ; g : par graphe t ;
y = nbpère g {
f : file ( graphe t ) = NULL ; /*adresse d'un nœud en fait*/
y = NULL ;
x :: pt_entrée g { /*parcours en tant que liste*/
y ( x ) = 0 ;
ins ( x ; f )
{
f = NULL ) break
del ( x ; f ) ;
s :: suiv x {
s  y {
y ( s ) = y ( s ) + 1
} else {
y ( s ) = 1 ; ins ( s ; f )

Graphe étiqueté

Un graphe étiqueté est un graphe tel que chaque arc possède aussi une information
F
C
3
2 6 6 K
9
3 3
A I 3
5 4 3
D
4 9 3
2 L
B 2 G 1 10
2 5
10 6 6 J 2
E 5
2 2 1
2 3
3 M
2 3 9
H 6

graphe(ta;t) =
*adr( info:t ; suiv:*(étiq:ta;nsuiv:graphe(ta;t) )

On peut aussi mettre les attributs des arcs dans une table. Lorsque tous les nœuds peuvent
être des points d’entrée, la représentation dans une table s’impose. Exemple : calcul du plus
court chemin entre 2 nœuds a et z ( produire une liste de nœuds )

réseau(ti;ta;t) =
ti*( info:t ; suiv:*(étiq:ta;nsuiv:ti) )
y : par *ti ; r : réseau(ti;ta;t) ; (a;z) : par 2*ti
ta1 : par type ; dist : fonction ta1
y = pcm ( r ; a ; z ; dist ) {
f : ta1*ti = NULL ; /* "file" d’attente*/
prec : ti*ti = NULL ;
da : ti*ta1 = NULL ; /*distance par rapport à a*/
x = a ; da a = 0 ; az : ta1 = ∞ ; ax : ta1 = 0 ;
{
s :: suiv r x { ns = nsuiv s ;
as = ax + dist s ;
as < az { /*recherche dans un cercle de rayon az*/
ns  da ou_bien da(ns) > as {
da ns = as ; prec ns = x ;
ns = z {

56
az = as ;
} else {
ins ( as ; ns ; f ) /* si s  f */
f = NULL ) break
del_min ( ax ; x ; f )
y = ()
x = z ; { /*inverser la "liste" prec */
x = a ) break
inst ( prec x ; y ) ; x = prec x

Pour accélérer l’algorithme, nous pouvons, lors de l’insertion d’un élément dans f :
- le chercher d’abord en se basant sur l’ancienne distance et (en cas d’égalité)
l’identificateur du nœud,
- ensuite supprimer cet élément
- et enfin, insérer la nouvelle distance.

Processeur

Un processeur est un ensemble de circuits de calcul, de registres, d’indicateurs, etc. qui peut
exécuter des instructions. Nous supposons par la suite que nous disposons d’un ordinateur
possédant des processeurs qui peuvent lire au même moment une même partie de la mémoire.
Processeurs

Bus d’adresse
3
Bus de données
2

0 1 2 3 4 5 6 7…
Mémoires
Même si l’ordinateur ne possède qu’un seul processeur, celui–ci peut simuler un multi–
processeur en exécutant un des programmes pendant un très court laps de temps avant de
simuler un autre « processeur » (thread dans le vocabulaire Windows). En C, on lance un
thread par le programme

'#include <process.h>, Win32 et OS/2 seulement


unsigned long _beginthread
(_USERENTRY (*start_address)(void *),
unsigned stack_size, void *arglist)

57
start_address est le nom du programme à exécuter par le processeur, stack_size est la
taille, en octets, de l’espace (la pile des appels) à utiliser par le processeur (on se demande
comment peut–on connaître une telle quantité ?), arglist est l’unique paramètre de
start_address. Il doit être présent, mais il peut être NULL si start_address n’a pas de
paramètres. Si start_address a besoin de plusieurs paramètres, on peut toujours définir un
struct et passer l’adresse de ces structures comme argument. La valeur retournée est un entier
appelé identificateur du thread. Le thread s’arrête quand le programme principal s’arrête, ou
si le thread a atteint la fin de start_address, on s’il rencontre le programme

'#include <process.h>, Win32 et OS/2 seulement


void _endthread(void)

Exemple 1 : soit un processeur principal qui lance quelques « fils ». Ces fils affichent
simplement leur numéro.

' fichier c_thread.cpp


#include <process.h>
#include <dos.h>
#include <stdio.h>
void fils ( void* i0 ) {
integer i = (integer) i0 ; 'transform° de i0 en entier
printf ( "Je suis le fils numéro " )
printf ( "%d", i ) ; printf ( "\n" )
}
void main () {
integer f[2]
for ( integer i = 1 ; i <= 2 ; i++ ) {
_beginthread ( fils, 4096, (void*)i)
'transform° de i en pointeur sur void
}
printf ( "Tapez ENTREE pour arrêter le 'Main'\n" )
getchar() ; 'pour attendre que tous les fils se sont arrêtés
} ;

Exemple 2 : cinq philosophes passent leur temps à méditer. Nature oblige, il doivent manger
de temps en temps. Ils sont assis autour d’une table sur laquelle est placée un grand bol de
spaghettis. Chaque philosophe possède une assiette et une fourchette. Mais les spaghettis sont
tellement difficiles à manipuler, qu’il faut deux fourchettes pour les manger. Un philosophe
peut emprunter une fourchette à son voisin de gauche ou de droite. Écrire le programme qui
assure qu’une fourchette n’est pas utilisée en même temps par deux philosophes.

' fichier c_philo.cpp


#include <process.h> /* _beginthread */
#include <dos.h> /* time, gettime */
#include <\tah\c\lib\source\number.h> /* put */
struct no_id { ' c'est le struct 'bidon'
integer *f ; ' adresse d'un tableau
integer id
}
double asSec ( struct time t ) { ' conversion en secondes
return ( t.ti_hour*3600.+t.ti_min*60.+t.ti_sec+t.ti_hund/100.)
}
/*struct time {
unsigned char ti_min; /* minutes */
unsigned char ti_hour; /* hours */
unsigned char ti_hund; /* hundredths of seconds */

58
unsigned char ti_sec; /* seconds */
};*/
void delay ( double d ) { ' attente pendant d secondes
struct time t1, t2
gettime ( &t1 )
while ( true ) {
gettime ( &t2 )
if ((asSec(t2)-asSec(t1))>d) break
}
}
void enfant(void *x0) {
no_id *x = (no_id*) x0
integer d
integer i = x->id
'----------------------
while ( true ) { ' boucle infinie, c'est le Main qui arrête tout
while ( x->f[i] <> 2 ) {} ; ' attendre 2 fourchettes
d = random ( 10 ) ; ' nombre aléatoire entre 1 et 10
delay ( d ) ; ' manger pendant d secondes
x->f[i] = 0
' passer les fourchettes aux voisins :
if ( i = 0 ) { x->f[4]++
} else { x->f[i-1]++ ; }
if ( i = 4 ) { x->f[0]++
} else { x->f[i+1]++ ; }
}
}

integer main(void){
integer i
integer t[5] ; ' nombre de fourchettes par philosophe
no_id *x
'-----------------------
randomize () ; ' initialis° des nombres aléatoires
for (i = 0; i < 5; i++) { ' une fourchette pour chacun d'abord
t[i] = 1 ; }
for (i = 1; i < 5; i+=2) {
t[i] = 0 ; ' pas de fourchette pour 1 et 3
t[i-1] += 1 ; ' 2 fourchettes pour 0 et 2
}
for (i = 0; i < 5; i++){
x = new no_id
x->f = t ; x->id = i
_beginthread(enfant,4096,(void *)x)
}
for ( integer j = 1 ; j <= 60 ; j++ ) {
put ( j ) ; put ( "\t: " )
for ( integer k = 0 ; k < 5 ; k++ ) {
put ( t[k] ) ; put ( " " )
} ; put ( "\n" )
delay ( 1 ) ; ' affichage de t toutes les secondes
}
}

Ce programme doit être compilé avec l’option -tWM. Ne sachant pas comment entrer une telle
option via l’IDE de Borland C++, nous écrivons dans l’invite de commande

cd c:\tah\c\debug\source\cours
bcc32 -tWM c_philo.cpp

59
Ne sachant pas comment indiquer où se trouve les fichiers d’en–tête .h, nous écrivons leur
nom complet.

Hypercube

Un hypercube de dimension n est un réseau formé de 2n nœuds. En reliant les nœuds de deux
hypercubes de dimension n-1 dont les numéros en base 2 ne diffère que par un seul bit, nous
obtenons un hypercube de dimension n

00 01 000 001 100 101

0 1
10 11 010 011 110 111
n=1 n=2 n=3

Deux nœuds quelconques sont séparés par n liaisons au maximum. La recherche d’un chemin
est facile : il suffit de trouver un nœud libre dont le numéro diffère d’un seul bit par rapport
au numéro du nœud courant.

Prédicat

La syntaxe de SWI–Prolog est proche de C++ sauf sur quelques points

– un nom de variable commence toujours par une majuscule : X


– la fonction puissance est 2^3
– il n’y pas de type tableau
– un littéral liste est formée par un crochet gauche [, des expressions séparées par des
virgules et un crochet droite ] : [1, 2, 3]
De plus, on peut séparer les champs info et alt par un | : [1, 2 | X]
– on peut utiliser n’importe où des littéraux textes entre ' : 'Tahiana'
– si un littéral texte commence par une minuscule et ne contient que des lettres, des
chiffres et des _, les ' peuvent être omis : tahiana
– les commentaires sont entre /* et */
– il n’y a pas de moyen de distinguer une donnée et un résultat : si une variable est libre
(n’a pas encore été initialisée), elle recevra une valeur
– l’extension des fichiers compilables est .pl ou .pro
– la compilation se lance par File\Consult ou par un double clic sur le fichier

Un prédicat est une fonction qui retourne un bit avec un effet de bord : si un au moins des
paramètres est libre (vient d’être déclaré), alors ce paramètre recevra une valeur. Exemple :
conversion d’un nombre formé de chiffres en toutes lettres, et vice versa

/* fichier conv.pl */
conv(X,Y) :-
X='1', Y=un
X='2', Y=deux
X='3', Y=trois.

en supposant que le , équivalent à l’opérateur et, est évalué avant le ; équivalent à ou. Ce
programme peut être utilisé de 4 façons différentes

60
– connaissant un nombre en toutes chiffres, l’écrire en toutes lettres

conv('1',X).
X = un

– connaissant un nombre en toutes lettres, l’écrire en toutes chiffres

conv(X,deux).
X = 2

– est–ce qu’un nombres en toutes chiffres est l’équivalent d’un autre en toutes lettres

conv('3',trois).
Yes

– produire toutes les conversions possibles

conv(X,Y).
X = '1'
Y = un ; ' ici, appuyez sur la touche point–virgule

X = '2'
Y = deux

X = '3'
Y = trois

p1(x) ; p2(x)

est vrai si l’une au moins de ces alternatives est vraie. L’évaluation de cette instruction se fait
comme suit :
– si p1(x) est vrai alors l’évaluation se termine par un succès
– sinon, on passe à l’alternative suivante, p2(x)
– si p2(x) est un succès alors l’évaluation se termine par un succès
– sinon l’évaluation se termine par un échec (il n’y a plus d’autres alternatives)

p1(x) , p2(x)

est vrai si les deux buts p1(x) et p2(x) sont tous vrais. L’évaluation de cette instruction se
fait comme suit
– si p1 x est faux, p2 x ne sera pas évalué, le résultat est faux
– si p1 x est vrai, p2 x sera évalué
– si p2 x est vrai alors le résultat est vrai
– si p2 x est faux,
– si x n’est pas libre, le résultat est faux
– sinon, p1 x est réévalué pour d’autres valeurs de x (backtrack)
– si on ne trouve pas d’autres valeurs de x, le résultat est faux
– sinon, p2 x sera évalué, etc.

61
Clause

La définition d’un prédicat formé de plusieurs alternatives peut être repartie sur plusieurs
clauses et nous retrouvons l’équivalent d’une table

conv ( '1', 'un' ).


conv ( '2', 'deux' ).
conv ( '3', 'trois' ). /*etc.*/

Les clauses dont la partie droite est vide sont des faits. Le prédicat fail provoque un retour
en arrière (équivalent à 1=2). write(X) affiche X à l’écran. Ajoutez les lignes suivantes au
fichier conv.pl

/* suite du fichier conv.pl */


conv:-
conv(X,Y),
write('X = '), write(X),
write(', Y = '), write(Y), write(' ;\n'),fail.

Recompilez conv.pl puis lancez

conv.
X = 1, Y = un
X = 2, Y = deux
X = 3, Y = trois

No

Le prédicat ! (appelé cut) empêche le retour en arrière

conv:-
conv(X,Y),!,
write('X = '), write(X),
write(', Y = '), write(Y), write(' ;\n'),fail.

donnera

conv.
X = '1', Y = un

No

Ce prédicat ! empêche le passage à la clause suivante (ou à l’alternative suivante) d’un


prédicat donné s’il est placé en tête d’une liste de buts. Exemple :

conv ( '1', 'un' ).


conv ( '2', 'deux' ):-!.
conv ( '3', 'trois' ).
conv:-
conv(X,Y),
write('X = '), write(X),
write(', Y = '), write(Y), write(' ;\n'),fail.

donnera

62
conv.
X = 1, Y = un
X = 2, Y = deux

No

Pour ajouter une clause avant toutes les clauses existantes d’un prédicat donné, il faut d’abord
indiquer que le prédicat est dynamique

:-dynamic(conv/2).
conv ( '1', 'un' ).
conv ( '2', 'deux' ).
conv ( '3', 'trois' ).
conv:-
conv(X,Y),
write('X = '), write(X),
write(', Y = '), write(Y), write(' ;\n'),fail.

conv/2 indique que c’est le conv avec deux paramètres qui est dynamique.

asserta(conv('0', 'zero')).

Yes
conv.
X = 0, Y = zero
X = 1, Y = un
X = 2, Y = deux
X = 3, Y = trois

No

L’insertion d’une clause après toutes les clauses existantes se fait par

assertz(conv('4', quatre)).

Yes
conv.
X = 0, Y = zero
X = 1, Y = un
X = 2, Y = deux
X = 3, Y = trois
X = 4, Y = quatre

No

Pour enlever une clause donnée

retract(conv('1',_)).

Yes
conv.
X = 0, Y = zero
X = 2, Y = deux
X = 3, Y = trois
X = 4, Y = quatre

No

63
Le soulignement _ est une variable anonyme, et indique que sa valeur ne nous intéresse pas.

64
3ème PARTIE. PROBLÈMES

Vous êtes un chien, et un ami humain vient de lancer votre os favori dans le
jardin voisin par–dessus un grillage (…) Or, une barrière est ouverte dans le
grillage à environ quinze mètres de l’os. Que faites–vous ? Certains chiens
courront jusqu’au grillage et resteront là à aboyer. D’autres s’en éloigneront pour
aller jusqu’à la barrière ouverte et courir jusqu’à l’os tant désiré.
(Douglas HOFSTADTER – Gödel, Escher, Bach :
les Brins d’une Guirlande Éternelle)

– Tas dynamique [ Lignelet 90 ] : construire un tas dont la taille maximale n’est pas connue
d’avance

– Union d’intervalles : construire la liste représentant l’union (ou l’intersection) de deux ou


plusieurs liste d’intervalles fermés

– Inclusion de deux polygones [ Sedgewick 91 ] : déterminer si un polygone donné est inclus
dans un autre polygone. Un polygone est une liste de points (ou un anneau de points).

– Transect : chercher les points d’intersections d’une droite (ou d’un ensemble de segments
de droite représenté par une liste de points) avec un ensemble de « courbes de niveau »
(des listes de points, chaque liste possédant une altitude).

– Mariages stables [ Sedgewick 91 ] : soit n hommes et n femmes. Chaque individu de


chaque ensemble a ordonné les individus de l’autre ensemble selon une liste de préférence.
Former des couples tels que chaque individu soit satisfait au maximum.

– Permutation par échange de voisins [ Arsac 83 ] : produire toutes les permutations


possibles d’un vecteur donné en échangeant deux éléments voisins.

– Permutation de distance minimale : produire toutes les permutations possibles d’un vecteur
donné en limitant la distance entre les éléments échangés

– Coloriage d’une carte [ Lignelet 90 ] : colorier une carte contenant des régions en utilisant
le minimum de couleurs possibles de telle façon que deux régions voisines soient de
couleurs différentes. La carte peut être représentée par un graphe : deux nœuds sont reliés
si les régions correspondantes sont voisines.

– Flot maximum [ Sedgewick 91 ]: soit un réseau de pipelines reliant un site de production


et un site de stockage. Chaque pipeline possède un diamètre (sa capacité) ainsi qu’une
vanne pour limiter la quantité de produit qui le traverse. Calculer l’ouverture de chaque
vanne pour que le maximum de flots puisse arriver rapidement au site de stockage.

– Multiprocesseur : relier n processeurs donnés en minimisant le nombre (maximum et


moyen) de liens entre 2 processeurs et le nombre de liens par processeur.

– Centre d’un graphe : déterminer le nœud d’un graphe tel que la distance qui le sépare du
nœud le plus lointain soit la plus petite possible.

65
– Triangulation de Delaunay [ Sedgewick 91 ] : le polygone de Voronoï d’un point « cible »
est l’ensemble des points de l’espace qui soient plus proche de lui que tout autre point
cible. Construire le graphe représentant ces polygones.

– Arbre 2D équilibré : étant donné un ensemble de points en 2D, construire un arbre qui
permet de trouver rapidement les points ayant une abscisse et une ordonnée donnés.

– Points d’articulation [ Sedgewick 91 ] : déterminer les points d’un graphe dont la


suppression rend certains points inaccessibles.

– Tracé d’un graphe planaire : tracer un graphe tel qu’aucun arc ne croise un autre.

– Voyageur de commerce [ Sedgewick 91 ] : parcourir un graphe (étiqueté) en passant une


seule fois dans tous les nœuds et en réduisant au minimum la distance totale parcourue.

– Nombres heureux [ Arsac 83 ] : on écrit les entiers à partir de 2


2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
Le premier nombre de la suite est « heureux » : h(1) = 2. On raye les nombres de 2 en 2
3 5 7 9 11 13 15 17 19 21 23 25 27 29 31
Le premier nombre de cette suite est heureux : h(2) = 3, puis on raye les nombres de 3 en
3... Calculer h(i) connaissant h(1) à h(i–1)

– Traduction d’un nombre [ Gramm 86 ] : écrire un nombre en toutes lettres et vice versa

66
CONCLUSION

Nous terminons par un résumé de l’histoire des langages de programmation. Le premier


langage de haut niveau connu est Fortran (« Formula Translator », traducteur de formule),
qui, comme son nom l’atteste, a été écrit en 1954 par J. Backus pour les mathématiciens qui
ne connaissaient (voire ne connaissent) que les matrices et les GOTO. Ces derniers ont
soulevé une énorme polémique, après la publication de la lettre ouverte d’E.W. Dijkstra en
1968 : GOTO Statement Considered Harmful.

Ce langage a survécu pendant longtemps, à notre connaissance, il y a un Fortran 90, sans


aucune amélioration majeure : pas de pointeur, d’article, de type défini par l’utilisateur ni de
récursion, des notions introduites, par exemple, par Algol ( « Algorithmic Language » ) en
1960, résultat d’un groupe international d’experts dirigé par P. Naur, et reprises dans presque
tous les langages impératifs nés dans les années 70, comme ce fut le cas de Pascal, inventé
par N. Wirth en 1969, qui a ajouté en plus une notation plus claire, x:integer au lieu de
integer x, « imité » par le langage C, integer x, conçu en 1972 par B. W. Kernighan et D.
Ritchie.

Mais il a fallu attendre Ada, mis au point en 1979 par une équipe dirigée par J. Ichbiach, pour
voir apparaître les types paramétrés (génériques dans le vocabulaire Ada) et le
polymorphisme qui permet d’éviter la profusion de noms : liste_chaînée,
liste_contigüe, etc. Des efforts ont été aussi fournis pour standardiser la programmation
parallèle, mais le langage souffre encore d’un manque de lisibilité et n’intègre pas la
programmation déclarative introduite par A. Colmerauer dans le langage Prolog ( «
Programming in Logic » ) en 1973. Les prédicats peuvent être vus comme une tendance vers
le polymorphisme, puisque le test d’égalité et l’affectation, par exemple, n’utilisent qu’un
seul identificateur.

Remarquons, par ailleurs que ces deux langages ont été inventés par des universitaires
français, soucieux d’écrire un logiciel « parfait », ils ont d’autres chats… cours à enseigner,
contrairement aux industriels américains qui se retrouveront au chômage le jour où ils
sortiraient le Graal des programmeurs, du genre combinant programmation parallèle et
déclarative avec type paramétré, liste(t:type):=adr(info:t,alt:liste(t)), et des
littéraux liste d’instruction, for x in l do put(x.info) end... Tel est le cas de C++,
inventé en 1983 par B. Stourstrup avec des « versions » qui n’en finissent plus...

67
BIBLIOGRAPHIE

Il faut avoir beaucoup étudié pour savoir peu.


(MONTESQUIEU – Pensées VII, 1144)

Arsac J. – Les bases de la programmation. 1983, Dunod, Paris.


Expose d’autres méthodes de programmation : à partir d’un programme contenant des
aller_en ou récursif, produire un programme itératif clair et efficace.

Borland – Borland C++ 3.0 Programmer’s Guide. 1991, Scotts Valley Drive, CA.
Pratiquement équivalent à l’aide en ligne.

Borland – Turbo Prolog 0.5Owner’s Handbook. 1986, Scotts Valley Drive, CA.
Utile ne serait ce que pour apprendre les éléments communs à tous les compilateurs
Prolog.

Courtin J., Kowarski I. – Initiation à l’algorithmique et aux structures de données. Tome 2 :
récursivité. 1984, Dunod, Paris.
Semblable au présent ouvrage mais ne traite pas les graphes. Contient de nombreux
algorithmes sur la manipulation des listes et des arbres.

Gramm A. – Raisonner pour programmer. 1986, Dunod, Paris.


Donne quelques jolis problèmes avec la recherche systématique de toutes les solutions
possibles.

Lignelet P., Perrot J.F. – Structures de données avec Ada. 1990, Masson, Paris.
Contient, entre autres, les tas dynamiques, les tas avec préservation de l’ordre d’entrée
et le coloriage d’une carte avec 4 couleurs. Démontre en passant la toute–puissance
des types paramétrés du langage Ada.

Meyer B., Bauddin C. – Méthodes de programmation. 1978, Eyrolles, Paris.


Parle des listes, des arbres, des graphes et même de la programmation parallèle ou
déclarative, le tout traité avec humour ! Probablement le meilleur livre d’introduction
à l’informatique malgré sa taille –– plus de 600 pages...

Sedgewick R. – Algorithmes en C. 1991, Interéditions, Paris.


Plus complet que [ Meyer 78 ] avec les B–arbres et de nombreux programmes sur la
géométrie et les graphes. Un ouvrage extrêmement stimulant malgré la notation peu
claire du langage C.

68

Vous aimerez peut-être aussi