next up previous contents index
Next: 7 Pointeurs Up: Le langage C Previous: 5 Classification des données

Subsections


6 Objets structurés

    Nous avons présenté dans la section 1, les types élémentaires de données du langage C. Les langages de programmation qui se limiterait à ces seules types de données seraient excessivement pauvres. Aussi, ils disposent généralement des structures de données structurées à partir des types de bases. Les structures les plus couramment disponibles sont les tableaux et les structures.

6.1 Les tableaux

   Un tableau est une suite d'eléments homogènes (de même type) rangés de manière consécutive en mémoire. Les éléments d'un tableau sont de type quelconque (mais homogènes): types élémentaires, pointeurs (voir chapitre 7), tableaux et structures. Lorque l'élément d'un tableau n'est pas lui-même un tableau, il s'agit d'un tableau unidimensionnel. Dans le cas contraire, il s'agit d'un tableau multidimensionnel.

6.1.1 Les tableaux à une dimension

Un tableau unidimensionnel est composé d'élements d'un certain type donné. La donnée de ce type et du nombre d'éléments du tableau permet de représenter un tableau en mémoire.

La définition suivante  

 
    type tab[5];
alloue en mémoire un zone capable de contenir 5 éléments de type type i.e. 5 * sizeof(type). Les éléments du tableau sont désignés par tab[0], tab[1], tab[2], tab[3] et tab[4].

Dans le langage C, le premier élément d'un tableau est d'indice 0 et non pas 1 !!!

