next up previous
Next: Les fonctions Up: Initiation au langage C Previous: Les structures de contrôle

Sous-sections

   
Tableaux, pointeurs et structures

Nous avons déjà vu les types de base disponibles en C. Nous allons maintenant aborder les types plus complexes que sont les tableaux, les pointeurs, les structures et les unions.

les tableaux

  Il est possible d'utiliser des tableaux de valeurs. Pour déclarer un tableau il faut donner le type de ses éléments puis son nom et enfin sa taille entre crochets. Tous les éléments d'un tableau sont obligatoirement du même type.

 Pour un tableau de taille N, l'indice du premier élément est 0 et celui du dernier est (N -1). On peut utiliser des tableaux de dimension 2 ou plus.

définition de tableaux

    Dans l'exemple suivant, nous définissons deux tableaux de 100 éléments, l'un contenant des float, l'autre des char. Le dernier tableau définit une matrice $3 \times 3$ de double.

float VecteurA[100];
int VecteurB[100];
double MatriceTroisTrois[3][3];

initialisation d'un tableau

    On peut initialiser un tableau dès sa déclaration en lui affectant une liste de valeurs séparées par des virgules et entourée par des accolades. L'exemple suivant initialise le tableau Platon et une matrice $3 \times 3$ identité :

int Platon[5] = {4, 6, 8, 12, 20};
double Matrice[3][3] = {{ 1, 0, 0 },
                        { 0, 1, 0 },
                        { 0, 0, 1 }};

Un cas particulier est l'initialisation d'un tableau de caractères pour laquelle on peut utiliser une chaîne de caractères. Les deux lignes suivantes sont équivalentes :

char Str[20] = {'B', 'o', 'n', 'j', 'o', 'u', 'r'};
char Str[20] = "Bonjour";

accès aux valeurs d'un tableau

  Pour accéder à un élément d'un tableau, on utilise l'opérateur [] . La valeur mise entre crochets peut être un calcul. Dans l'exemple suivant, on stocke dans le troisième élément de Tab la valeur du ième élément :

Tab[2] = Tab[i - 1];

les pointeurs

  Un pointeur contient l'adresse en mémoire d'un objet d'un type donné. Ainsi, on parler de « pointeur sur int » ou de « pointeur sur double ». L'utilisation des pointeurs en C est l'un des points les plus complexes du langage. Mais c'est aussi une fonctionnalité qui rend le C très puissant surtout si on l'utilise avec les fonctions d'allocation dynamique de la mémoire que nous verrons plus tard.

définition d'un pointeur

  Pour définir un pointeur, on doit écrire le type d'objet sur lequel il pointera suivi du caractère * pour préciser que c'est un pointeur puis enfin son nom.

Dans l'exemple suivant, p est défini comme un pointeur sur un double et q est défini comme un pointeur sur un pointeur sur int :

double * p;
int * * q;

Attention : dans la définition d'un pointeur, le caractère * est rattaché au nom qui le suit et non pas au type. Ainsi, dans la définition qui suit, p est bien un pointeur sur char mais t est simplement une variable de type char. La seconde ligne, par contre, définit deux pointeurs sur double :

char * p, t;
double * p2, * p3;

adresse d'un objet

  Pour récupérer l'adresse en mémoire d'un objet, on utilise l'opérateur & . Cette adresse pourra être stockée dans un pointeur. Dans l'exemple suivant, le pointeur p contient l'adresse en mémoire de la variable car :

char car;
char * p;
    
p = & car;

accès au contenu d'un pointeur

  Pour accéder au contenu de l'adresse mémoire pointée par un pointeur, on utilise l'opérateur * . Ainsi, en continuant l'exemple précédent, la ligne suivante stockera dans la variable car le caractère A puisque p pointe sur son adresse en mémoire :

*p = 'A';

adresses et tableaux

On peut récupérer l'adresse de n'importe quel objet. Par exemple, il est possible d'obtenir l'adresse d'un élément d'un tableau (dans cet exemple, le onzième élément6.1) :

double a[20];
double * p;
p = & (a[10]);

Par convention, le nom d'un tableau est une constante égale à l'adresse du premier élément du tableau. En continuant l'exemple précédent, les deux lignes suivantes sont équivalentes :

p = & a[0];
p = a;

calculs sur les pointeurs

  Il est possible de faire des calculs sur les pointeurs. On peut ajouter ou soustraire une valeur entière à un pointeur. Dans l'exemple suivant, p pointe à la fin sur le troisième élément du tableau a (donc sur a[2]) :

double a[20];
double * p;
p = & (a[10]);
p = p - 8;

       

Pour effectuer ce calcul tous les opérateurs classiques d'addition et de soustraction sont utilisables en particulier les opérateurs d'incrémentation. Nous avons vu qu'une chaîne de caractères se terminait toujours par le caractère de code ASCII 0 (\0). L'exemple suivant permet de compter le nombre de caractères stockés dans le tableau de caractères str (le caractère nul ne fait pas partie du compte) :

char * p = str;
int NbCar = 0;
while ( *p != '\0') {
    p++;
    NbCar++;
}

   

En fait, les calculs sur pointeurs et l'utilisation de l'opérateur [] d'accès à un élément d'un tableau peuvent être considérés comme équivalent. Sachant que Tab est un tableau de double, les deux lignes suivantes sont équivalentes :

