Les pointeurs (suite) et les structures |
Les pointeurs et les structures sont des
données complexes qui si associées peuvent généer du code efficace
notamment avec les listes chainées. Voyons plus en détail ces notions.
|
Sommaire |
Les pointeurs génériques |
Nous avons vu qu'en C un pointeur correspondait
à une adresse en mémoire et à un type.
Il est possible de faire en sorte que ce typage soit générique, c'est à dire qu'un pointeur puisse recevoir l'adresse d'une variable de n'importe quel type. Le pointeur " void * " désigne un pointeur sur une donnée de type quelconque. Malheureusement celui-ci est assez limité : Les opérations arithmétiques lui sont interdit(si var est de type " void * " alors il est impossible de faire " var+1 " ou d'aditionner var à une autre variable/pointeur. De même il n'est pas possible d'utiliser l'opérateur * sur un pointeur de type " void * ". Il est à noter que l'on pourrait se contenter d'un pointeur de type " char * " pour accéder aux octets de la mémoire. Prenons pour clore ce point un exemple portant sur la bonne et la mauvaise utilisation d'un pointeur générique : |
//un tableau de doubles double tableau[5] ; //et notre pointeur générique void *generique ; //instruction posible generique=tableau ; //opération interdite : *generique ne peut //remplacer tableau[0] car il n'a pas de type *generique = 145,456 ; //au contraire cette instruction marche : *(double *) generique = 145,456 ; |
un tableau est un pointeur constant |
En C le nom d'un tableau utilisé comme
instruction est considéré comme un pointeur constant pointant sur la premiere case
de ce tableau
Si nous déclarions : " int tableau[10] " écrire dans le source " tableau " reviendrai pour le compilateur à ecrire : " &tableau[0] " . Par exemple si nous voulions mettre dans chaques cases du tableau la valeur 1 nous pourrions faire ainsi : |
int increment ; for (increment = 0 ; increment < 10 ; increment++) *(tableau + increment) = 1 ; |
On fait ici avancer le pointeur d'une case
adresse et on lui donne la valeur 1 jusqu'à ce que les 10 cases soit faites.
De même nous pourrions utiliser l'aide d'un pointeur : |
int increment ; int *p; for (p = tableau ; increment = 0 ; increment < 10 ; increment++ ;, p++) *p = 1 ; |
Ici p pointe sur la première case du
tableau. On donne la valeur 1 à la case dont l'adresse est contenue dans p et on fait
pointer p sur la case suivante du tableau 10 fois de suite.
Comme dans un source 'tableau' revient à ecrire " &tableau[0] " alors tableau + n " représente l'adresse du nième élement du tableau. Dans l'exemple si dessus nous faisons " p = tableau " soit donc " p = &tableau[0] " mais nous aurion pu tout aussi bien faire " p = &tableau[4] " Donc p[0] serait équivalent à l'adresse de tableau[4]p[1] a &tableau[5] et p[-1] correspondrait lui à l'adresse de tableau[3]. Pour conclure il faut savoir que inversement nous pourrions utiliser la notation " tableau[5] " si tableau avait été un pointeur déclaré ainsi : "int *tableau" Bien entendu il aurai fallu allouer l'espace mémoire neccéssaire. |
![]() pointeurs et tableaux |
les pointeurs de fonctions |
Si une variable ne peut contenir le nom d'une
fonction, un pointeur pourra en contenir l'adresse. De plus tout comme les tableau, utiliser le
nom seul de la fonction revient a manipuler l'adresse de cette fonction.
Voyons plus en détails : |
int (* pfonction)(double , int) ; |
ici pfonction est un pointeur sur une fonction
retournant un int et prenant comme arguments une donnée de type double et une de type
int.
Nous pourrions donc faire si nous avions une fonction de ce type : " int fonction(double var, int var2) ; " un pointage de la sorte : |
pfonction = fonction ; |
pfoncton contient alors l'adresse de notre
fonction.
Nous pouvons alors appeller n'importe quelle fonction pointée par pfonction de cette manière : |
(* pfonction)(3.14, 10) ; |
" * pfonction" correspond bien sur à la
fonction pointée à ce moment là par pfonction. Celle-ci prendra pour
argument 3.14 et10.
Enfi il est tout a fait possible de transmettre une fonction en argument de la sorte : |
void fonc(int (*pfonction(double, int), int variable) ; |
les structures |
Si les tableaux nous permettent sous un seul nom
plusieurs valeurs de même type, une structure permet elle de désigner plusieurs
valeurs de type différents.
De même l'accès à une valeur à l'interieur de cette structure se fera par un nom identifiant cette valeur et no par un indice. Prenons pour exemple un programme répertoriant les clients d'une entreprise. Nous devrions pour enregistrer chacun des acheteurs déclarer ce genre de variables : |
char nom[20] ; char prenom[20] ; int codepostal ; char ville[10] ; |
sachant que nous aurions plus d'un client il nous
faudrait alors recourir au tableaux et aux tableaux de tableaux, ce qui transformerait notre
source en un listing indéchiffrable.
La solution serait alors de regrouper toutes ces données sous un seul nom. C'est là la définition d'une structure. |
Déclarer une structure |
Une structure suis toujours ce type de
déclaration :
|
struct <nom_structure> { <type de la donnée 1> <nom de la donnée 1> <type de la donnée 2> <nom de la donnée 2> ... <type de la donnée n> <nom de la donnée n> } ; |
Pour continuer notre exemple nous declarions
notre structure comme l'image ci-contre nous le montre.
Il est important de ne jamais donner le même nom à deux données d'une structure. De même on ne peut mettre une donnée du type de la structure à laquelle elle appartient, en d'autres termes notre structure client ne pourra avoir de données de type client. Par contre client pourrait très bien avoir des données du type d'une autre structure. Pour définir une variable de type client il nous faudra faire : |
struct client client1 ; |
Les variables structurées |
Pour accéder à une donnée
à l'intérieur d'une structure il suffit de faire :
|
<nom de la variable structurée>.<nom de la donnée> |
Prenons un petit exemple
|
//declarer client client1, client2 ; //définir client1.nom = "Norbert" ; client2.nom = "Tartuffe" ; client1.codepostal = 93140 ; client1.prenom= "Augustin" ; client1.ville= "Tarascon" ; //afficher l'enregistrement prtinf("nom : %s, prenom : %s, CP :%d, ville : %s\n", client1.nom, client1.prenom, client1.codepostal, client1.ville) ; |
Bien sur nous aurions tout aussi bien pu faire
une entrée cliavier pour enregistrer toutes ces données.
|
![]() notre structure client |
Nous finirons cette approche du langage C avec
les listes chainées, notion que tout bon programmeur C se doit de connaître.
|
par Valentin BILLOTTE vbillotte@programmationworld.com http://www.programmationworld.com Dernière mise à jour: |