
FAQ CConsultez toutes les FAQ
Nombre d'auteurs : 28, nombre de questions : 175, création le 11 janvier 2013
Sommaire→Pointeurs, tableaux et chaînes de caractères→Les tableaux- Que signifie 'int t[] = {10, 20};' ?
- Que signifie 'int t[10] = {10, 20};' ?
- Soit t un tableau. Quelle est la différence entre t, &t et &(t[0]) ?
- Soit t un tableau. Que signifie *(t + 3) ?
- Quel est le rôle de l'opérateur [] ?
- Comment déclarer et utiliser un tableau 'à plusieurs dimensions' ?
- Que signifie 'int (*p)[4];' ?
- Comment déclarer et utiliser un tableau de pointeurs de fonctions ?
- Comment passer un tableau en paramètre à une fonction ?
- Comment copier un tableau ?
'int t[] = {10, 20};' crée un tableau de 2 éléments initialisé avec les valeurs t[0] = 10 et t[1] = 20. Cette écriture est donc strictement équivalente à : 'int t[2] = {10, 20};'.
'int t[10] = {10, 20};' crée un tableau de 10 éléments initialisé avec les valeurs t[0] = 10, t[1] = 20 et t[2] à t[9] = 0. Notez bien que la mise à 0 des éléments non initialisés d'une variable locale tableau n'a lieu que lorsqu'un élément au moins a été initialisé.
t représente l'objet tableau, &t l'adresse de ce tableau et &(t[0]) l'adresse du premier élément du tableau. Ces trois expressions sont donc très différentes
et n'ont à priori aucun point commun. Toutefois, &t et &(t[0]) représentent la même adresse, même si elles n'ont pas le même type (pour rappel, les adresses sont typées
en C). t tout court quant à lui, lorsqu'il n'est pas utilisé dans sizeof ou avec l'opérateur & ("adresse de"), est toujours converti par le compilateur en &(t[0]). D'autres conversions de ce genre
existent également (avec plus ou moins de conditions pour avoir lieu) et elles sont nombreuses. Ce sont ce qu'on appelle les conversions implicites.
La chose la plus importante à retenir est donc qu'une expression de type tableau (comme notre t ci-dessus, qui est de type int [10]), sauf lorsqu'elle est utilisée en unique argument de sizeof ou de l'opérateur & ("adresse de"),
est toujours convertie par le compilateur en un pointeur vers son premier élément.
Sinon il est reste un tableau.
Pour comprendre le sens de cette expression, il faut au moins savoir deux choses :
- Les adresses sont typées en C. Ajouter 1 à un pointeur revient à se déplacer en mémoire de la taille de la donnée pointée et non de 1 byte.
- Le nom d'un tableau (t), sauf lorsqu'il apparaît dans un sizeof ou à droite de l'opérateur & ("adresse de"), représente toujours un pointeur vers son premier élément (c'est-à-dire &(t[0])).
Le petit programme suivant devrait rendre les choses plus claires.
#include <stdio.h>
int main(void)
{
int t[] = {10, 20, 30, 40};
int * p = t;
/* p pointe sur t[0] donc *p equivaut a t[0]. */
printf("t[0] = %d\n", *p);
/* p pointe sur t[0] donc p + 1 pointe sur t[1]. *(p + 1) equivaut donc a t[1]. */
printf("t[1] = %d\n", *(p + 1));
/* p pointe sur t[0] donc p + 2 pointe sur t[2]. *(p + 2) equivaut donc a t[2]. */
printf("t[2] = %d\n", *(p + 2));
/* p pointe sur t[0] donc p + 3 pointe sur t[3]. *(p + 3) equivaut donc a t[3]. */
printf("t[3] = %d\n", *(p + 3));
return 0;
}
Ou, sans l'aide de la variable p :
#include <stdio.h>
int main(void)
{
int t[] = {10, 20, 30, 40};
printf("t[0] = %d\n", *t);
printf("t[1] = %d\n", *(t + 1));
printf("t[2] = %d\n", *(t + 2));
printf("t[3] = %d\n", *(t + 3));
return 0;
}
Cet opérateur permet d'accéder à un élément d'un tableau. Il requiert deux opérandes : une de type pointeur et l'autre de type entier. L'expression X[Y] est équivalente à *((X) + (Y)). Autrement dit, si p possède un type pointeur, alors les expressions p[1], 1[p] et *(p + 1) sont strictement équivalentes, tout comme p[-1], (-1)[p] et *(p - 1), etc.
En langage C, on ne classe pas les tableaux selon leurs dimensions, un tableau "à plusieurs dimensions" n'est d'ailleurs en C qu'un tableau dont les éléments sont eux aussi des tableaux. Il n'y a donc pas des règles spéciales pour ces tableaux "à plusieurs dimensions". Voici un exemple de programme manipulant un tableau dont les éléments sont des tableaux.
#include <stdio.h>
int main(void)
{
int t[10][4]; /* t est un tableau de 10 elements ou */
/* chaque element est un tableau de 4 entiers */
size_t j, i;
/* Parcourons les elements du tableau (t[0] a t[9]) */
for(j = 0; j < sizeof(t) / sizeof(t[0]); j++)
{
/* Pour chaque element t[j] du tableau, initialisons */
/* les elements de t[j] (t[j][0] a t[j][3]) */
for(i = 0; i < sizeof(t[j]) / sizeof(t[j][0]); i++)
{
t[j][i] = j;
/* Puis affichons t[j][i] */
printf("%d\t", t[j][i]);
}
printf("\n");
}
return 0;
}
'int (*p)[4];' crée une variable p tel que (*p) est un tableau de 4 entiers. p est donc un pointeur vers un tableau de 4 entiers. Il est de type int (*)[4]. On ne peut pas utiliser un tel nom de type pour déclarer une variable par exemple mais on peut par contre l'utiliser dans un cast ou un sizeof. Le programme suivant donne un exemple d'utilisation d'un pointeur vers tableau.
#include <stdio.h>
int main(void)
{
int t[10][4];
int (*p)[4] = t; /* p pointe sur t[0]. *p <=> t[0]. */;
( *p )[0] = 0; /* t[0][0] = 0 */
( *p )[1] = 0; /* t[0][1] = 1 */
( *(p + 1) )[0] = 1; /* t[1][0] = 1 */
( *(p + 1) )[1] = 1; /* t[1][1] = 1 */
printf("t[0][0] = %d\n", t[0][0]);
printf("t[0][1] = %d\n", t[0][1]);
printf("t[1][0] = %d\n", t[1][0]);
printf("t[1][1] = %d\n", t[1][1]);
return 0;
}
#include <stdio.h>
/* Definition d'un type fonction */
typedef int F(void);
/* Voici deux fonctions de type F */
int f1(void);
int f2(void);
/* Maintenant la fonction principale */
int main(void)
{
F * t[2];
t[0] = f1;
t[1] = f2;
printf("t[0]() = %d\n", t[0]());
printf("t[1]() = %d\n", t[1]());
return 0;
}
int f1(void)
{
return 1;
}
int f2(void)
{
return 2;
}
Etant donné que les fonctions, à l'instar des variables permanentes, existent en mémoire pendant toute l'exécution du programme, l'adresse d'une fonction peut être utilisée pour initialiser une variable permanente (en termes techniques, l'adresse d'une fonction, tout comme l'adresse d'une variable permanente, est donc, en C, une "expression constante").
#include <stdio.h>
typedef int F(void);
int f1(void);
int f2(void);
F * t[] = {f1, f2};
int main(void)
{
printf("t[0]() = %d\n", t[0]());
printf("t[1]() = %d\n", t[1]());
return 0;
}
int f1(void)
{
return 1;
}
int f2(void)
{
return 2;
}
Lorsqu'un tableau figure en paramètre d'une fonction, il est automatiquement converti en son adresse (plus précisément en l'adresse de son premier élément). Un tableau n'est donc jamais copié par une fonction. La méthode généralement utilisée pour passer un tableau à une fonction c'est de passer un pointeur vers son premier élément et sa taille (si nécessaire) comme paramètres.
#include <stdio.h>
void initialiser_tab(int * ptr, size_t n_elements)
{
size_t i;
for(i = 0; i < n_elements; i++)
ptr[i] = (int)i;
}
int main(void)
{
int t[10];
size_t i, n_elements = sizeof(t) / sizeof(t[0]);
/* t peut egalement s'ecrire &(t[0]) */
initialiser_tab(t, n_elements);
for(i = 0; i < n_elements; i++)
printf("%d\n", t[i]);
return 0;
}
Etant donné qu'un tableau est toujours converti en un pointeur vers son premier élément, les prototypes suivants sont strictement les mêmes :
void initialiser_tab(int * ptr, size_t n_elements)
void initialiser_tab(int ptr[], size_t n_elements)
void initialiser_tab(int ptr[10], size_t n_elements) /* Le 10 ne sert absolument a rien */
Voici un exemple avec un tableau "à deux dimensions" :
#include <stdio.h>
/* initialiser_tab_2 : initialise un tableau de N elements ou */
/* chaque element est un tableau de M entiers . */
void initialiser_tab_2(int * ptr, size_t N, size_t M)
{
/* ptr = &(t[0][0]) d'ou : */
/* - ptr[i] <=> t[0][i] */
/* - ptr[M * j] <=> t[j][0] */
/* - ptr[M * j + i] <=> t[j][i] */
size_t j, i;
for(j = 0; j < N; j++)
{
for(i = 0; i < M; i++)
{
ptr[M * j + i] = (int)j;
}
}
}
int main(void)
{
int t[10][4];
size_t j, i;
size_t N = sizeof(t) / sizeof(t[0]), M = sizeof(t[0]) / sizeof(t[0][0]);
/* &(t[0][0]) peut egalement s'ecrire t[0] ou encore (int *)t */
initialiser_tab_2(&(t[0][0]), N, M);
for(j = 0; j < N; j++)
{
for(i = 0; i < M; i++)
printf("%d\t", t[j][i]);
printf("\n");
}
return 0;
}
Pour copier un tableau, on a deux solutions. La première consiste à copier chaque case du tableau numéro 1 dans la case
correspondante du tableau numéro 2. Mais cette manière est parfois complexe et risque d'être lente.
La deuxième possibilité est d'utiliser la fonction memcpy(). Elle admet trois paramètres, le premier étant le tableau de destination et
le deuxième le tableau source. Enfin, le troisième est le nombre d'octets à copier. Par exemple, pour copier le tableau Tab1 dans le
tableau Tab2, il suffit de faire :
#include <string.h>
int Tab1[10], Tab2[10];
memcpy(Tab2, Tab1, sizeof Tab2);



