Concepts en C

Cohérence, logique et fonctionnement du C


précédentsommairesuivant

VI. Objets tableaux

Un objet tableau est un ensemble d'objets, tous du même type et placés consécutivement en mémoire. Céer un objet tableau revient à créer les objets qui le constituent. L'initialisation des objets dépend du mode de création du tableau (Voir Vie et mort des objets)

Il est possible de créer un objet tableau ne comportant qu'un objet, mais le comportement de cet objet tableau sera différent de celui de l'objet qu'il contient. On ne peut créer de tableau vide, c'est à dire ne comportant aucun objet.

Rappelons que les objets tableaux sont les seuls qui ont un type différent de celui de leur valeur. Voir Valeur d'un objet

VI-A. Type tableau

La notation [] permet d'obtenir les éléments d'un tableau. Pour écrire le type Tt d'un tableau de N objets de type T, en cohérence avec la signification de cet opérateur, on écrira que Tt [N] est un objet de type T :

T   Tt [N]

Les objets de type T peuvent être éventuellement eux-mêmes des tableaux de M objets de type T'. Le tableau est alors à plusieurs dimensions. On écrira alors suivant le même principe que Tt [N] est un tableau d'objets T' :

T'   (Tt [N] ) [M]

ou, comme les parenthèses sont ici inutiles,

T'   Tt [N] [M]

ce qui pourra être interprété au choix comme :

  • T'   Tt [N] [M] , autrement dit "Tt est tableau de N tableaux de M objets T' "

  • T'   Tt [N] [M] soit "Tt [] est tableau de M objets T' "

  • T'    Tt [N] [M] soit "Tt [] [] est un objet T' "

VI-B. Opérateurs définis pour les tableaux

