Structures de données dynamiques

 

Les variables statiques:

Pour réserver de la mémoire dans nos programmes, nous devions déclarer des variables.

Ces variables pouvaient être globales ou locales.

Lorsqu'elles sont globales, la mémoire est allouée (réservée) au  début du programme et est libérée lorsque le programme se termine.

Lorsque les variables sont locales à une procédure ou fonction, la mémoire est allouée au début de la procédure ou fonction et la mémoire est libérée à la fin de celle-ci.

 

L'allocation dynamique de la mémoire va nous permettre de réserver de la mémoire pour une variable quand on le désire et de la libérer quand on le souhaite.

Il y aura donc gestion plus fine de la mémoire et donc moins de gaspillage.

 

La Pile ou la mémoire vive:

Chaque programme réserve une partie de la mémoire vive pour son usage personnel: Stockage des variables locales.

Cette zone de mémoire s'appelle la Pile.

Cette quantité de mémoire réservée pour le programme peut être modifiée.

 

 Sous Delphi elle se paramètre comme ceci:

Projet → option → lieur → pile → taille minimale & taille maximale

Question: Notez ici les tailles de piles maximales et minimale de votre projet.

 

Plus vous avez besoin de mémoire plus la taille maximale de la pile doit être élevée.

 

Faisons abstraction de la notion de mot mémoire.

La mémoire est constituée de cellules d'un octet chacune. Chaque cellule a une adresse. (son numéro).

Les variables sont stockées dans la pile. Chaque variable a une adresse: l'adresse de la première cellule qui lui est réservée.

Prenons par exemple une variable de type réel "prix" Admettons que le réel est stocké sur 8 octets .Son adresse sera l'adresse de la première cellule qui lui est réservée. "6825300".

6825308

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

6825300

 

6825299

 

En jaune, vous voyez l'ensemble de la zone mémoire réservée et en rouge l'adresse de notre variable.

Une variable déclarée ensuite aura comme adresse: 6825308 l'adresse de la première cellule libre de la pile. Remarque alors que les variables globales sont stockées en montant du bas de la pile au milieu de la pile. Les variables locales sont elles stockées depuis le sommet de la pile vers le milieu.

 

Exercice sous Delphi:

Déclarez deux variables de type "byte".

Faire afficher les adresses des deux variables.

On utilisera la fonction addr(nom de variable) qui fournit l'adresse de la variable dont le nom est passé en paramètre.

Attention cette adresse doit être transtypée ou convertie en "INTEGER";

Edit1.text:=intToStr(Integer(addr(lavariable)));

 

Calculer la différence entre les deux adresses qu'en déduit-on sur la taille réservée pour un byte?

 

Faire de même pour deux variables de type "INTEGER"  sur combien d'octets tient un INTEGER ?

 

Puis deux "REAL"; puis deux clients du type TClient où :

TClient = record

nom:string(25);

prenom:string(10);

               end;

 

 

Notion de pointeur:

Pour allouer dynamiquement de la mémoire au fur et à mesure des besoins, réserver des zones dans la pile, nous avons besoin de la notion de  pointeur.

 

Qu'est-ce que c'est ?

Un pointeur est une variable donc il faut le déclarer.

Un pointeur pointe sur quelque chose ou sur rien.

Lorsqu'un pointeur pointe sur rien il est égal à nil  (Non Identified Link).

La valeur d'un pointeur est l'adresse de début  de la zone mémoire sur laquelle il pointe.

 

Un pointeur est une variable:

Et oui, tout pointeur doit être déclaré.

Var monpointeur : POINTER;  ici on ne sait pas sur quel type de donnée il pointe.

      Monpointeur: ^integer;                   se lit "pointeur sur entier"

      Monpointeur:^TClient;                   se lit pointeur sur TClient.(type structuré)

 

Exercice:

Procedure form1.button1click(sender:tobject);

Var  monentier:integer;

       monpointeur:^integer;

begin

      monentier:=4;

      monpointeur:=addr(monentier); //on fait pointer monpointeur sur la zone réservée à

monentier

      if monpointeur^ =4 then // se lit si la zone référencée par monpointeur est égale à 4 …

            label1.caption:= 'j''ai bien l''impression qu''il pointe sur monentier';

      monpointeur^:=8; la zone référencée par monpointeur reçoit 8

      label2.caption:=inttostr(monentier);

end;

Question indiquez le texte de label1 et label2 après exécution de ce code

 

Faites afficher l'adresse de monentier et faites afficher monpointeur.

 

Qu'a-t-on appris:

Monpointeur est en fait l'adresse de début de la variable qu'il référence.

Afficher(Monpointeur^) affiche le contenu de la zone mémoire dont l'adresse est Monpointeur.

Monpointeur^:=25; écrit 25 dans la zone mémoire référencée par Monpointeur.

 

 

La déclaration d'un pointeur:

Déclaration simple:

   P_val_min, P_val_max: ^integer;

