home *** CD-ROM | disk | FTP | other *** search
/ DP Tool Club 17 / CD_ASCQ_17_101194.iso / dos / prg / alb_c10 / chap_08 / chap_08.txt < prev   
Encoding:
Text File  |  1994-10-03  |  18.1 KB  |  376 lines

  1.  
  2. ==========================================================================
  3.                              APPRENTISSAGE du C             version ALB_10                
  4.                              
  5.                          CHAPITRE 8: DES POINTEURS.                   
  6. ==========================================================================
  7.  
  8.  
  9.                         1. LA RELATION DE BASE.
  10.                         ========================                                       
  11.  
  12.                                                                 ************  
  13.       Nous avons déjà rencontré et utilisé des pointeurs.         CH08_01.C
  14.    Leur propriété fondamentale est appliquée dans ces deux        CH08_02.C
  15.    petits programmes. Tout le reste découle de ces propriétés.  ************
  16.  
  17.                 Avant d'analyser ces programmes, relisez attentivement 
  18.                 le paragraphe 5.5. du chapitre 3 et accrochez vous!
  19.  
  20.    1°  Dans le premier programme, on déclare deux variables entières: une 
  21.    variable normale "a" et un pointeur "*pa". 
  22.    
  23.    Souvenez vous: 
  24.    
  25.         La déclaration de la variable ordinaire "a" entraîne la réservation 
  26.         d'une zone de mémoire dont la taille est fonction du type de "a". 
  27.         Le système crée une table qui conserve son nom et son adresse que 
  28.         nous pouvons connaître en utilisant l'opérateur de référencement "&a". 
  29.         C'est pourquoi cette adresse est aussi appelée la référence de la 
  30.         variable.
  31.         Au moment de l'initialisation, a= 32, le système cherchera dans ses
  32.         tables une variable du nom de "a" et placera à son adresse 32.
  33.  
  34.       La DECLARATION du pointeur "*pa" entraîne la réservation d'une zone 
  35.    de mémoire dont la taille est celle d'une adresse, 2 octets, la taille 
  36.    d'un entier non signé.
  37.    La notation *pa nous indique  que le pointeur est le résultat d'un 
  38.    déréférencement d'une variable pa. 
  39.    L'application de l'opérateur de référencement "&" au pointeur nous donne  
  40.    &(*pa)= pa. Nous appellerons donc "pa" la référence du pointeur. 
  41.    C'est une variable adresse, placée dans la zone de mémoire réservée au 
  42.    moment de la déclaration du pointeur. 
  43.    Cela ne présente en général que peu d'intérêt mais on pourrait connaître 
  44.    l'adresse de cette zone en faisant &pa.
  45.  
  46.       L'INITIALISATION du pointeur se fait en affectant à la référence "pa" 
  47.    l'adresse de la variable "a". Le programme affiche la valeur de cette 
  48.    adresse. L'adresse de la variable "a" a été fixée une fois pour toutes par 
  49.    le compilateur au moment de la déclaration de "a" et lui restera attachée 
  50.    jusqu'à sa destruction à la fin du programme. C'est une constante.
  51.       Par contre l'adresse qui est le contenu de la référence "pa" pourra 
  52.    être modifiée. La référence est une variable.
  53.  
  54.         La variable et le pointeur ont maintenant la même référence.
  55.         Puisque le programme utilise cette référence, cette adresse, pour
  56.         écrire des valeurs dans la mémoire, il y aura une équivalence entre
  57.         le pointeur et la variable. Vérifions:
  58.   Chapitre 8: Des pointeurs                                         page 1
  59.                                                       
  60.  
  61.  
  62.       La variable "a " est initialisée à 32. 
  63.       On constate que c'est aussi la valeur de "*pa". 
  64.       Le pointeur indique la valeur de la variable qu'il pointe, 
  65.       variable dont l'adresse est la valeur de sa référence "pa".
  66.  
  67.                     ------------------------------------- 
  68.                     |  pa= &a   =>    *(pa)= *(&a)= a   |
  69.                     -------------------------------------
  70.  
  71.    Remarque 1: Une adresse a pour valeur un nombre, mais par sa nature ce 
  72.                n'est pas un nombre ordinaire. 
  73.                
  74.                         int a, b;
  75.                         b= &a;     est incorrect et générera une erreur
  76.                                    
  77.               Ici, b n'est pas une adresse, c'est une variable ordinaire. 
  78.               
  79.    Remarque 2:  La déclaration et l'initialisation du pointeur se sont faites 
  80.                 en deux temps:
  81.  
  82.                         int a, *pa;
  83.                         pa= &a;         
  84.  
  85.       Evidemment il faut d'abord déclarer la variable "a". Puis on affecte 
  86.       à la référence du pointeur, qui est une variable ADRESSE l'adresse "&a". 
  87.  
  88.       Une écriture permet de faire ces deux opérations simultanément: 
  89.    
  90.                         int a, *pa= &a;
  91.  
  92.       Cette méthode est recommandée car il est dangereux de laisser se 
  93.       promener un pointeur sans affectation dans un programme. 
  94.       
  95.    Remarque 3:  On initialise un pointeur en lui affectant une adresse et 
  96.                 non pas un nombre.
  97.                         
  98.                         int *pa= 16;    est incorrect car à l'initialisation 
  99.                                         on doit lui affecter une adresse. 
  100.    
  101.    Remarque 4:  Dans certains cas, quand il n'est pas possible de déclarer 
  102.    d'abord la variable, il faut laisser le pointeur en attente d'affectation. 
  103.    C'est très dangereux car il pointe sur n'importe quoi. Si plus loin dans 
  104.    le code quelqu'un écrit un jour:  *pa= x; la valeur de la variable x sera 
  105.    copiée à l'adresse mémoire pa, c'est à dire n'importe où.
  106.    Pour éviter ce risque on affecte au pointeur la constante symbolique NULL 
  107.    dont la valeur est zéro et qui est définie dans stdio.h, string.h, mem.h, 
  108.    stdlib.h, alloc.h. Il faut réserver ce symbole aux pointeurs pour des 
  109.    raisons de clarté.
  110.                                 int *pa= NULL;
  111.    
  112.    La plupart des compilateurs actuels considèrent cette adresse comme 
  113.    interdite et provoquent une erreur si on cherche à y écrire. Dans ces
  114.    conditions un pointeur NULL ne pointe sur rien, alors qu'un pointeur
  115.    non affecté pointe sur n'importe quoi.
  116.  
  117.    Ce petit programme donne à réfléchir mais n'a guère d'intérêt pratique.
  118.    Nous avons modifié un pointeur à partir d'une variable mais nous aimerions
  119.    plutôt faire le contraire, modifier une variable à partir d'un pointeur.
  120.   Chapitre 8: Des pointeurs                                         page 2
  121.                                                       
  122.  
  123.  
  124.    2°  Nous déclarons la variable "a", sans l'initialiser, et le pointeur 
  125.    "*pa" que nous initialisons aussitôt avec l'adresse "&a".
  126.  
  127.    La valeur 16 est affectée au pointeur:   *pa= 16; cette fois le pointeur 
  128.    possède une référence. On peut lui affecter un nombre.
  129.  
  130.    Nous constatons que la valeur de la variable "a" a été indirectement 
  131.    initialisée. De là viens le terme anglais "indirection" que nous 
  132.    traduisons par déréférencement. Chez nous les latins, c'est la définition 
  133.    qui sert à nommer l'opération, alors que chez les anglo-saxons c'est son 
  134.    utilité!  
  135.                      -------------------------- 
  136.                      |  pa= &a;  =>   a= *pa  |
  137.                      --------------------------
  138.  
  139.                                                                 ************
  140.       Ce petit programme peut être une source de méditation!      CH08_03.C
  141.       Mais surtout pas un modèle de programmation!              ************
  142.       Cette fois ci, variables et pointeurs sont de type double.
  143.  
  144.  
  145.  
  146.  
  147.                 2. PRINCIPALES PROPRIETES DES POINTEURS.
  148.                 ========================================
  149.  
  150.    Nous venons de voir :
  151.  
  152.    1° Un pointeur permet de mémoriser l'adresse d'une variable ordinaire et 
  153.       donc de la modifier, nous avons utilisé cette propriété pour passer des 
  154.       arguments à une fonction.
  155.    2° Il peut prendre la valeur symbolique NULL qui représente le zéro.
  156.  
  157.    Mais aussi:
  158.  
  159.    3° Un pointeur peut prendre la valeur d'un autre pointeur, quand ils 
  160.       pointent des objets de même type.
  161.    4° On peut retrancher ou ajouter une valeur entière à un pointeur.
  162.    5° On peut calculer la différence de deux pointeurs lorsqu'ils pointent 
  163.       des éléments d'un même tableau.
  164.  
  165.       D'une manière générale on peut faire avec des pointeurs des opérations
  166.       qu'il semble raisonnable de faire avec des adresses.
  167.       La multiplication ou de la division de deux adresses semblent a priori
  168.       des choses peu raisonnables. La plupart des compilateurs rejettent les
  169.       opérations incorrectes.
  170.   Chapitre 8: Des pointeurs                                         page 3
  171.                                                       
  172.  
  173.  
  174.                        2.1. Affectation de pointeurs:             
  175.                        -------------------------------          
  176.                                                                 ************
  177.    2.1.1.  Affectation de références de pointeurs:               CH08_04.C
  178.                                                                 ************
  179.  
  180.    1° On déclare un entier et deux pointeurs sur des entiers. L'un des 
  181.    pointeurs n'est pas initialisé aussitôt et on lui affecte la constante
  182.    symbolique NULL, pour signaler qu'il n'est pas lié à une variable.
  183.  
  184.    Nous avons déclaré des variables de type entier. Le compilateur réserve 
  185.    pour chacune, une zone mémoire de 2 octets. Pour des variables de type 
  186.    double, il aurait réservé des zones de 8 octets. On ne pourra donc pas 
  187.    mélanger les types, associer des variables sur 8 octets avec des pointeurs 
  188.    sur 2 octets pour stocker les mêmes valeurs.
  189.  
  190.    2° Affectation de la référence de *pb à celle de *pa:         pa= pb; 
  191.    
  192.       Les adresses pointées, exprimées par les références, sont maintenant 
  193.       les mêmes, celle de la variable "b" attribuée par le compilateur au 
  194.       moment de sa déclaration et qui peut changer à chaque compilation.
  195.       
  196.  
  197.       Il sera possible d'agir sur la variable "b" par l'intermédiaire des 
  198.       pointeurs *pa ou *pb. En effet, si on donne à *pa la valeur 32, on 
  199.       constate que b et *pb prennent la même valeur.
  200.  
  201.                                                                 ************
  202.    2.1.2.  Affectation de pointeurs:                              CH08_05.C
  203.                                                                 ************
  204.  
  205.       * Faisons l'état des lieux avec printf():
  206.  
  207.         Les deux pointeurs ont des valeurs différentes et pointent des 
  208.         variables différentes. Notez que la référence pa du pointeur contient 
  209.         l'adresse de "a" et que cette adresse est à 8 octets plus loin dans 
  210.         la mémoire que celle de "b". C'est normal puisque la taille d'un 
  211.         double est de 8 octets. Si nous avions utilisé des variables de type 
  212.         int le décalage n'aurait été que de 2 octets.
  213.  
  214.       * Le pointeur *pb est affecté à *pa. Quelles en sont les conséquences?
  215.  
  216.          Les deux pointeurs ont la même valeur et pointent deux variables 
  217.          différentes a et b puisque leur référence respective ne change pas.
  218.  
  219.       En somme, l'affectation d'une référence à une autre permet de pointer 
  220.       la même variable et l'affectation d'un pointeur à un autre de rendre 
  221.       égales les variables qu'ils pointent.
  222.   Chapitre 8: Des pointeurs                                         page 4
  223.                                                       
  224.  
  225.                                                                 ************
  226.         2.2. Incrémentation et décrémentation de pointeurs:       CH08_06.C
  227.         ----------------------------------------------------    ************
  228.  
  229.       1° L'incrémentation de *pe se fait en utilisant des parenthèses car
  230.    l'opérateur (++) a la priorité sur l'opérateur  d'indirection (*), 
  231.    consultez le paragraphe 6. du chapitre 3.
  232.  
  233.       Nous avons écrit,         (*pe)++   incrémenter le pointeur *pe.
  234.  
  235.       puis:                     *pe++;    pe a d'abord été incrémentée 
  236.                                           puis déréférencée.
  237.  
  238.    *  Dans le premier cas le pointeur pointe la variable "entier" puisque 
  239.       la référence ne change pas. Leur valeur commune est incrémentée.
  240.  
  241.    *  Dans le second cas la référence pe est incrémentée de la valeur d'un 
  242.       "int", soit 2 octets. 
  243.       
  244.         Quelle adresse, si s'en est une, allons nous y trouver? 
  245.         Totale incertitude, la porte de l'enfer des programmeurs est ouverte!
  246.         Notre pointeur *pe va désormais pointer l'inconnu. 
  247.         Ma machine me donne:
  248.                                         entier: 17 , *pe: 6814 , pe: 6736
  249.  
  250.         Ce qui veut dire qu'à l'adresse 6736 de la mémoire on trouve la 
  251.         valeur 6814.
  252.  
  253.       C'est donc dangereux quand on utilise des variables normales parce 
  254.       qu'on ne sait pas ce qui suit dans la mémoire. Par contre dans 
  255.       les tableaux on sait parfaitement ce qu'il y a dans la zone qui suit 
  256.       et les pointeurs deviennent le moyen d'accès privilégié aux valeurs 
  257.       qu'ils contiennent. Nous les utiliserons largement dans le chapitre 
  258.       qui leur est dédié.
  259.  
  260.       2° Nous venons de voir les effets de l'incrémentation de la référence.
  261.    Si nous décrémentons maintenant la référence pe, nous revenons à la valeur 
  262.    initiale et nous pointons donc à nouveau la variable "entier" de valeur 17.
  263.  
  264.   Chapitre 8: Des pointeurs                                         page 5
  265.                                                       
  266.  
  267.  
  268.                                                                 
  269.                         3. QUELQUES POINTS PARTICULIERS:      
  270.                         =================================   
  271.  
  272.    3.1. Pointeurs sur des fonctions.
  273.    ---------------------------------
  274.  
  275.       C'est une catégorie particulière de pointeurs qui comme les pointeurs 
  276.    que nous connaissons permettent d'accéder à des adresses mémoire. Mais
  277.    dans ce cas les adresses sont celles de fonctions. Nous allons voir dans 
  278.    un exemple simple le mécanisme de l'emploi de ces pointeurs. 
  279.  
  280.         Nous étudierons plus loin de nouveaux objets, les structures, qui 
  281.         permettent d'associer des variables et des pointeurs, donc aussi, en 
  282.         utilisant ces pointeurs particuliers, des fonctions. C'est le point 
  283.         de départ en C de la programmation orientée objet et sans que nous 
  284.         soyons obligés d'ajouter une nouvelle couche de soft comme en Basic 
  285.         ou en Pascal.
  286.  
  287.                                                                 ************
  288.                                                  Analyser :       CH08_07.C
  289.                                                                 ************
  290.  
  291.       Le pointeur, *pointeur_sur_fonction, pointe une fonction à un seul 
  292.    paramètre de type double. Le nom du pointeur est entre parenthèses sinon 
  293.    le système l'interpréterait comme le prototype d'une fonction qui retourne 
  294.    un pointeur spécial dont le type est void . 
  295.  
  296.       Le nom d'une fonction peut être considéré lui même comme un pointeur
  297.    dont la référence est l'adresse de la fonction. D'une certaine manière
  298.    on pourrait écrire:
  299.  
  300.          Affiche_1() est une fonction, Affiche_1 son adresse, sa référence.    
  301.          (*pointeur_sur_fonction) est un pointeur sur la fonction.
  302.          &(*pointeur_sur_fonction) équivalent à pointeur_sur_fonction est 
  303.          sa référence, qui devient par l'affectation l'adresse de la fonction 
  304.          Affiche_1().
  305.  
  306.    Un pointeur sur une fonction, comme un pointeur sur une variable, permet
  307.    d'appeler indirectement la fonction:
  308.  
  309.    Dans notre programme, le pointeur est utilisé pour appeler successivement 
  310.    trois fonctions d'affichage. Nous avons commencé chaque fois par écrire le 
  311.    code normal avant d'utiliser ce pointeur.
  312.    De même qu'un pointeur ordinaire permet de "remplacer" une variable, un 
  313.    pointeur sur une fonction permet de "remplacer" la fonction, mais ces 
  314.    pointeurs ne peuvent être incrémentés.
  315.   Chapitre 8: Des pointeurs                                         page 6
  316.                                                       
  317.  
  318.  
  319.    3.2. Constantes et pointeurs.
  320.    -----------------------------
  321.  
  322.       Un élément défini comme constant ne peut plus être modifié. 
  323.    Deux cas sont possibles avec les pointeurs: 
  324.    
  325.    1° int *const pa;  le pointeur pointera toujours la même adresse.
  326.                       mais le contenu de cette adresse pourra être modifié.
  327.  
  328.    2° const int *pa; ou bien
  329.       int const *pa; le pointeur pourra pointer des adresses différentes
  330.                      mais les valeurs pointées ne seront pas modifiables.
  331.  
  332.    3.3. Une fonction peut renvoyer un pointeur.
  333.    --------------------------------------------
  334.  
  335.     double* fonction( int*, double);
  336.  
  337.       Ce prototype  est celui d'une fonction qui reçoit comme arguments un
  338.    pointeur sur un entier et un réel double. Elle renvoie un pointeur sur
  339.    une variable de type double, donc une adresse et il sera possible d'agir
  340.    sur cette variable à partir de ce pointeur.
  341.  
  342.       Vous trouverez plus loin un exemple avec le programme CH13_03.C du 
  343.    développeur français William Marie.
  344.  
  345.    3.4. Pointeur void.
  346.    -------------------
  347.       C'est un pointeur dont le type n'est pas défini. 
  348.    Il possède une référence mais comme à cette adresse on ne connait pas la 
  349.    taille de l'enregistrement à lire , la lecture est impossible. Il faut 
  350.    donc lui imposer un type avant de l'utiliser.
  351.  
  352.    un exemple est donné par la fonction malloc() que nous étudierons vers la
  353.    fin du manuel.
  354.    On crée au préalable un pointeur, par exemple double *ptr; pointeur de 
  355.    type double. La fonction malloc() nous reserve un espace de mémoire pour 
  356.    une variable de type double et nous renvoie un pointeur dont la référence
  357.    est l'adresse de cette zone. Nous affectons sa référence à ptr.
  358.    La fonction est apte à répondre à tous les types de variables car le 
  359.    pointeur qu'elle renvoie est lui même de type void.
  360.    Il suffit d'imposer à ce pointeur une conversion de type pour pouvoir 
  361.    légalement l'affecter au notre.
  362.  
  363.         void* malloc( unsigned int taille);
  364.  
  365.    Cette fonction utilise un argument qui est la taille de la mémoire à 
  366.    réserver et renvoie un pointeur void de type non défini.
  367.    Nous écrirons notre code de la manière suivante:
  368.  
  369.    double *ptr;
  370.    ptr= ( double*)  malloc( taille);  le pointeur de type void qui est 
  371.                                       retourné est converti en double.
  372.  
  373.  
  374.   Fin du chapitre 8: Des pointeurs                                  page 7
  375.  
  376.