Les seuls opérateurs définis pour un tableau sont l' opérateur unaire & (adresse de) et sizeof. Il n'y a donc aucune possibilité d'effectuer des traitements sur un tableau (dans son ensemble).
Pour manipuler l'information contenue dans un tableau, il faut traiter individuellement les éléments du tableau avec les opérateurs qui leurs sont propres.

  • L'opérateur & donne l'adresse du tableau (qui correspond à l'adresse du début du tableau).

    Le type Ta de cette valeur est "adresse d'un tableau de N objets de type T ". On écrira donc que (*Ta) est un tableau de N objets de type T :
T   (*Ta) [N]

Les parenthèses sont obligatoires ici : En leur absence, la priorité des opérateurs donnerait le groupement
T *  (Ta [N]) ce qui définirait un tableau de pointeurs sur T.

  • L'opérateur sizeof donne le nombre de bytes occupés en mémoire par le tableau. On a donc, puisque les objets sont consécutifs en mémoire, sizeof  (Tt) qui est égal à N*sizeof (T).

Soit tab, déclaré par T tab [N].
- Alors sizeof tab est égal à N*sizeof (T).
- Mais si tab est dans un contexte de valeur, comme dans sizeof (tab+0), le résultat est égal à sizeof (T*).

(Voir Valeur d'un objet tableau)

VI-C. Valeur d'un objet tableau

Si un objet "tableau d'objets de type T" a été créé, la valeur de l'objet tableau est l'adresse du premier objet du tableau. Les opérateurs s'appliquant à la valeur d'un objet tableau sont donc les opérateurs s'appliquant aux adresses.

- Le type Tv de cette valeur étant "adresse d'un objet de type T" , on a :

T * Tv
 

Elle est donc différente du type Tt du tableau : T Tt [N]   et du type Ta de l'adresse du tableau : T   (*Ta) [N]).

La connaissance du contexte dans lequel est placé l'identificateur du tableau est donc primordiale : Dans un contexte d'objet, il est du type T  [N] et dans un contexte de valeur du type T * .

- Si le type Tt du tableau est T'   Tt [N] [M], alors le type Tv de sa valeur sera "adresse d'un tableau de M objets de type T' " :

T'  (*Tv) [M]
  • La valeur de l'objet tableau indique où se trouvent les éléments du tableau, mais pas leur nombre. Le nombre d'éléments du tableau est déterminé à sa création et si on en a besoin ultérieurement (ce qui est fréquent), il faudra prendre la précaution de le sauvegarder.

  • La valeur d'un objet tableau est non modifiable, autrement dit, les opérateurs =, +=, ... ,++ et -- ne sont pas définis pour les tableaux et il est impossible d'écrire tableau = ...

    (Voir Valeur d'un objet)

VI-D. Fonctions et tableaux

Puisque les arguments d'une fonction sont dans un contexte de valeur, on ne peut pas passer en argument d'une fonction un objet mais seulement la valeur d'un objet.

Dans le cas d'un objet tableau, l'argument sera donc la valeur du tableau soit l'adresse de son premier élément. Si il s'agit d'un tableau d'objets de type T, le paramètre correspondant de la fonction sera donc du type T*.
Le langage tolère également de déclarer le paramètre dans la liste des paramètres de la fonction sous la forme tableau : T[]. Le choix de T* montre bien que la variable locale à la fonction est un pointeur (ce qui est vrai dans les deux cas) alors que la notation tableau T[] insiste sur le fait que l'argument passé à la fonction est l'adresse du début d'un tableau et non pas celle d'un objet "simple".

exemple
Sélectionnez
 T tab[N];
 Fonc(tab); // tab est ici du type T*
 ....
 Fonc(T* t) // ou Fonc(T t[]) 
 {
  // t est un pointeur local sur des objets du type T 
 ....
 }


La fonction ne peut connaître le nombre d'éléments du tableau sauf si celui-ci est passé en argument ou si il peut se déduire du contenu du tableau. Ce dernier cas est celui des chaînes de caractères où la valeur '\0' est présente comme sentinelle pour indiquer la fin du tableau.

Dans le cas des tableaux à plusieurs dimensions, le problème se complique : Si le tableau est déclaré comme :

T'   Tab [N] [M]


le type Tv de sa valeur, qui doit être le type du paramètre correspondant dans la fonction, est

T'   (*Tv) [M]


Le problème majeur est la présence de la valeur M dans la définition de ce type : Il est nécessaire que le compilateur connaisse cette valeur sinon, il ne connaît pas la taille des éléments du tableau et ne peut donc calculer leur position. Ceci est une énorme contrainte puisqu'une fonction, écrite pour une valeur donnée de M ne peut être utilisée pour une autre valeur (à moins d'utiliser des tableaux de longueur variable (VLA) du C99 ).

exemple
Sélectionnez
 T tab[N][M];
 Fonc(tab); // tab est ici du type T (*)[M]
 ....
 Fonc(T (*t)[M])  
 {
  // t est un pointeur local sur des objets du type T[M]
 ....
 }

Finalement, les tableaux à plusieurs dimensions sont très contraignants pour les fonctions et on préfèrera souvent les concevoir comme un ensemble de tableaux à une dimension, très souples à utiliser, au prix d'une occupation mémoire augmentée et du code supplémentaire nécessaire à leur création. Pour créer un tableau à deux dimensions, on créera un tableau (à une dimension) de pointeurs, chaque pointeur contenant l'adresse d'un tableau (à une dimension) représentant une "ligne" du tableau à deux dimensions.
Le même principe est applicable pour un nombre de dimensions plus grand.


L'expression suivant un return étant dans un contexte de valeur, il n'est pas possible à une fonction de retourner un tableau mais uniquement la valeur du tableau, donc l'adresse de son premier élément.

On se souviendra qu'il est interdit de retourner l'adresse d'un objet local, tableau ou non, car, étant en allocation automatique, l'objet est détruit en sortie de la fonction et on renvoie dans ce cas l'adresse d'un objet "mort".


précédentsommairesuivant

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Ce document est issu de http://www.developpez.com et reste la propriété exclusive de son auteur. La copie, modification et/ou distribution par quelque moyen que ce soit est soumise à l'obtention préalable de l'autorisation de l'auteur.