Tableaux (C++)

Un tableau est une séquence d’objets du même type qui occupent une zone contiguë de mémoire. Les tableaux de style C traditionnels sont la source de nombreux bogues, mais sont toujours courants, en particulier dans les bases de code plus anciennes. Dans C++moderne, nous vous recommandons vivement d’utiliser std::vector ou std::array de remplacer les tableaux de style C décrits dans cette section. Ces deux types de bibliothèque standard stockent leurs éléments sous la forme d’un bloc contigu de mémoire. Toutefois, ils offrent une plus grande sécurité de type et prennent en charge les itérateurs qui sont garantis pour pointer vers un emplacement valide dans la séquence. Pour plus d’informations, consultez Conteneurs.

Déclarations de pile

Dans une déclaration de tableau C++, la taille du tableau est spécifiée après le nom de la variable, et non après le nom du type comme dans d’autres langues. L’exemple suivant déclare un tableau de 1 000 doubles à allouer sur la pile. Le nombre d’éléments doit être fourni en tant que littéral entier ou sous forme d’expression constante. C’est parce que le compilateur doit savoir combien d’espace de pile allouer ; elle ne peut pas utiliser une valeur calculée au moment de l’exécution. Chaque élément du tableau reçoit une valeur par défaut de 0. Si vous n’attribuez pas de valeur par défaut, chaque élément contient initialement les valeurs aléatoires qui se trouvent à cet emplacement de mémoire.

    constexpr size_t size = 1000;

    // Declare an array of doubles to be allocated on the stack
    double numbers[size] {0};

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i-1] * 1.1;
    }

    // Access each element
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

Le premier élément du tableau est le zéro élément. Le dernier élément est l’élément (n-1), où n est le nombre d’éléments que le tableau peut contenir. Le nombre d’éléments de la déclaration doit être de type intégral et doit être supérieur à 0. Il est de votre responsabilité de s’assurer que votre programme ne passe jamais une valeur à l’opérateur d’indice supérieur (size - 1)à .

Un tableau de taille zéro n’est légal que lorsque le tableau est le dernier champ d’un struct ou union lorsque les extensions Microsoft sont activées (/Za ou /permissive- n’est pas définie).

Les tableaux basés sur la pile sont plus rapides à allouer et à accéder qu’aux tableaux basés sur le tas. Toutefois, l’espace de pile est limité. Le nombre d’éléments de tableau ne peut pas être si volumineux qu’il utilise trop de mémoire de pile. Combien dépend trop de votre programme. Vous pouvez utiliser des outils de profilage pour déterminer si un tableau est trop volumineux.

Déclarations de tas

Vous pouvez nécessiter un tableau trop volumineux pour allouer sur la pile ou dont la taille n’est pas connue au moment de la compilation. Il est possible d’allouer ce tableau sur le tas à l’aide d’une new[] expression. L’opérateur retourne un pointeur vers le premier élément. L’opérateur d’indice fonctionne sur la variable de pointeur de la même façon que sur un tableau basé sur la pile. Vous pouvez également utiliser l’arithmétique du pointeur pour déplacer le pointeur vers tous les éléments arbitraires du tableau. Il est de votre responsabilité de s’assurer que :

  • vous conservez toujours une copie de l’adresse du pointeur d’origine afin de pouvoir supprimer la mémoire lorsque vous n’avez plus besoin du tableau.
  • vous n’incrémentez pas ou décrémentez l’adresse du pointeur au-delà des limites du tableau.

L’exemple suivant montre comment définir un tableau sur le tas au moment de l’exécution. Il montre comment accéder aux éléments de tableau à l’aide de l’opérateur d’indice et à l’aide de l’arithmétique du pointeur :

void do_something(size_t size)
{
    // Declare an array of doubles to be allocated on the heap
    double* numbers = new double[size]{ 0 };

    // Assign a new value to the first element
    numbers[0] = 1;

    // Assign a value to each subsequent element
    // (numbers[1] is the second element in the array.)
    for (size_t i = 1; i < size; i++)
    {
        numbers[i] = numbers[i - 1] * 1.1;
    }

    // Access each element with subscript operator
    for (size_t i = 0; i < size; i++)
    {
        std::cout << numbers[i] << " ";
    }

    // Access each element with pointer arithmetic
    // Use a copy of the pointer for iterating
    double* p = numbers;

    for (size_t i = 0; i < size; i++)
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    // Alternate method:
    // Reset p to numbers[0]:
    p = numbers;

    // Use address of pointer to compute bounds.
    // The compiler computes size as the number
    // of elements * (bytes per element).
    while (p < (numbers + size))
    {
        // Dereference the pointer, then increment it
        std::cout << *p++ << " ";
    }

    delete[] numbers; // don't forget to do this!

}
int main()
{
    do_something(108);
}

Initialisation des tableaux

Vous pouvez initialiser un tableau dans une boucle, un élément à la fois ou dans une seule instruction. Le contenu des deux tableaux suivants est identique :

    int a[10];
    for (int i = 0; i < 10; ++i)
    {
        a[i] = i + 1;
    }

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

Passage de tableaux à des fonctions

Lorsqu’un tableau est passé à une fonction, il est passé en tant que pointeur vers le premier élément, qu’il s’agisse d’un tableau basé sur une pile ou d’un tas. Le pointeur ne contient aucune autre taille ou information de type. Ce comportement est appelé décomposition du pointeur. Lorsque vous passez un tableau à une fonction, vous devez toujours spécifier le nombre d’éléments dans un paramètre distinct. Ce comportement implique également que les éléments de tableau ne sont pas copiés lorsque le tableau est passé à une fonction. Pour empêcher la fonction de modifier les éléments, spécifiez le paramètre comme pointeur vers const des éléments.

L’exemple suivant montre une fonction qui accepte un tableau et une longueur. Le pointeur pointe vers le tableau d’origine, pas une copie. Étant donné que le paramètre n’est pas const, la fonction peut modifier les éléments de tableau.

void process(double *p, const size_t len)
{
    std::cout << "process:\n";
    for (size_t i = 0; i < len; ++i)
    {
        // do something with p[i]
    }
}

Déclarez et définissez le paramètre p de tableau pour const le rendre en lecture seule dans le bloc de fonction :

void process(const double *p, const size_t len);

La même fonction peut également être déclarée de ces façons, sans changement de comportement. Le tableau est toujours passé en tant que pointeur vers le premier élément :

// Unsized array
void process(const double p[], const size_t len);

// Fixed-size array. Length must still be specified explicitly.
void process(const double p[1000], const size_t len);

Tableaux multidimensionnels

Les tableaux construits à partir d'autres tableaux sont des tableaux multidimensionnels. Ces tableaux multidimensionnels sont spécifiés en plaçant dans l'ordre plusieurs expressions de constantes entre accolades. Par exemple, observez cette déclaration :

int i2[5][7];

Il spécifie un tableau de type int, organisé conceptuellement dans une matrice à deux dimensions de cinq lignes et sept colonnes, comme illustré dans la figure suivante :

Conceptual layout of a multidimensional array.

L’image est une grille de 7 cellules larges et 5 cellules élevées. Chaque cellule contient l’index de la cellule. Le premier index de cellule est étiqueté 0,0. La cellule suivante de cette ligne est 0,1, et ainsi de suite à la dernière cellule de cette ligne, qui est de 0,6. La ligne suivante commence par l’index 1,0. La cellule qui a un index de 1,1. La dernière cellule de cette ligne est 1,6. Ce modèle se répète jusqu’à la dernière ligne, qui commence par l’index 4,0. La dernière cellule de la dernière ligne a un index de 4 6. :::image-end

Vous pouvez déclarer des tableaux multidimensionnés qui ont une liste d’initialiseurs (comme décrit dans Initialiseurs). Dans ces déclarations, l’expression constante qui spécifie les limites de la première dimension peut être omise. Par exemple :

// arrays2.cpp
// compile with: /c
const int cMarkets = 4;
// Declare a float that represents the transportation costs.
double TransportCosts[][cMarkets] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

La déclaration précédente définit un tableau composé de trois lignes et de quatre colonnes. Les lignes représentent les fabriques et les colonnes les marchés auxquels les fabriques livrent. Les valeurs sont les coûts de transport entre les fabriques et les marchés. La première dimension du tableau est omise, mais le compilateur la complète en examinant l'initialiseur.

L’utilisation de l’opérateur indirection (*) sur un type de tableau ndimensionnel génère un tableau n-1 dimensionnel. Si n est égal à 1, un scalaire (ou un élément de tableau) est généré.

Les tableaux C++ sont stockés dans l'ordre row-major. L'ordre row-major signifie que le dernier indice varie le plus rapidement.

Exemple

Vous pouvez également omettre la spécification des limites pour la première dimension d’un tableau multidimensionnel dans les déclarations de fonction, comme illustré ici :

// multidimensional_arrays.cpp
// compile with: /EHsc
// arguments: 3
#include <limits>   // Includes DBL_MAX
#include <iostream>

const int cMkts = 4, cFacts = 2;

// Declare a float that represents the transportation costs
double TransportCosts[][cMkts] = {
   { 32.19, 47.29, 31.99, 19.11 },
   { 11.29, 22.49, 33.47, 17.29 },
   { 41.97, 22.09,  9.76, 22.55 }
};

// Calculate size of unspecified dimension
const int cFactories = sizeof TransportCosts /
                  sizeof( double[cMkts] );

double FindMinToMkt( int Mkt, double myTransportCosts[][cMkts], int mycFacts);

using namespace std;

int main( int argc, char *argv[] ) {
   double MinCost;

   if (argv[1] == 0) {
      cout << "You must specify the number of markets." << endl;
      exit(0);
   }
   MinCost = FindMinToMkt( *argv[1] - '0', TransportCosts, cFacts);
   cout << "The minimum cost to Market " << argv[1] << " is: "
       << MinCost << "\n";
}

double FindMinToMkt(int Mkt, double myTransportCosts[][cMkts], int mycFacts) {
   double MinCost = DBL_MAX;

   for( size_t i = 0; i < cFacts; ++i )
      MinCost = (MinCost < TransportCosts[i][Mkt]) ?
         MinCost : TransportCosts[i][Mkt];

   return MinCost;
}
The minimum cost to Market 3 is: 17.29

La fonction FindMinToMkt est écrite de telle sorte que l’ajout de nouvelles fabriques ne nécessite aucune modification de code, juste une recompilation.

Initialisation des tableaux

Les tableaux d’objets qui ont un constructeur de classe sont initialisés par le constructeur. Lorsqu’il y a moins d’éléments dans la liste d’initialiseurs que les éléments du tableau, le constructeur par défaut est utilisé pour les éléments restants. Si aucun constructeur par défaut n’est défini pour la classe, la liste d’initialiseurs doit être terminée, autrement dit, il doit y avoir un initialiseur pour chaque élément du tableau.

Prenons la classe Point qui définit deux constructeurs :

// initializing_arrays1.cpp
class Point
{
public:
   Point()   // Default constructor.
   {
   }
   Point( int, int )   // Construct from two ints
   {
   }
};

// An array of Point objects can be declared as follows:
Point aPoint[3] = {
   Point( 3, 3 )     // Use int, int constructor.
};

int main()
{
}

Le premier élément de aPoint est construit en utilisant le constructeur Point( int, int ). Les deux éléments restants sont construits à l'aide du constructeur par défaut.

Les tableaux de membres statiques (qu’ils const soient ou non) peuvent être initialisés dans leurs définitions (en dehors de la déclaration de classe). Par exemple :

// initializing_arrays2.cpp
class WindowColors
{
public:
    static const char *rgszWindowPartList[7];
};

const char *WindowColors::rgszWindowPartList[7] = {
    "Active Title Bar", "Inactive Title Bar", "Title Bar Text",
    "Menu Bar", "Menu Bar Text", "Window Background", "Frame"   };
int main()
{
}

Accès aux éléments de tableau

Vous pouvez accéder à des éléments individuels d’un tableau à l’aide de l’opérateur d’indice de tableau ([ ]). Si vous utilisez le nom d’un tableau unidimensionnel sans indice, il est évalué comme pointeur vers le premier élément du tableau.

// using_arrays.cpp
int main() {
   char chArray[10];
   char *pch = chArray;   // Evaluates to a pointer to the first element.
   char   ch = chArray[0];   // Evaluates to the value of the first element.
   ch = chArray[3];   // Evaluates to the value of the fourth element.
}

Lorsque vous utilisez des tableaux multidimensionnels, vous pouvez utiliser différentes combinaisons dans les expressions.

// using_arrays_2.cpp
// compile with: /EHsc /W1
#include <iostream>
using namespace std;
int main() {
   double multi[4][4][3];   // Declare the array.
   double (*p2multi)[3];
   double (*p1multi);

   cout << multi[3][2][2] << "\n";   // C4700 Use three subscripts.
   p2multi = multi[3];               // Make p2multi point to
                                     // fourth "plane" of multi.
   p1multi = multi[3][2];            // Make p1multi point to
                                     // fourth plane, third row
                                     // of multi.
}

Dans le code précédent, multi il s’agit d’un tableau tridimensionnel de type double. Le p2multi pointeur pointe vers un tableau de type de taille double trois. Dans cet exemple, le tableau est utilisé avec un, deux et trois indices. Bien qu’il soit plus courant de spécifier tous les indices, comme dans l’instruction cout , il est parfois utile de sélectionner un sous-ensemble spécifique d’éléments de tableau, comme indiqué dans les instructions qui suivent cout.

Opérateur d’indice surchargé

Comme d’autres opérateurs, l’opérateur d’indice ([]) peut être redéfini par l’utilisateur. Le comportement par défaut de l'opérateur d'indice, s'il n'est pas surchargé, est de combiner le nom d'un tableau et l'indice à l'aide de la méthode suivante :

*((array_name) + (subscript))

Comme dans tous les autres cas qui impliquent des types de pointeurs, la mise à l’échelle est effectuée automatiquement pour ajuster la taille du type. La valeur résultante n’est pas n octets de l’origine de array_name; à la place, il s’agit du nièmeélément du tableau. Pour plus d’informations sur cette conversion, consultez Opérateurs additifs.

De même, pour des tableaux multidimensionnels, l'adresse est dérivée à l'aide de la méthode suivante :

((array_name) + (subscript1 * max2 * max3 * ... * maxn) + (subscript2 * max3 * ... * maxn) + ... + subscriptn))

Tableaux dans les expressions

Lorsqu’un identificateur d’un type de tableau apparaît dans une expression autre que sizeof, adresse de (&) ou initialisation d’une référence, il est converti en pointeur vers le premier élément de tableau. Par exemple :

char szError1[] = "Error: Disk drive not ready.";
char *psz = szError1;

Le pointeur psz pointe vers le premier élément du tableau szError1. Les tableaux, contrairement aux pointeurs, ne sont pas modifiables l-values. C’est pourquoi l’affectation suivante est illégale :

szError1 = psz;

Voir aussi

std::array