Pointeurs et tableaux



next up previous contents index
Next: Structures Up: Quelques éléments de Previous: Procéduresfonctions, structure

Pointeurs et tableaux

        C'est un des points les plus troublants en C pour le débutant. Les pointeurs ressemblent beaucoup à Pascal, seule la syntaxe diffère. Les opérateurs de base sont & pour référencer un objet et * pour déréférencer, écrits en préfixe. Ainsi

int   x = 1, y = 2, z[10];
int   *ip;       /* - */

ip = &x;         /* - */
y = *ip;         /* - */
*ip = 0;         /* - */
ip = &z[0];      /* - */

De même, la fonction d'échange s'écrira

int   x, y;

void Echange (int *xp, int *yp)
{
    int z = *xp;
    *xp = *yp;
    *yp = z;
}
...
    Echange (&x, &y);

        Nous verrons plus tard (cf. section gif) les fonctions malloc et free de la librairie C qui permettent à un pointeur de manipuler des objets du tas de C (comme new et dispose en Pascal). La constante NULL, déinie dans le fichier <stdio.h> est la valeur standard pour un pointeur vide (le nil de Pascal). Mais en C, en plus des opérations usuelles d'égalité sur les pointeurs, on peut faire des opérations arithmétiques (additions et soustractions). Nous éviterons d'en faire trop dans ce cours. En général, il s'agit de pointer sur l'élément d'un tableau et de passer sur ses éléments voisins. En C, tout se dérive de l'égalité suivante, que l'on peut considérer comme l'équation de C (pour le meilleur et pour le pire!)

     

Les corollaires de cette équation sont nombreux. D'abord si , cela veut dire qu'une expression contenant le nom d'un tableau a est un raccourci pour signifier la valeur d'un pointeur pointant sur son premier élément (toujours à l'indice 0 en C). Ainsi on comprend mieux les signatures des fonctions Erreur de l'exemple du carré magique (page gif), de atoi (page gif), de strlen (page gif), Imprimer (page gif). Ensuite, comme , on a . Nous utiliserons peu cette écriture inélégante, mais le type de ces deux notations permet d'écrire autrement la signature des fonctions précédentes

void Erreur (char *s)   {...}

Ecrire sous une forme ou l'autre est une affaire de goût, mais dans le cas présent la première est plus naturelle, puisque c'est vraiment une chaîne de caractères que l'on veut donner comme argument. Enfin, si , l'instruction

p = p + j;

implique que . Donc, si on incrémente un pointeur, on passe à l'élément suivant du tableau, quelque soit le type de cet élément.

  Il y a un type de tableau qui est très utilisé: les chaînes de caractères. Une chaîne de caractères est un simple tableau de caractères. La fin d'une chaîne de caractères est le caractère '\0'. Ainsi

char   ch[256];

    ch[0] = 'P'; ch[1] = 'a'; ch[2] = 'u'; ch[3] = 'l'; ch[4] = '\0';

décrit un tableau ch contenant la chaîne de caractères "Paul" de longueur 4. On aurait pu obtenir le même résultat avec la fonction strcpy de la bibliothèque C, dont le prototype est dans <string.h>, en faisant

strcpy (ch, "Paul");

Le fichier include <string.h> contient des opérations de comparaisons, de recherche de sous-chaîne.

Enfin, il faut bien comprendre qu'il existe des différences entre tableaux et pointeurs. Au point de vue mémoire, un pointeur est un simple ``mot-mémoire'' contenant une référence vers un élément du langage. Un tableau est un ensemble de cases mémoire pour chacun de ses éléments. Ainsi un tableau a de pointeurs vers des caractères est différent, d'un tableau b de caractères à deux dimensions

char    *a[10];
char     b[10][256];

Le premier occupe 10 cases mémoires, le deuxième 2560 octets, même si a et b peuvent être passés comme argument d'une même procédure

void F(char **x) {...}

  En Unix, la fonction main a comme argument le nombre d'argument argc de la ligne de commande appelant le programme, et un tableau de pointeurs vers les chaînes arguments argv. (argv[0] est le nom de la commande elle-même). Ainsi la commande echo du système Unix s'écrit

int main (int argc, char *argv[])
{
    int  i;

    while (--argc > 0)
        printf ("%s%s", argv[i], (i < argc - 1) ? " " : "");
    printf ("\n");
    return 0;
}

Il n'y a pas de variables fonctionnelles en C (comme en Scheme ou ML). Mais, il existe des pointeurs sur des fonctions. Ainsi on peut faire dépendre une fonction d'une autre fonction, en lui passant en argument un pointeur vers une fonction. On peut donc reprendre le programme de recherche de zéro d'une fonction quelconque (cf. page gif).

#include  <math.h>
#define Pi         3.14
#define Epsilon    1.0e-7
#define Nmax       100

double Zero (double (*f)(double), double a,b)
{
    int     n;
    double   m;

     n = 1;
     while (fabs (b - a) < Epsilon && n < Nmax) {
         m = (a + b) / 2;
         if ((*f) (m) > 0 == (*f) (a) > 0)
             a = m;
         else
             b = m;
         ++n;
     }
     return a;
}
...
    Zero (sin, Pi/2, 3*Pi/2);
    Zero (cos, 0, Pi);

Par convention, une fonction représente un pointeur sur elle-même, un peu comme pour les tableaux, et le déréférencement implicite se fera (à un niveau). On peut donc simplement écrire f(m) pour (*f)(m). La signature de Zero peut aussi être simplifiée. L'écriture ressemble alors à celle de Pascal.



next up previous contents index
Next: Structures Up: Quelques éléments de Previous: Procéduresfonctions, structure