Se lit: P_val_min, P_val_max: pointeurs sur entier;

Cela signifie que dans les zones réservées aux deux pointeurs, on ne pourra mettre que des entiers.

Cela signifie aussi que les zones réservées auront une taille permettant d'y stocker des entiers.

 

Construction d'un type de pointeur:

Construisons un nouveau type ptr_sur_entier;

 

Type Ptr_sur_entier =^integer;

 

Se lit Type Ptr_sur_entier = pointeur sur entier

 

Déclarons maintenant deux pointeurs de ce type

Var ptr_entier1,ptr_entier2: Ptr_sur_entier;

On se retrouve dans la configuration précédente.

 

Lorsqu'un pointeur est déclaré il ne pointe sur rien.

Nil: not identified link

Ces deux pointeurs sur entiers sont donc déclarés mais ils sont encore = à nil.

Ils ne pointent pour l'instant sur rien.

Pour qu'ils pointent sur quelque chose, il faut soit leur donner comme valeur l'adresse d'une variable existante comme  dans le code précédent: ptr_entier1:=addr(monentier);

Allocation Dynamique de mémoire NEW

Soit allouer dynamiquement un ensemble de cellule dans la pile.

New(ptr_entier1)

Entrons dans les détails. La procédure new fait trois choses:

  1. Elle recherche la première cellule vide de la pile
    exemple 62405000.
  2. Elle met son adresse dans le pointeur qui vaudra maintenant 62405000.
  3.  Elle réserve le nombre de cellule nécessaire au stockage du contenu.

 

 Elle réservera donc une cellule si ptr_entier1 est un pointeur sur byte, 4 cellules si ptr-entier1 est un pointeur sur INTEGER et par exemple 26 cellules si ptr_entier1 est un pointeur sur chaîne(25).

 

 

Examinons cet exemple

Begin

   //premier exemple:

   new(ptr_entier1); //allocation de mémoire pour l'entier sur lequel va pointer ptr_entier1

la valeur de ptr_entier1 est l'adresse de la zone mémoire qui vient d'être réservée.

  Ptr_entier1^:=12; // on écrit 12 dans la pile à l'adresse ptr_entier

                                   

  Afficher(Ptr_entier1^ *2) //va afficher 24

  Free(Ptr_entier1) libère la mémoire qui pourra à nouveau être utilisée.

 

End;

 

Libération dynamique de mémoire free:

 

Free(LePointeur) ceci va libérer la zone de la pile référencée par LePointeur et mettre nil dans la variable LePointeur;

Attention: Toute allocation dynamique doit être libérée sinon vous obtenez un programme qui plante au bout d'un certain temps.

Attention encore la mise à NIL du pointeur doit souvent être forcée.

LePointeur:=Nil;

 

Utilisation des pointeurs:

Voici un exemple d'utilisation des pointeurs.

 

Soit le type suivant:

TYPE

T_Ptr_client =^T_Client;

T_Client= enregistrement

            Nom:string;

            Pnom:string;

End;

 

Var ptr_moncli:T_Ptr_client;

Begin

   //Réservation de mémoire pour un client:

  new(ptr_moncli); //la quantité de mémoire réservée est fonction du type du contenu vers

lequel pointe le pointeur.

   Ptr_moncli^.nom:=dupond;

  Ptr_moncli^.prenom:=dupond;

  Afficher(Ptr_moncli^.nom)

  Afficher(Ptr_moncli^.prenom)

  //ne pas oublier de libérer la mémoire allouée:

  free(Ptr_moncli);

End;

 

 

On voit dans cet exemple qu'un pointeur peut pointer sur un type utilisateur structuré.

 

Afin de ne pas avoir à déclarer autant de pointeurs sur client que l'on a de client on doit créer des structures dynamiques de données.

 

On pourra créer des piles, des files, des listes chainées doublement chainées et circulaires ou des arbres .

 

Les Listes chaînées:

T_Ptr_client =^T_Client;

T_Client= enregistrement

            Nom:string;

            Pnom:string;

            P_suiv:T_Ptr_client;

End;

 

Tout client pointe sur le client suivant grâce à p_suiv.

Var p_prem: T_Ptr_client;

 

Procedure ajoutclient;

begin

//Création de  client :

if P_prem=nil then

begin

New(P_prem);

P_prem^.nom:=editnom.text;

P_prem^.pnom:=editpnom.text;

End

Else

begin

   //Ajout d'un client

 

New (p_nouv);

P_nouv^.nom:=editnom.text;

P_nouv^.pnom:=editpnom.text;

//         

p_cli:=p_prem_cli;

while p_cli^.suiv<>nil do

begin

   p_cli:=pcli^.suiv;

end;

pcli^.suiv:=p_nouv;

end;

 

Exercices:

Ecrire la procédure de suppression d'un élément de la liste.

Ecrire une fonction renvoyant le nombre d'élément d'une liste.

 

Les listes doublements chaînées.

Les listes circulaires.

Les Arbres binaires: