Académique Documents
Professionnel Documents
Culture Documents
Lesadressesetlespointeurs
Lesadressesetlespointeurs
Les adresses
Les cases mémoires ont toutes un numéro qui les distingue les
une des autres: ce numéro est appelé adresse.
0
1
2
3
4
. .
. .
. .
max
Les adresses et les variables
Le nom que l’on donne au cases mémoires est traduit en une adresse
juste avant l’exécution d’un programme. Cela est nécessaire afin que le
processeur sache à quelle case mémoire est associée chaque variable.
0
c1: 1 char
c1 = c2; 2
Lire le contenu de la case 3. c2: 3 1324 char
Mettre ce qui a été lu dans la 4
case 1. . .
. .
. .
max
Le partage de la mémoire
Sur les systèmes modernes, il peut y avoir plusieurs usagers
se partageant la mémoire et chaque usager peut exécuter
plusieurs programmes simultanément.
Cela signifie que l’on n’est pas libre d’utiliser toutes les
cases mémoires comme on le veut. Une case peut être
occupée par un programme à un certain moment et libre à un
autre. Cette situation est aléatoire.
n: 0 00000000000000000000000000001101
int int
c1: 4 00011011 char
c2: 5 00011100 .. char
. .
. .
. .
max
Les adresses et les tableaux
Le nom d’un tableau correspond à l’adresse du début du tableau.
Exemple:
char tab[5];
printf(“%p\n”, tab);
4027630992
printf(“%p\n”, tab+1);
4027630993
printf(“%p\n”, tab+2);
4027630994
int tab[5];
printf(“%p\n”, tab);
4027630976
printf(“%p\n”, tab+1); +4
4027630980
printf(“%p\n”, tab+2); +4
4027630984
Question: Pourquoi?
L’incrémentation d’une
adresse
L’adresse a+1 a: 16216 int
.. ..
n’est pas valide . .
b[0]: b=24600 int
b[1]: b+1=24604 int
Incrémenter une adresse ne b[2]: b+2=24608 int
veux pas dire ajouter 1, cela b[3]: b+3=24612 int
veut dire aller à l’adresse
suivant la variable courante. .. ..
. .
En général cela n’a du sens d[0]: d=54316 char
que si on est dans un tableau. char
d[1]: d+1=54317
d[2]: d+2=54318 char
.. ..
. .
Remarque
Exemple:
char c;
int n, tab[1000];
Exemple:
char c;
int tab[1000];
On peut:
Exemple:
.. ..
. .
Les pointeurs: exemple
Exemple:
int tab[10], *p;
p=tab;
tab[3] = 70;
*(tab + 3) = 70;
tous équivalents:
p[3] = 70;
*(p + 3) = 70;
Remarque
Le nom d’un tableau est une adresse constante et non pas
un pointeur qui est une variable.
Exemple:
int tab[10], *p;
p=tab;
p = tab; /* Valide */
tab = p; /* Non valide */
0 1 2 3 4 5 6 7 8 9 10
tab:
p:
Quelques utilités des
pointeurs
En C++: echanger(a, b)
tmp = x;
x = y;
y = tmp;
}
Les pointeurs comme
paramètres
En C: echanger(&a, &b)
tmp = *x;
*x = *y;
*y = tmp;
}
Les tableaux passés en
paramètre
Une des plus importantes utilisation des pointeurs réside
dans le passage des tableaux en paramètre.
tab:
ptab:
Les indices de tableaux
Exemple 2
-65 0 1 2 3 4 21 22 23 24 25
tab:
ptab:
Allocation statique et
dynamique
Jusqu’à maintenant, toutes la mémoire que nous avons
utilisée dans nos programmes devait avoir été allouée avant
l'exécution à l’aide des déclarations de variables.
- malloc
- free
- calloc
- realloc
(void *)malloc(size_t size)
p = (int*) malloc(sizeof(int))
*p = 10; /* VALIDE */
Avant malloc p:
Après malloc p:
Exemple 2
int *p;
p = (int) malloc(sizeof(int))
*p = 10; /* VALIDE */
Pourquoi?
Avant malloc p:
Après malloc p:
Pointeurs sur void
double x;
int n;
char c;
Dans la construction
(nom de type) expression
l’expression est convertie dans le type précisé (selon
certaines règles).
Exemple 3
int *p;
struct complexe *cplx;
p = (int *) malloc(sizeof(int));
cplx = (struct complexe *) malloc(sizeof(struct complexe));
void free(void * p)
int *p, n;
scanf(“%d”, &n);
p = (int *) calloc(n, sizeof(int));
p: …
Exemple 5
Liste chaînée
struct noeud{
int valeur;
noeud *suivant;
};
chaine:
p:
Exemple 5
chaine = (struct noeud) malloc(sizeof(struct noeud));
valeur suivant
chaine:
p:
Exemple 5
chaine = (struct noeud) malloc(sizeof(struct noeud));
chaine -> valeur = 8;
chaine: 8
p:
Exemple 5
chaine -> suivant = (struct noeud) malloc(sizeof(struct noeud));
chaine: 8
p:
Exemple 5
p = chaine -> suivant;
chaine: 8
p:
Exemple 5
p -> valeur = 5;
chaine: 8 5
p:
Exemple 5
p -> suivant = (struct noeud) malloc(sizeof(struct noeud));
chaine: 8 5
p:
Exemple 5
p = p -> suivant;
chaine: 8 5
p:
Exemple 5
p -> valeur = 13;
p -> suivant = NULL;
chaine: 8 5 13 0
p:
• Nous venons ainsi de créer une liste de
valeurs qui sont liées entre elles par des
pointeurs. Autrement dit, ces valeurs en
mémoire peuvent être dans des endroits
non contigues. Pou accéder ces valeurs, il
suffit d’avoir l’adresse de la première
valeur qui est dans le pointeur chaine.
Impression des valeurs d’un chaine
• Soit une liste chainée en mémoire
centrale, dont le premier élément est à
l’adresse chaine.
chaine: 8 5 13 0
p:
Exemple 6
• La solution précédente consiste à mettre
l’adresse du premier élément dans le
pointeur p. La valeur p->valeur est alors
affichée; pour aller à l’élément suivant, il
suffit d’avoir son adresse qui est dans
p->suivant. Ce processus est répété jusqu’à
atteindre l fin de la chaine qui est donnée
par le pointeur NULL.
Suppression d’un élément dans
une liste chainée
• Soit une liste chainée en mémoire
centrale, dont le premier élément est à
l’adresse chaine.
Q->Suivant = P->suivant
free(P)
chaine: 8 5 13 0
P
Q
chaine: 8 5 13 0
chaine: 8 V 13 0
P X
Q
L’algorithme est alors comme suit:
• p = chaine; trouve = 0
• while ((p != NULL) && (!trouve))
• if (p->valeur = V)
• trouve =1;
• else p = p->suivant;
• If (trouve) /* l’élément existe */
• {
• Q = (struct noeud) malloc(sizeof (struct noeud));/* alloue une addresse mémoire
• Q->valeur = X; /* y mettre la valeur X */
• Q-> suivant = p->suivant;
• P->suivant = Q;
• }
• else printf(‘élément inexistant’);
•
2. Insertion avant un élément de valeur V:
La solution consiste dans un premier temps à chercher si cet élément
de valeur V existe dans la liste. Si cet élément n’existe pas, il n’y a
rien à faire. Sinon, on procède comme suit:
- soit P l’adresse de cet élément
- allouer un espace mémoire pour ce nouvel élément à l’aide de la
fonction malloc; soit Q l’adresse de cet espace mémoire.
- après insertion, l’élément X sera entre l’élément de valeur V et le
l’élément précédent de V. Autrment dit, le précédent de V aura pour
élément suivant l’élément X et le précédent de V sera maintenant le
nouvel élément X. Pour effectuer ces opérations, on aura besoin de
l’adresse de l’élément précédent de V. Soit preced cette adresse.
Notons que si V est le premier élément, alors après insertion de X,
ce sera X qui sera le nouveau premier élément de la liste.
En termes algoritmiques, on exprime cette idée comme suit:
• Si p == chaine (signifie que V est le premier élément de la liste)
l’insertion va se faire comme suit:
Q->suivant = chaine (le suivant de X est V)
chaine = Q (X devient le premier élément de la liste chaînée)
chaine: 8 V 13 0
X
P
preced
Q
chaine: V 5 13 0
chaine: Null
• La déclaration de cette structure de
données est comme suit:
2 5
Définition: Un arbre binaire est dit de recherche (binary
search tree) si, pour chaque noeud V, tous les éléments
qui aont dans sous arbre gauche de V ont des valeurs
inférieures à celle de V, et tous les éléments du sous-
arbres droit de V sont supérieurs à celle de V.
Exemple: 8
6 10
4 7
15
13
• Remarqez que pour une suite de
nombres, il existe différentes arbres
binaires de recherche correspondant à
cette liste. Tout dépend de la manièe dont
sont présentés ces nombres.
• La déclaration de cette structure de
données est comme suit:
Arbre binaire
return(racine);
}
typede fin = -999;
noeud *creation(noeud *racine){
*racine = NULL;
for (scanf (“%d”, &donnee); donnee != fin; scanf
(“%d”, &donnee))
inserer(racine, donnee);
if (trouve)
printf(‘élément trouvé’);
else printf(élément inexistant’);
return(p)
}
La version récursive est comme suit:
}
Suppression d’un élément dans un arbre binaire de recherche
Cette opération doit être telle que l’arbre qui va en résulter doit toujours rester un arbre binaire de
recherche.
Cette opération est un peu plus complexe et plus longue que celle de l’insertion bien qu’elle ne
consiste qu’en quelques miouvements de pointeurs
On peut penser que la suppression d’un élément consiste à le trouver, puis à relier la structure sous-
jacente à celle qui subsiste.
La structure de la fonction supprimer semble similaire à celle de la fonction rechercher.
Elle l’est effectivement, mais dans le cas où l’élément à supprimer est trouvé, il convient de dsitinguer
plusiers cas de figures:
Élément à
B supprimer
A C
D’après la fonction de création, tous les éléments de C sont plus
grands que ceux de A.
Premier cas particulier: celui où C est vide. Dans ce cas, A devient la
racine.
Dans la cas contraire, on al choix entre A et C, pour la racine.
C
Avec C comme racine, nous aurions:
A
• CAS 2: C’est le cas où l’élément enlevé n’est pas la
racine de l’arbre.dans ce cas, il faut alors anticiper la
recherche de cet élément pour pouvoir raccrocher les
pointeurs de la structure sous-jacente à ceux de la
structure sus-jacente à l’élément concerné.
B Élément à supprimer
A C
Si on supprime B, on a donc:
A<C <D
• Ceci implique, soit une structure du type
suivant, qui réalise un chagement de
racine et une rotation de toute la structure
à droite:
A D
• Soit une structure de cet autre type, où la structure sus-jacente n’est
pas modifiée:
A
• Dans le cas où C, on a simplement
A
• Nous retiendrons, et programmerons, la
deuxième solution.
Élément à supprimer
E G
• Dans ce cas, nous avons:
D<E<F<G
Si on supprime F, on a:
D<E<G
D G
Ou bien:
G
• Le raisonnement est tout à fait similaire, et
symétrique, à celui qui a été vu à gauche.
Parcours postfix
Parcours infix
Parcours prefix
Exemple
• Dans la parcours postfix, pour un noeud
donné, il est visité aprés les noeuds de sa
partie gauche et de sa partie droite.
12 23
8 15 20 27
6
9 14
Le parcours en postfixe
6 9 8 14 15 12 20 27 23 21
Le parcours en infixe
6 8 9 12 14 15 20 21 23 27
Le parcours en prefixe
21 12 8 6 9 15 14 23 20 27
void postfixe(noeud *racine){
if (racine != NULL){
postfixe(racine->gauche);
postfixe(racine ->droite);
printf(‘%d’,racine->valeur);
}
}
........
Tk
T1 T2 T3
• Le parcours en prefixe des noeuds de cet arbre
est la racine n suivie des neouds de T1 en
prefixe, puis ceux de T2, T3, ... Et Tk en prefixe
• Le parcours en infixe des noeuds de cet arbre
est lesnoeuds de T1 en infixe, suivi de la racine
n, suivies des noeuds de T2, T3, ... et Tk en
infixe
• Le parcours en postfixe des noeuds de cet arbre
est les noeuds de T1, puis de T2, T3, ... et Tk
en postfixe, ensuite la racine n
Le parcours en postfixe se fait comme suit:
• Prefixe(V)
• Pour chaque enfant C de V, de gauche à
droite, faire prefixe(C);
• Visiter C;
• Le parcours en infixe se fait comme suit:
Infixe(V)
if V est une feuille
visiter V;
else{
infixe(enfent gauche de V)
visiter V
pour chaque enfant C de V, de gauche à
droire, excepté le plus à gauche,
faire infixe(C)
}
• Le parcours en postfixe est comme suit:
2 4
3
5 6
7 8 9
10 11 12
13 14
• En prefixe, le parcours de cet arbre est
comme suit: