C Language
Tableaux
Recherche…
Introduction
Les tableaux sont des types de données dérivés, représentant une collection ordonnée de valeurs ("éléments") d'un autre type. La plupart des tableaux de C ont un nombre fixe d'éléments d'un type quelconque, et leur représentation stocke les éléments de manière contiguë dans la mémoire sans espaces ni remplissage. C permet des tableaux multidimensionnels dont les éléments sont d'autres tableaux, ainsi que des tableaux de pointeurs.
C prend en charge les tableaux alloués dynamiquement dont la taille est déterminée au moment de l'exécution. C99 et versions ultérieures prennent en charge les tableaux ou les VLA de longueur variable.
Syntaxe
- tapez nom [longueur]; / * Définit le tableau de 'type' avec le nom 'name' et la longueur 'length'. * /
- int arr [10] = {0}; / * Définit un tableau et initialise TOUS les éléments à 0. * /
- int arr [10] = {42}; / * Définit un tableau et initialise les 1er éléments à 42 et le reste à 0. * /
- int arr [] = {4, 2, 3, 1}; / * Définit et initialise un tableau de longueur 4. * /
- arr [n] = valeur; / * Définir la valeur à l'index n. * /
- valeur = arr [n]; / * Récupère la valeur à l'index n. * /
Remarques
Pourquoi avons-nous besoin de tableaux?
Les tableaux permettent d'organiser les objets en un agrégat ayant sa propre signification. Par exemple, les chaînes C sont des tableaux de caractères ( char
s) et une chaîne telle que "Hello, World!" a la signification d'un agrégat qui n'est pas inhérent aux caractères individuellement. De même, les tableaux sont couramment utilisés pour représenter des vecteurs et des matrices mathématiques, ainsi que des listes de nombreux types. De plus, sans moyen de regrouper les éléments, il faudrait s’adresser individuellement, par exemple via des variables distinctes. Non seulement cette opération est compliquée, mais elle ne permet pas d’acquérir facilement des collections de différentes longueurs.
Les tableaux sont implicitement convertis en pointeurs dans la plupart des contextes .
Sauf en tant qu'opérande de l'opérateur sizeof
opérateur _Alignof
(C2011) ou opérateur unaire &
(adresse-de), ou littéral de chaîne utilisé pour initialiser un (autre) tableau, un tableau est implicitement converti en ( "decays to") un pointeur sur son premier élément. Cette conversion implicite est étroitement liée à la définition de l'opérateur d'indexation du tableau ( []
): l'expression arr[idx]
est définie comme étant équivalente à *(arr + idx)
. De plus, l'arithmétique du pointeur étant commutative, *(arr + idx)
est également équivalent à *(idx + arr)
, qui à son tour équivaut à idx[arr]
. Toutes ces expressions sont valides et évaluent à la même valeur, à condition que idx
ou arr
soit un pointeur (ou un tableau qui se désintègre en un pointeur), l'autre est un entier et l'entier est un index valide dans le tableau vers lequel pointe le pointeur
Comme cas particulier, observez que &(arr[0])
est équivalent à &*(arr + 0)
, ce qui simplifie l' arr
. Toutes ces expressions sont interchangeables partout où le dernier se désintègre en un pointeur. Cela exprime simplement qu'un tableau se désintègre en un pointeur vers son premier élément.
En revanche, si l’adresse de l’opérateur est appliquée à un tableau de type T[N]
( ie &arr
), le résultat a le type T (*)[N]
et pointe sur l’ensemble du tableau. Ceci est différent d'un pointeur vers le premier élément du tableau au moins en ce qui concerne l'arithmétique du pointeur, qui est défini en termes de taille du type pointé.
Les paramètres de fonction ne sont pas des tableaux .
void foo(int a[], int n);
void foo(int *a, int n);
Bien que la première déclaration de foo
utilise une syntaxe de type tableau pour le paramètre a
, cette syntaxe est utilisée pour déclarer un paramètre de fonction déclare ce paramètre comme un pointeur sur le type d'élément du tableau. Ainsi, la deuxième signature de foo()
est sémantiquement identique à la première. Cela correspond à la décroissance des valeurs de tableau en pointeurs où ils apparaissent comme des arguments à un appel de fonction, de telle sorte que si une variable et un paramètre de fonction sont déclarés avec le même type de tableau, cette valeur peut être utilisée dans un appel de fonction argument associé au paramètre.
Déclaration et initialisation d'un tableau
La syntaxe générale pour déclarer un tableau à une dimension est
type arrName[size];
où type
peut être un type intégré ou des types définis par l'utilisateur tels que des structures, arrName
est un identificateur défini par l'utilisateur et la size
est une constante entière.
Déclarer un tableau (un tableau de 10 variables int dans ce cas) est fait comme ceci:
int array[10];
il contient maintenant des valeurs indéterminées. Pour vous assurer qu'il contient des valeurs nulles lors de la déclaration, vous pouvez le faire:
int array[10] = {0};
Les tableaux peuvent aussi avoir des initialiseurs, cet exemple déclare un tableau de 10 int
, où les 3 premiers int
contiendront les valeurs 1
, 2
, 3
, toutes les autres valeurs seront nulles:
int array[10] = {1, 2, 3};
Dans la méthode d'initialisation ci-dessus, la première valeur de la liste sera affectée au premier membre du tableau, la deuxième valeur sera affectée au second membre du tableau, etc. Si la taille de la liste est inférieure à la taille du tableau, alors, comme dans l'exemple ci-dessus, les membres restants du tableau seront initialisés à zéro. Avec l'initialisation de la liste désignée (ISO C99), une initialisation explicite des membres du groupe est possible. Par exemple,
int array[5] = {[2] = 5, [1] = 2, [4] = 9}; /* array is {0, 2, 5, 0, 9} */
Dans la plupart des cas, le compilateur peut déduire la longueur du tableau pour vous, cela peut être réalisé en laissant les crochets vides:
int array[] = {1, 2, 3}; /* an array of 3 int's */
int array[] = {[3] = 8, [0] = 9}; /* size is 4 */
Déclarer un tableau de longueur nulle n'est pas autorisé.
Des tableaux de longueur variable (VLA en abrégé) ont été ajoutés en C99 et rendus facultatifs dans C11. Ils sont égaux aux tableaux normaux, avec une différence importante: la longueur ne doit pas nécessairement être connue au moment de la compilation. Les VLA ont une durée de stockage automatique. Seuls les pointeurs vers les VLA peuvent avoir une durée de stockage statique.
size_t m = calc_length(); /* calculate array length at runtime */
int vla[m]; /* create array with calculated length */
Important:
Les VLA sont potentiellement dangereux. Si le tableau vla
dans l'exemple ci-dessus nécessite plus d'espace sur la pile que disponible, la pile débordera. L'utilisation des VLA est donc souvent déconseillée dans les guides de style, les livres et les exercices.
Effacer le contenu du tableau (mise à zéro)
Parfois, il est nécessaire de définir un tableau à zéro, une fois l'initialisation terminée.
#include <stdlib.h> /* for EXIT_SUCCESS */
#define ARRLEN (10)
int main(void)
{
int array[ARRLEN]; /* Allocated but not initialised, as not defined static or global. */
size_t i;
for(i = 0; i < ARRLEN; ++i)
{
array[i] = 0;
}
return EXIT_SUCCESS;
}
Un raccourci courant vers la boucle ci-dessus consiste à utiliser memset()
partir de <string.h>
. Passer array
comme indiqué ci-dessous le fait passer à un pointeur sur son premier élément.
memset(array, 0, ARRLEN * sizeof (int)); /* Use size explicitly provided type (int here). */
ou
memset(array, 0, ARRLEN * sizeof *array); /* Use size of type the pointer is pointing to. */
Comme dans cet exemple, le array
est un tableau et pas seulement un pointeur sur le premier élément d'un tableau (voir la section Longueur du tableau sur l'importance de cet élément). Une troisième option permettant de désactiver le tableau est possible:
memset(array, 0, sizeof array); /* Use size of the array itself. */
Longueur du tableau
Les tableaux ont des longueurs fixes connues dans le cadre de leurs déclarations. Néanmoins, il est possible et parfois pratique de calculer des longueurs de tableau. En particulier, cela peut rendre le code plus flexible lorsque la longueur du tableau est déterminée automatiquement à partir d'un initialiseur:
int array[] = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
/* size of `array` in bytes */
size_t size = sizeof(array);
/* number of elements in `array` */
size_t length = sizeof(array) / sizeof(array[0]);
Cependant, dans la plupart des contextes où un tableau apparaît dans une expression, il est automatiquement converti en ("se désintègre en") en un pointeur vers son premier élément. Le cas où un tableau est l'opérande de l'opérateur sizeof
est l'un des quelques rares exceptions. Le pointeur résultant n'est pas lui-même un tableau et ne contient aucune information sur la longueur du tableau à partir duquel il a été dérivé. Par conséquent, si cette longueur est nécessaire avec le pointeur, par exemple lorsque le pointeur est passé à une fonction, il doit être acheminé séparément.
Par exemple, supposons que nous voulions écrire une fonction pour retourner le dernier élément d’un tableau de type int
. En continuant de ce qui précède, nous pourrions l'appeler ainsi:
/* array will decay to a pointer, so the length must be passed separately */
int last = get_last(array, length);
La fonction pourrait être implémentée comme ceci:
int get_last(int input[], size_t length) {
return input[length - 1];
}
Notons en particulier que, bien que la déclaration de paramètre input
ressemble à celle d'un tableau, il déclare en fait une input
tant que pointeur (vers int
). Cela équivaut exactement à déclarer input
comme int *input
. La même chose serait vraie même si une dimension était donnée. Cela est possible parce que les tableaux ne peuvent jamais être des arguments réels pour les fonctions (ils se désintègrent en pointeurs lorsqu'ils apparaissent dans les expressions d'appel de fonction) et peuvent être considérés comme mnémoniques.
C'est une erreur très courante d'essayer de déterminer la taille d'un tableau à partir d'un pointeur, qui ne peut pas fonctionner. NE FAITES PAS CELA:
int BAD_get_last(int input[]) {
/* INCORRECTLY COMPUTES THE LENGTH OF THE ARRAY INTO WHICH input POINTS: */
size_t length = sizeof(input) / sizeof(input[0]));
return input[length - 1]; /* Oops -- not the droid we are looking for */
}
En fait, cette erreur particulière est si courante que certains compilateurs le reconnaissent et l’avertissent. clang
, par exemple, émettra l'avertissement suivant:
warning: sizeof on array function parameter will return size of 'int *' instead of 'int []' [-Wsizeof-array-argument]
int length = sizeof(input) / sizeof(input[0]);
^
note: declared here
int BAD_get_last(int input[])
^
Définition de valeurs dans les tableaux
L'accès aux valeurs de tableau se fait généralement entre crochets:
int val;
int array[10];
/* Setting the value of the fifth element to 5: */
array[4] = 5;
/* The above is equal to: */
*(array + 4) = 5;
/* Reading the value of the fifth element: */
val = array[4];
Comme effet secondaire des opérandes à l'opérateur +
étant échangeable (-> loi commutative), ce qui suit est équivalent:
*(array + 4) = 5;
*(4 + array) = 5;
ainsi les prochaines déclarations sont équivalentes:
array[4] = 5;
4[array] = 5; /* Weird but valid C ... */
et ces deux aussi:
val = array[4];
val = 4[array]; /* Weird but valid C ... */
C n'effectue aucune vérification des limites, l'accès au contenu en dehors du tableau déclaré n'est pas défini ( Accès à la mémoire au-delà du bloc alloué ):
int val;
int array[10];
array[4] = 5; /* ok */
val = array[4]; /* ok */
array[19] = 20; /* undefined behavior */
val = array[15]; /* undefined behavior */
Définir un tableau et un élément de tableau d'accès
#include <stdio.h>
#define ARRLEN (10)
int main (void)
{
int n[ ARRLEN ]; /* n is an array of 10 integers */
size_t i, j; /* Use size_t to address memory, that is to index arrays, as its guaranteed to
be wide enough to address all of the possible available memory.
Using signed integers to do so should be considered a special use case,
and should be restricted to the uncommon case of being in the need of
negative indexes. */
/* Initialize elements of array n. */
for ( i = 0; i < ARRLEN ; i++ )
{
n[ i ] = i + 100; /* Set element at location i to i + 100. */
}
/* Output each array element's value. */
for (j = 0; j < ARRLEN ; j++ )
{
printf("Element[%zu] = %d\n", j, n[j] );
}
return 0;
}
Allouer et initialiser à zéro un tableau avec une taille définie par l'utilisateur
#include <stdio.h>
#include <stdlib.h>
int main (void)
{
int * pdata;
size_t n;
printf ("Enter the size of the array: ");
fflush(stdout); /* Make sure the prompt gets printed to buffered stdout. */
if (1 != scanf("%zu", &n)) /* If zu is not supported (Windows?) use lu. */
{
fprintf("scanf() did not read a in proper value.\n");
exit(EXIT_FAILURE);
}
pdata = calloc(n, sizeof *pdata);
if (NULL == pdata)
{
perror("calloc() failed"); /* Print error. */
exit(EXIT_FAILURE);
}
free(pdata); /* Clean up. */
return EXIT_SUCCESS;
}
Ce programme essaie d'analyser une valeur entière non signée à partir de l'entrée standard, alloue un bloc de mémoire pour un tableau de n
éléments de type int
en appelant la fonction calloc()
. La mémoire est initialisée à tous les zéros par ce dernier.
En cas de succès, la mémoire est free()
par l'appel à free()
.
Itérer à travers un tableau efficacement et ordre de rangée
Les tableaux en C peuvent être considérés comme un bloc de mémoire contigu. Plus précisément, la dernière dimension du tableau est la partie contiguë. Nous appelons cela l'ordre des rangées majeures . Comprendre cela et le fait qu'un défaut de cache charge une ligne de cache complète dans le cache lors de l' accès aux données non mises en cache pour éviter les défauts de cache suivantes, nous pouvons voir pourquoi l' accès à un tableau de dimension 10000x10000 avec array[0][0]
serait potentiellement charge array[0][1]
dans le cache, mais accéder au array[1][0]
juste après génèrerait un second défaut de cache, car il est sizeof(type)*10000
octets du array[0][0]
, et donc certainement pas sur la même ligne de cache. C'est pourquoi l'itération comme celle-ci est inefficace:
#define ARRLEN 10000
int array[ARRLEN][ARRLEN];
size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
for(j = 0; j < ARRLEN; ++j)
{
array[j][i] = 0;
}
}
Et itérer comme ça est plus efficace:
#define ARRLEN 10000
int array[ARRLEN][ARRLEN];
size_t i, j;
for (i = 0; i < ARRLEN; ++i)
{
for(j = 0; j < ARRLEN; ++j)
{
array[i][j] = 0;
}
}
Dans le même ordre d'idées, c'est la raison pour laquelle, lorsqu'il s'agit d'un tableau à une dimension et de plusieurs index (disons 2 dimensions ici pour la simplicité avec les index i et j), il est important de parcourir le tableau comme ceci:
#define DIM_X 10
#define DIM_Y 20
int array[DIM_X*DIM_Y];
size_t i, j;
for (i = 0; i < DIM_X; ++i)
{
for(j = 0; j < DIM_Y; ++j)
{
array[i*DIM_Y+j] = 0;
}
}
Ou avec 3 dimensions et index i, j et k:
#define DIM_X 10
#define DIM_Y 20
#define DIM_Z 30
int array[DIM_X*DIM_Y*DIM_Z];
size_t i, j, k;
for (i = 0; i < DIM_X; ++i)
{
for(j = 0; j < DIM_Y; ++j)
{
for (k = 0; k < DIM_Z; ++k)
{
array[i*DIM_Y*DIM_Z+j*DIM_Z+k] = 0;
}
}
}
Ou de manière plus générique, lorsque nous avons un tableau avec N1 x N2 x ... x Nd éléments, d dimensions et indices notés n1, n2, ..., et le décalage est calculé comme ceci
Image / formule extraite de: https://en.wikipedia.org/wiki/Row-major_order
Tableaux multidimensionnels
Le langage de programmation C permet des tableaux multidimensionnels . Voici la forme générale d'une déclaration de tableau multidimensionnel -
type name[size1][size2]...[sizeN];
Par exemple, la déclaration suivante crée un tableau d'entiers tridimensionnel (5 x 10 x 4):
int arr[5][10][4];
Tableaux bidimensionnels
La forme la plus simple de tableau multidimensionnel est le tableau à deux dimensions. Un tableau à deux dimensions est essentiellement une liste de tableaux à une dimension. Pour déclarer un tableau entier à deux dimensions de dimensions mxn, nous pouvons écrire comme suit:
type arrayName[m][n];
Où type
peut être n'importe quel type de données C valide ( int
, float
, etc.) et arrayName
peut être n'importe quel identifiant C valide. Un tableau à deux dimensions peut être visualisé sous forme de tableau avec m
lignes et n
colonnes. Note : L'ordre est important en C. Le tableau int a[4][3]
n'est pas le même que le tableau int a[3][4]
. Le nombre de lignes vient en premier car C est un langage de rang supérieur.
Un tableau à deux dimensions a
, qui contient trois lignes et quatre colonnes peut être affiché comme suit:
Ainsi, chaque élément du tableau a
est identifié par un nom d'élément de la forme a[i][j]
, où a
est le nom du tableau, i
représente quelle ligne et j
représente quelle colonne. Rappelez-vous que les lignes et les colonnes sont indexées à zéro. Ceci est très similaire à la notation mathématique pour les matrices 2D.
Initialisation de tableaux bidimensionnels
Les tableaux multidimensionnels peuvent être initialisés en spécifiant des valeurs entre parenthèses pour chaque ligne. Ce qui suit définit un tableau avec 3 lignes où chaque ligne a 4 colonnes.
int a[3][4] = {
{0, 1, 2, 3} , /* initializers for row indexed by 0 */
{4, 5, 6, 7} , /* initializers for row indexed by 1 */
{8, 9, 10, 11} /* initializers for row indexed by 2 */
};
Les accolades imbriquées, qui indiquent la ligne voulue, sont facultatives. L'initialisation suivante est équivalente à l'exemple précédent:
int a[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
Bien que la méthode de création de tableaux avec des accolades imbriquées soit facultative, elle est fortement recommandée car elle est plus lisible et plus claire.
Accès à des éléments de tableau à deux dimensions
On accède à un élément dans un tableau à deux dimensions en utilisant les indices, à savoir l’index de ligne et l’index de colonne du tableau. Par exemple -
int val = a[2][3];
L'instruction ci-dessus prendra le 4ème élément de la 3ème ligne du tableau. Laissez-nous vérifier le programme suivant où nous avons utilisé une boucle imbriquée pour gérer un tableau à deux dimensions:
#include <stdio.h>
int main () {
/* an array with 5 rows and 2 columns*/
int a[5][2] = { {0,0}, {1,2}, {2,4}, {3,6},{4,8}};
int i, j;
/* output each array element's value */
for ( i = 0; i < 5; i++ ) {
for ( j = 0; j < 2; j++ ) {
printf("a[%d][%d] = %d\n", i,j, a[i][j] );
}
}
return 0;
}
Lorsque le code ci-dessus est compilé et exécuté, il produit le résultat suivant:
a[0][0]: 0
a[0][1]: 0
a[1][0]: 1
a[1][1]: 2
a[2][0]: 2
a[2][1]: 4
a[3][0]: 3
a[3][1]: 6
a[4][0]: 4
a[4][1]: 8
Tableau tridimensionnel:
Un tableau 3D est essentiellement un tableau de tableaux de tableaux: il s’agit d’un tableau ou d’une collection de tableaux 2D, et un tableau 2D est un tableau de tableaux 1D.
Carte mémoire 3D:
Initialisation d'un tableau 3D:
double cprogram[3][2][4]={
{{-0.1, 0.22, 0.3, 4.3}, {2.3, 4.7, -0.9, 2}},
{{0.9, 3.6, 4.5, 4}, {1.2, 2.4, 0.22, -1}},
{{8.2, 3.12, 34.2, 0.1}, {2.1, 3.2, 4.3, -2.0}}
};
Nous pouvons avoir des tableaux avec un nombre quelconque de dimensions, bien qu'il soit probable que la plupart des tableaux créés auront une ou deux dimensions.
Itérer à travers un tableau en utilisant des pointeurs
#include <stdio.h>
#define SIZE (10)
int main()
{
size_t i = 0;
int *p = NULL;
int a[SIZE];
/* Setting up the values to be i*i */
for(i = 0; i < SIZE; ++i)
{
a[i] = i * i;
}
/* Reading the values using pointers */
for(p = a; p < a + SIZE; ++p)
{
printf("%d\n", *p);
}
return 0;
}
Ici, dans l'initialisation de p
dans la première for
condition de la boucle, le tableau d' a
désintègre à un pointeur sur son premier élément, car elle dans presque tous les endroits où une telle variable tableau est utilisé.
Ensuite, le ++p
effectue l'arithmétique du pointeur sur le pointeur p
et parcourt un par un les éléments du tableau, en les référençant par *p
.
Passer des tableaux multidimensionnels à une fonction
Les tableaux multidimensionnels suivent les mêmes règles que les tableaux à une dimension lors de leur transmission à une fonction. Cependant, la combinaison de decay-to-pointer, de priorité des opérateurs et des deux manières différentes de déclarer un tableau multidimensionnel (tableau de tableaux vs tableau de pointeurs) peut rendre la déclaration de ces fonctions non intuitive. L'exemple suivant montre comment transmettre correctement des tableaux multidimensionnels.
#include <assert.h>
#include <stdlib.h>
/* When passing a multidimensional array (i.e. an array of arrays) to a
function, it decays into a pointer to the first element as usual. But only
the top level decays, so what is passed is a pointer to an array of some fixed
size (4 in this case). */
void f(int x[][4]) {
assert(sizeof(*x) == sizeof(int) * 4);
}
/* This prototype is equivalent to f(int x[][4]).
The parentheses around *x are required because [index] has a higher
precedence than *expr, thus int *x[4] would normally be equivalent to int
*(x[4]), i.e. an array of 4 pointers to int. But if it's declared as a
function parameter, it decays into a pointer and becomes int **x,
which is not compatable with x[2][4]. */
void g(int (*x)[4]) {
assert(sizeof(*x) == sizeof(int) * 4);
}
/* An array of pointers may be passed to this, since it'll decay into a pointer
to pointer, but an array of arrays may not. */
void h(int **x) {
assert(sizeof(*x) == sizeof(int*));
}
int main(void) {
int foo[2][4];
f(foo);
g(foo);
/* Here we're dynamically creating an array of pointers. Note that the
size of each dimension is not part of the datatype, and so the type
system just treats it as a pointer to pointer, not a pointer to array
or array of arrays. */
int **bar = malloc(sizeof(*bar) * 2);
assert(bar);
for (size_t i = 0; i < 2; i++) {
bar[i] = malloc(sizeof(*bar[i]) * 4);
assert(bar[i]);
}
h(bar);
for (size_t i = 0; i < 2; i++) {
free(bar[i]);
}
free(bar);
}
Voir également
Passer des tableaux à des fonctions