Le nombre d'éléments d'un tableau (5 dans l'exemple 6.1.1) peut être spécifié par une expression à condition qu'à la compilation on puisse connaître sa valeur.

Il y a deux cas où la taille du tableau est omise:

Le nom d'un tableau est équivalent à une constante et peut figurer dans n'importe quelle expression où une constante est permise. Il joue également le même rôle que l'adresse du premier élément du tableau. Les deux expressions

 
t  et  &t[0]
sont identiques sauf dans les deux cas:

Tout ceci donne bien la philosophie de l'implantation des tableaux dans le langage C. Excepté au moment de la déclaration et du calcul de la taille, le langage C ignore complètement l'espace mémoire occupé par un tableau: il en découle qu'il appartient au programmeur de s'assurer qu'il ne déborde pas du tableau; il n'y a aucun test de débordement pouvant servir de garde-fou pour le programmeur. Dans l'exemple 6.1.1 du tableau de 5 éléments, on peut tout à fait accéder au dixième élément (ficif) tab[10], aux risques et périls du programme.

Le débordement du tableau fait partie des erreurs de programmation fréquentes et dramatiques.

Comme nous l'avons déjà dit, le nom d'un tableau doit être considéré comme une constante; ce n'est pas une lvalue. Si tab1 et tab2 sont deux tableaux, l'affectation tab1 = tab2 produira une erreur de compilation car tab1 n'est pas modifiable.

Initialisation.

 

Comme toute variable, une fois définie, un tableau doit être initialisé. La première manière de le faire est d'écrire une fonction d'initialisation :

 
#define NBRE_ELTS         10                           voir chapitre 9
#define MAX_INDICE        NBRE_ELTS - 1

int tab[NBRE_ELTS];

...

for ( i = 0; i <= MAX_INDICE; i++) boucle d'initialisation tab[i] = ... valeur quelconque ...

Le langage C permet également l'initialisation d'un tableau à la défintion de celui-ci. Cette initialisation se fait à la définition et donc les valeurs utilisées doivent être des constantes (sous forme d'expression si nécessaire).
 
#define NBRE_ELTS         10                            voir chapitre 9
#define MAX_INDICE        NBRE_ELTS - 1

int tab1[NBRE_ELTS] = {0,1,2,3,4,5,6,7,8,9}; initialisation des 10 éléments int tab2[NBRE_ELTS] = {0}; initialisation du seul premier élément int tab3[NBRE_ELTS] = {0,1,2,3,4,5,6,7,8,9,10,11}; Warning de compilation tableau à 1 éléments int tab3[] = {0,1,2,3,4,5,6,7,8,9,10,11}; tableau à 12 éléments

6.2 Passage d'arguments de type tableau

   Nous avons dit (qu'un passage par valeur consistait à recopier le paramètre effectif et à travailler sur cette copie. Que se passe-t-il lorsque ce paramètre est un tableau ? Lorsqu'un tableau est passé en paramètre, contrairement à ce que l'on pourrait croire, c'est l'adresse du premier élément du tableau qui est en fait passé en argument.

Dans la déclaration d'un argument formel t de type tableau:

Voici deux versions d'une même fonction:

 
int strlen(char chaine[]) {
    register int i=0;
    while (chaine[i] != 0) 
        i++;
    return i;
}
 
int strlen(char *chaine) {
    register int i=0;
    while (chaine[i] != 0) 
        i++;
    return i;
}
 
int strlen(char *chaine) {
    register int i=0;
    while (*(chaine+i) != 0) 
        i++;
    return i;
}
Il n'est pas possible, de manière simple, de passer par valeur un tableau.

De même, lorque le retour d'une fonction est un tableau, ce qui est transmis, ce n'est pas le tableau tout entier mais uniquement l'adresse du premier élément du tableau. Il faut alors s'assurer que le tableau transmis n'est pas un tableau local qui disparaît à la fin de fonction appelée.

 
tab * fonc(void) {
     int tab[30];
     ...
     return tab;               ce tableau n'aura plus d'existence 
}                             à la fin de cette fonction

Une fonction ne doit jamais retourner un tableau local.

Nous verrons dans la section qui suit une astuce pour contourner ce problème.

6.2.1 Les tableaux à plusieurs dimensions

 

Les tableaux à plusieurs dimensions sont des tableaux dont les éléments sont eux même des tableaux (par exemple des matrices). La déclaration d'un tableau multi-dimentionnel est comme suit:

 
type nom[index1][index2] ... [indexN];
type nom[index1][index2] ... [indexN] = {  ...  } ;
Par exemple, une matrice de dimension 4 * 4 se définit comme suit:
 
int matrice[4][4] 
A une telle matrice est associée une zone mémoire de 4 * 4 * sizeof(int) octets consécutifs; de façon interne, une matrice est représentée de manière linéaire. L'élément d'indice ( i , j ) de la matrice se note matrice[i][j][*].

L'initialisation de cette matrice peut se faire de deux manières:

 
int matrice1[4][4] = {1,2,3,4,5,6,7,9};
1 2 3 4
5 6 7 8
A9      
       

  1 2 3 4 5 6 7 8 9                

 
int matrice2[4][4] = { {1,2,3},  {4,5,6},  {7,8,9} };
1 2 3  
4 5 6  
7 8 9  
       

  1 2 3   4 5 6   7 8 9            

6.2.2 Les chaînes de caractères

    Comme nous l'avons déjà dit (voir chapitre 1), une chaîne de caractères est un tableau de caractère. La fin de la chaîne est matérialisée par le caractère NULL \0.

Voici pour exemple, la fonction extraite de la bibliothèque standard qui calcule la longueur d'une chaîne de caractères:

int strlen(char s[]) {
    int i = 0;
    while (s[i] != '\0') 
        i=i+1;
    return i;
}

Initialisation.

Comme les tableaux, les chaînes de caractères peuvent être initialisées lors de leur définition.
 
char chaine1[100] = {'c', 'o', 'u', 'c', 'o', 'u'};      
                 initialisation des 6 premiers caractères
char chaine2[]    = {'c', 'o', 'u', 'c', 'o', 'u'};      
                 chaîne de longueur 6 (+1 pour coder la fin de chaine)
Et l'on dispose de racourcis agréables:
 
char chaine1[100] = "coucou";                            
                 initialisation des 6 premiers caractères
char chaine2[]    = "coucou";                            
                 chaîne de longueur 6 (+1 pour coder la fin de chaine)

6.3 Les structures

   Les structures sont des éléments composées de champs de types différents et qui obéissent à la syntaxe suivante:

Voici un premier exemple de définition d'une structure:

 
 struct fiche {
     int numero;
     char nom[32];
     char ville[32];
} a, b, c;
Les trois variables a, b et c sont définies de type struct fiche. Ce type est constitué d'un numéro (entier), d'un nom (chaîne de caractère) et d'une ville (chaîne de caractère). Dans la suite du programme, il est désormais possible de définir des variables de type struct fiche tout comme n'importe quel type prédéfini.
 
struct fiche x, y;
Si l'on ne désire pas définir d'autres variables de type struct fiche, on peut omettre de nommer la structure.
 
struct  {
     int numero;
     char nom[32];
     char ville[32];
} a, b, c, x, y;

Accès aux champs d'un struct.

  Pour accéder au champ d'un struct, on utilise l'opérateur de selection ``.'' (voir section 2.2.8). Le champ numero de la variable a est noté a.numero
 
{
...
a.numero = 124;
a.nom = "Touraivane";
a.ville = "Marseille";
...
}

Manipulations globales.

L'affectation d'une variable de type struct qqchose à une autre variable du même type correspond à l'affectation de chacun de champs de la structure.

Le passage par valeur d'une structure consiste à passer chacune de ses champs. Le retour d'une fonction peut être une structure. Nous verrons ces notions plus en détail dans le chapitre 4.

Disposition des champs d'une structure.

  Le lanage C garantit que les champs d'une structure sont alloués dans l'ordre où ils apparaissent dans la déclaration. Pour l'exemple de la structure fiche (page [*]), voici la dispostion en mémoire:

  a.numéro a.nom (32 octets) a.ville (32 octets)  

Initialisation.

    Comme pour les tableaux, une structure peut être initialisée lors de sa déclaration:
 
struct  fiche {
     int numero;
     char nom[32];
     char ville[32];
} a = { 124, "Touraivane",     "Marseille" },
   b = { 125, "Mondain-Monval", "Marseille" };

6.4 Les unions

 

Les champs des stuctures se suivent sans se chavaucher alors que les champs des unions ont la particularité de se chauvaucher. Il s'agit d'un moyen de ``voir'' une donnée sous de multiples aspects.

 
union mot  {
     unsigned long ulong;
     char *ptr;
} a;
La variable peut être vue comme un unsigned long ou comme un pointeur vers un caractère (char *). Ainsi, a.ulong et a.ptr désigne respectivement l'aspect unsigned long et char * de la variable a.

Les champs d'une union ne sont pas forcément de même taille; la taille d'une union est alors la taille du plus volumineux de ces champs.

6.5 Passage d'arguments de type struct ou union

    Contrairement aux tableaux, les structures sont recopiées champ par champ lors d'un passage d'argument par valeur. Une structure est donc passée par valeur. De même, le retour d'une fonction peut être une structure.

Une astuce pour transmettre un tableau par valeur consiste à l'encapsuler dans une structure.

 
struct capsule  int tab[100]; 

void modifier(struct capsule s) { s.tab[1] = 999; }

void main(void) { struct capsule c; c.tab[1]=0; modifier(c); printf("%d", c.tab[1]); La valeur affichée sera 0: c'est bien un passage par valeur }

6.6 Les champs de bits

 

Le langage C permet de définir des structures dont les champs ne sont pas des objets de types élémentaires ou structurés. Il s'agit des champs de bits i.e. une décompostion plus fine de la structure:

 
struct  mot {
     unsigned c1    : 4 ;
     unsigned c2    : 12 ;
                    : 4 ;
     unsigned c3    : 12 ;
}

6.7 Enumérations

      L'énumération n'est pas un type structuré. Il s'agit tout juste de désigner par des noms symboliques des entiers.
 
enum jour {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} x;
Comme pour les struct et les union, il est possible de définir d'autres variables de ce type de la manière suivante :
 
enum jour  y, z;
Encore un fois, comme pour les struct et les union, le nom de l'énumaration n'est utile que si l'on désire définir par la suite d'autre variables de ce même type.
 
enum {lundi, mardi, mercredi, jeudi, vendredi, samedi, dimanche} x, y, z;
Les noms symboliques que l'on se donne dans la définition d'une énumération sont en correspondance parfaite avec des entiers; ici lundi correspond à 0, mardi correspond à 1, etc ...

On peut toutefois altérer cette correspondance de manière explicite :

 
enum {lundi=3, mardi, mercredi, jeudi=8, vendredi, samedi, dimanche} x, y, z;
et lundi correspond à 3, mardi correspond à 4, mercredi correspond à 5, jeudi correspond à 8, vendredi correspond à 9, etc ...


next up previous contents index
Next: 7 Pointeurs Up: Le langage C Previous: 5 Classification des données

Touraivane
9/21/1998