Tab[45] = 123.456;
*(Tab + 45) = 123.456;

Ceci est tellement vrai qu'on peut même utiliser un pointeur directement comme un tableau. Les deux écritures suivantes sont donc exactement équivalentes que p soit le nom d'un pointeur ou celui d'un tableau :

p[i] $\equiv$ *(p + i)

On a le même type d'équivalence au niveau des paramètres d'une fonction. Les deux lignes suivantes déclarent toutes les deux que le paramètre p de la fonction f est un pointeur sur double :

void f(double * p);
void f(double q[]);

les structures et les unions

    En général, les types de base que propose le C ne suffisent pas pour stocker les données à utiliser dans un programme. Par exemple, il serait bien embêtant de devoir utiliser deux variables de type double pour stocker un nombre complexe. Heureusement le C permet de déclarer de nouveaux types. Nous ne ferons qu'évoquer les unions pour nous focaliser sur les structures qui permettent de répondre à la plupart des besoins.

déclaration d'une structure

      Une structure possède un nom et est composée de plusieurs champs. Chaque champ à son propre type et son propre nom. Pour déclarer un structure on utilise le mot-clé struct :

struct nomStructure {
    type1 champ1;
    ...
    typeN champN;
} ;

Voici un exemple qui déclare une structure permettant de stocker un nombre complexe :

struct complex {
    double reel; /* partie reelle */
    double imag; /* partie imaginaire */
};

accès aux champs

    À partir de cette déclaration, il est possible d'utiliser ce nouveau type. L'opérateur . permet d'accéder à l'un des champs d'une structure. En continuant l'exemple précédent, les lignes suivantes initialisent un complexe à la valeur (2 + 3i).

struct complex a;
    
a.reel = 2;
a.imag = 3;

utilisation de typedef

    Le mot-clé typedef permet d'associer un nom à un type donné. On l'utilise suivi de la déclaration d'un type (en général une structure ou une union) puis du nom qui remplacera ce type. Ceci permet, par exemple, de s'affranchir de l'emploi de struct à chaque utilisation d'un complexe. Il n'est pas alors nécessaire de donner un nom à la structure. L'exemple précédent peut donc se réécrire de la manière suivante :

typedef struct {
    double reel; /* partie reelle */
    double imag; /* partie imaginaire */
} complexe;
    
complexe a;
    
a.reel = 2;
a.imag = 3;

initialisation et affectation

      Il est possible d'affecter une variable de type structure dans une autre variable du même type. Le contenu de chacun des champs de la première variable sera alors recopié dans le champ correspondant de la seconde variable. On peut initialiser une variable de type structure dès sa définition en lui affectant une liste de valeurs séparées par des virgules et entourées par des accolades.

complexe a = { 1, 0 }; /* le reel 1 */
complexe b;
    
b = a;

Il est par contre impossible de comparer ou d'effectuer des calculs entre deux structures.

exemple de structures imbriquées

On peut imbriquer plusieurs structures. Dans l'exemple suivant nous déclarons une structure pour stocker une commande d'un client contenant :

Cette structure se déclare de la manière suivante :

typedef struct {
    int refProd; /* reference produit */
    struct {
        double HT; /* prix hors taxe */
        double TVA; /* taux de TVA en pourcentage */
    } prix;
    int q; /* quantite commandee */
    double remise; /* remise en pourcentage */
} commande;

Pour accéder aux champs de la sous-structure, il faut utiliser deux fois l'opérateur . d'accès aux champs. En supposant que com contienne une telle commande, voici le calcul du prix total :

double P_TTC, P_AvantRemise, P_Total;
P_TTC = com.prix.HT * (1 + com.prix.TVA / 100);
P_AvantRemise = P_TTC * com.q;
P_Total = P_AvantRemise - P_AvantRemise * com.remise / 100;

les unions

  Les unions se déclarent de la même manière que les structures. Elles possèdent donc elles aussi des champs typés. Mais on ne peut utiliser qu'un seul champ à la fois. En fait tous les champs d'une union se partagent le même espace mémoire. Les unions sont rarement nécessaires sauf lors de la programmation système.

les pointeurs sur structures

  L'utilisation de pointeurs sur structures est très courante en C. Voici un exemple d'utilisation d'un pointeur sur un complexe :

complexe a = { 3.5, -5.12 };
complexe * p = &a;
(*p).reel = 1;
(*p).imag = -1;
/* a vaut (1 - i) */

  Nous avons été obligé de mettre des parenthèses autour de *p car l'opérateur . est plus prioritaire que l'opérateur * . Cela rend difficile la lecture d'un tel programme. Heureusement, l'utilisation de pointeurs sur structures est si courante que le C définit l'opérateur -> pour accéder aux champs d'une structure via un pointeur. Les deux expressions suivantes sont donc équivalentes :

(*pointeur).champ
pointeur->champ

Ainsi l'exemple précédent s'écrit beaucoup plus facilement de la manière suivante :

complexe a = { 3.5, -5.12 };
complexe * p = &a;
p->reel = 1;
p->imag = -1;
/* a vaut (1 - i) */


next up previous
Next: Les fonctions Up: Initiation au langage C Previous: Les structures de contrôle

Copyright © EMAC - 1997 - Paul GABORIT