512 votes

Comment puis-je utiliser des tableaux en C++?

C++ hérité de tableaux à partir de C, où ils sont utilisés pratiquement partout. C++ fournit des abstractions qui sont plus faciles à utiliser et moins sujettes à l'erreur (std::vector<T> depuis C++98 et std::array<T, n> depuis C++11), de sorte que le besoin pour les tableaux ne se pose pas tout à fait aussi souvent que il n'en C. Cependant, lorsque vous lisez le code de legs ou d'interagir avec une bibliothèque écrite en C, vous devriez avoir une prise ferme sur la façon dont les matrices de travail.

Cette FAQ est divisé en cinq parties:

  1. des tableaux au niveau du type et de l'accès aux éléments
  2. la création de la matrice et de l'initialisation
  3. la cession et la transmission de paramètres
  4. les tableaux multidimensionnels et des tableaux de pointeurs
  5. les pièges les plus courants lors de l'utilisation de tableaux

Si vous vous sentez quelque chose d'important est manquant dans cette FAQ, écrire une réponse et le lien ici comme un élément additionnel.

Dans le texte suivant, "array" signifie "C tableau", et non pas le modèle de classe std::array. Connaissances de base de la C de demande de déclaration syntaxe est supposé. Notez que le manuel d'utilisation de l' new et delete comme illustré ci-dessous est extrêmement dangereux face à des exceptions, mais c'est le sujet d' un autre FAQ.

(Note: Ceci est destiné à être une entrée à Débordement de Pile du C++ FAQ. Si vous voulez une critique de l'idée de fournir une FAQ dans ce formulaire, puis de la poster sur meta qui a commencé tout cela serait l'endroit pour le faire. Les réponses à cette question sont surveillés en C++ salon, où la FAQ idée a commencé à en premier lieu, de sorte que votre réponse est très probablement le faire lire par ceux qui sont venus avec l'idée.)

319voto

FredOverflow Points 88201

Des tableaux au niveau du type

Un type de pile est notée T[n]T est le type d'élément et d' n est positif, la taille, le nombre d'éléments dans le tableau. Le type du tableau est un type de produit, le type de l'élément et de la taille. Si l'un ou les deux de ces ingrédients différents, vous obtenez un type distinct:

#include <type_traits>

static_assert(!std::is_same<int[8], float[8]>::value, "distinct element type");
static_assert(!std::is_same<int[8],   int[9]>::value, "distinct size");

Notez que la taille fait partie de la nature, qui est, les types de tableau de taille différente sont incompatibles types qui n'ont absolument rien à voir les uns avec les autres. sizeof(T[n]) est équivalent à n * sizeof(T).

Tableau de pointeur de la décroissance

Le seul "lien" entre T[n] et T[m] , c'est que les deux types peuvent être implicitement converti à l' T*, et le résultat de cette conversion est un pointeur vers le premier élément du tableau. C'est, n'importe où, T* est requis, vous pouvez fournir un T[n], et le compilateur en silence à fournir le pointeur:

                  +---+---+---+---+---+---+---+---+
the_actual_array: |   |   |   |   |   |   |   |   |   int[8]
                  +---+---+---+---+---+---+---+---+
                    ^
                    |
                    |
                    |
                    |  pointer_to_the_first_element   int*

Cette conversion est connu comme "tableau de pointeur de la décroissance", et il est une source majeure de confusion. La taille de la matrice est perdu dans ce processus, car il n'est plus de la partie de type (T*). Pro: l'Oubli de la taille d'un tableau sur le type de niveau permet un pointeur sur le premier élément d'un tableau de toute taille. Con: un pointeur vers le premier (ou à tout autre élément d'un tableau, il n'y a aucun moyen de détecter la taille de ce tableau est ou exactement où le pointeur de points par rapport à la limites du tableau. Les pointeurs sont extrêmement stupide.

Les tableaux ne sont pas des pointeurs

Le compilateur en silence à générer un pointeur vers le premier élément d'un tableau à chaque fois qu'il est jugé utile, qui est, chaque fois qu'une opération échouait sur un tableau, mais réussir sur un pointeur. Cette conversion de tableau de pointeur est trivial, puisque le pointeur résultant de la valeur est simplement l'adresse de la table. Notez que le pointeur est pas stocké dans le tableau lui-même (ou n'importe où ailleurs dans la mémoire). Un tableau n'est pas un pointeur.

static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");

Un important contexte dans lequel une matrice de ne pas se désintégrer en un pointeur sur son premier élément, c'est quand l' & opérateur est appliqué. Dans ce cas, l' & opérateur renvoie un pointeur sur l' ensemble du tableau, et pas seulement un pointeur vers son premier élément. Bien que dans ce cas les valeurs (les adresses) sont les mêmes, un pointeur vers le premier élément d'un tableau et un pointeur vers le tableau d'ensemble sont complètement types distincts:

static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");

La suite de l'ASCII art explique cette distinction:

      +-----------------------------------+
      | +---+---+---+---+---+---+---+---+ |
+---> | |   |   |   |   |   |   |   |   | | int[8]
|     | +---+---+---+---+---+---+---+---+ |
|     +---^-------------------------------+
|         |
|         |
|         |
|         |  pointer_to_the_first_element   int*
|
|  pointer_to_the_entire_array              int(*)[8]

Notez comment le pointeur vers le premier élément de points à un seul entier (représentée comme une petite boîte), alors que le pointeur à l'ensemble de la matrice de points à un tableau de 8 entiers (représentée comme une grande boîte).

La même situation se présente dans les classes et est peut-être plus évident. Un pointeur vers un objet et un pointeur vers son premier membre de données ont la même valeur (même adresse), mais ils sont complètement types distincts.

Si vous n'êtes pas familier avec le C de demande de déclaration de la syntaxe, de la parenthèse dans le type int(*)[8] sont essentiels:

  • int(*)[8] est un pointeur vers un tableau de 8 entiers.
  • int*[8] est un tableau de 8 pointeurs, chaque élément de type int*.

L'accès aux éléments

C++ fournit deux syntaxique des variations d'accéder aux éléments d'un tableau. Aucun d'eux n'est supérieur à l'autre, et vous devriez vous familiariser avec les deux.

L'arithmétique des pointeurs

Un pointeur p pour le premier élément d'un tableau, l'expression p+i renvoie un pointeur vers la i-ème élément du tableau. Par déréférencement du pointeur par la suite, on peut accéder aux éléments individuels:

std::cout << *(x+3) << ", " << *(x+7) << std::endl;

Si x dénote un tableau, puis tableau de pointeur de la chute du coup, parce que l'ajout d'un tableau et d'un entier est vide de sens (il n'y a pas d'opération plus sur les tableaux), mais l'ajout d'un pointeur et un entier qui prend tout son sens:

   +---+---+---+---+---+---+---+---+
x: |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
     |           |               |
x+0  |      x+3  |          x+7  |     int*

(Notez que le générées implicitement pointeur n'a pas de nom, j'ai donc écrit x+0 afin de l'identifier.)

Si, d'autre part, x indique un pointeur vers le premier (ou à tout autre élément d'un tableau, puis tableau de pointeur de la décomposition n'est pas nécessaire, car le pointeur sur qui i va être ajoutée existe déjà:

   +---+---+---+---+---+---+---+---+
   |   |   |   |   |   |   |   |   |   int[8]
   +---+---+---+---+---+---+---+---+
     ^           ^               ^
     |           |               |
     |           |               |
   +-|-+         |               |
x: | | |    x+3  |          x+7  |     int*
   +---+

Notez que dans le dépeint cas, x est un pointeur de variable (perceptibles par la petite case à côté de x), mais elle pourrait tout aussi bien être le résultat d'une fonction retournant un pointeur (ou toute autre expression de type T*).

L'indexation de l'opérateur

Depuis la syntaxe *(x+i) est un peu maladroit, C++ fournit la syntaxe alternative x[i]:

std::cout << x[3] << ", " << x[7] << std::endl;

En raison du fait que l'addition est commutative, le code suivant n'est exactement le même:

std::cout << 3[x] << ", " << 7[x] << std::endl;

La définition de l'opérateur d'indexation conduit à la suivante intéressant d'équivalence:

&x[i]  ==  &*(x+i)  ==  x+i

Toutefois, &x[0] est généralement pas équivalent à x. Le premier est un pointeur, ce dernier tableau. Seulement lorsque le contexte déclenche tableau de pointeur de la décroissance peut - x et &x[0] être utilisés de façon interchangeable. Par exemple:

T* p = &array[0];  // rewritten as &*(array+0), decay happens due to the addition
T* q = array;      // decay happens due to the assignment

Sur la première ligne, le compilateur détecte une affectation d'un pointeur vers un pointeur, ce qui trivialement réussit. Sur la deuxième ligne, il détecte une affectation à partir d'un tableau à un pointeur. Depuis il n'en est rien (mais pointeur de pointeur de la cession de sens), tableau de pointeur de la désintégration des coups de pied comme d'habitude.

Les plages

Un tableau de type T[n] a n éléments, indexées à partir d' 0 de n-1; il n'existe aucun élément n. Et pourtant, à l'appui de la demi-plages ouvertes (où le début est inclusive et la fin est exclusif), C++ permet le calcul d'un pointeur vers l' (inexistant) n-ième élément, mais il est illégal de déréférencement de pointeur:

   +---+---+---+---+---+---+---+---+....
x: |   |   |   |   |   |   |   |   |   .   int[8]
   +---+---+---+---+---+---+---+---+....
     ^                               ^
     |                               |
     |                               |
     |                               |
x+0  |                          x+8  |     int*

Par exemple, si vous souhaitez trier un tableau, les deux fonctionnerait tout aussi bien:

std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);

Notez qu'il est illégal de fournir &x[n] comme deuxième argument, puisque c'est l'équivalent d' &*(x+n), et la sous-expression *(x+n) techniquement invoque un comportement indéfini en C++ (mais pas en C99).

Notez également que vous pouvez simplement fournir de l' x comme premier argument. C'est un peu trop court à mon goût, et il rend également argument de modèle de déduction un peu plus dur pour le compilateur, parce que dans ce cas, le premier argument est un tableau, mais le second argument est un pointeur. (Encore une fois, tableau de pointeur de la désintégration des coups de pied.)

146voto

FredOverflow Points 88201

Les programmeurs ont souvent confondre les tableaux multidimensionnels avec des tableaux de pointeurs.

Les tableaux multidimensionnels

La plupart des programmeurs sont familiers avec nommée tableaux multidimensionnels, mais beaucoup ne sont pas conscients du fait que le tableau multidimensionnel peut également être créé de manière anonyme. Les tableaux multidimensionnels sont souvent désignés comme des "tableaux de tableaux" ou "true tableaux multidimensionnels".

Nommé tableaux multidimensionnels

Lors de l'utilisation nommé tableaux multidimensionnels, toutes les dimensions doivent être connues au moment de la compilation:

int H = read_int();
int W = read_int();

int connect_four[6][7];   // okay

int connect_four[H][7];   // ISO C++ forbids variable length array
int connect_four[6][W];   // ISO C++ forbids variable length array
int connect_four[H][W];   // ISO C++ forbids variable length array

C'est comment un nom de tableau multidimensionnel ressemble en mémoire:

              +---+---+---+---+---+---+---+
connect_four: |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+
              |   |   |   |   |   |   |   |
              +---+---+---+---+---+---+---+

Notez que les grilles 2D tels que le ci-dessus sont simplement utiles, des visualisations. Du point de vue de C++, la mémoire est un "plat" de la séquence d'octets. Les éléments d'un tableau multidimensionnel sont stockés en ligne ordre majeur. C'est, connect_four[0][6] et connect_four[1][0] sont voisins dans la mémoire. En fait, connect_four[0][7] et connect_four[1][0] désignent la même élément! Cela signifie que vous pouvez prendre des multi-dimensions des tableaux et de les traiter comme des grandes, des tableaux unidimensionnels:

int* p = &connect_four[0][0];
int* q = p + 42;
some_int_sequence_algorithm(p, q);

Anonyme tableaux multidimensionnels

Avec anonyme de tableaux multidimensionnels, toutes les dimensions sauf la première doit être connu au moment de la compilation:

int (*p)[7] = new int[6][7];   // okay
int (*p)[7] = new int[H][7];   // okay

int (*p)[W] = new int[6][W];   // ISO C++ forbids variable length array
int (*p)[W] = new int[H][W];   // ISO C++ forbids variable length array

C'est de cette façon anonyme tableau multidimensionnel ressemble en mémoire:

              +---+---+---+---+---+---+---+
        +---> |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |     |   |   |   |   |   |   |   |
        |     +---+---+---+---+---+---+---+
        |
      +-|-+
   p: | | |
      +---+

Notez que le tableau lui-même est toujours allouée comme un seul bloc dans la mémoire.

Des tableaux de pointeurs

Vous pouvez surmonter la restriction de largeur fixe par l'introduction d'un autre niveau d'indirection.

Nommé tableaux de pointeurs

Voici un tableau de cinq pointeurs qui sont initialisés avec des anonymes, des tableaux de différentes longueurs:

int* triangle[5];
for (int i = 0; i < 5; ++i)
{
    triangle[i] = new int[5 - i];
}

// ...

for (int i = 0; i < 5; ++i)
{
    delete[] triangle[i];
}

Et voici à quoi il ressemble dans la mémoire:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
triangle: | | | | | | | | | | |
          +---+---+---+---+---+

Étant donné que chaque ligne est attribuée individuellement à maintenant, la visualisation 2D des tableaux en tableaux 1D ne fonctionne plus.

Anonyme des tableaux de pointeurs

Voici un tableau anonyme de 5 (ou tout autre nombre) des pointeurs qui sont initialisés avec des anonymes, des tableaux de différentes longueurs:

int n = calculate_five();   // or any other number
int** p = new int*[n];
for (int i = 0; i < n; ++i)
{
    p[i] = new int[n - i];
}

// ...

for (int i = 0; i < n; ++i)
{
    delete[] p[i];
}
delete[] p;   // note the extra delete[] !

Et voici à quoi il ressemble dans la mémoire:

          +---+---+---+---+---+
          |   |   |   |   |   |
          +---+---+---+---+---+
            ^
            | +---+---+---+---+
            | |   |   |   |   |
            | +---+---+---+---+
            |   ^
            |   | +---+---+---+
            |   | |   |   |   |
            |   | +---+---+---+
            |   |   ^
            |   |   | +---+---+
            |   |   | |   |   |
            |   |   | +---+---+
            |   |   |   ^
            |   |   |   | +---+
            |   |   |   | |   |
            |   |   |   | +---+
            |   |   |   |   ^
            |   |   |   |   |
            |   |   |   |   |
          +-|-+-|-+-|-+-|-+-|-+
          | | | | | | | | | | |
          +---+---+---+---+---+
            ^
            |
            |
          +-|-+
       p: | | |
          +---+

Les Conversions

Tableau de pointeur de la décroissance implique naturellement des tableaux de tableaux et des tableaux de pointeurs:

int array_of_arrays[6][7];
int (*pointer_to_array)[7] = array_of_arrays;

int* array_of_pointers[6];
int** pointer_to_pointer = array_of_pointers;

Cependant, il n'y a pas de conversion implicite de T[h][w] de T**. Si une telle conversion implicite n'existe pas, le résultat serait un pointeur vers le premier élément d'un tableau d' h des pointeurs vers T (chacune pointant vers le premier élément d'une ligne dans l'original tableau 2D), mais le pointeur de tableau n'existe nulle part dans la mémoire encore. Si vous voulez une telle conversion, vous devez créer et de remplir le nécessaire pointeur de tableau manuellement:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = connect_four[i];
}

// ...

delete[] p;

Notez que cela génère une vue de l'original tableau multidimensionnel. Si vous avez besoin d'une copie au lieu de cela, vous devez créer d'autres tableaux et de copier les données vous-même:

int connect_four[6][7];

int** p = new int*[6];
for (int i = 0; i < 6; ++i)
{
    p[i] = new int[7];
    std::copy(connect_four[i], connect_four[i + 1], p[i]);
}

// ...

for (int i = 0; i < 6; ++i)
{
    delete[] p[i];
}
delete[] p;

92voto

FredOverflow Points 88201

Affectation

Pour aucune raison en particulier, les tableaux ne peuvent pas être attribuées à un autre. Utiliser std::copy à la place:

#include <algorithm>

// ...

int a[8] = {2, 3, 5, 7, 11, 13, 17, 19};
int b[8];
std::copy(a + 0, a + 8, b);

C'est plus souple que ce que le vrai tableau d'affectation pourrait fournir, car il est possible de copier des tranches de grandes baies en petits tableaux. std::copy est généralement spécialisé pour les types primitifs pour donner le maximum de performance. Il est peu probable qu' std::memcpy plus performant. En cas de doute, de mesure.

Bien que vous ne pouvez pas attribuer les tableaux directement, vous pouvez attribuer des structures et des classes qui contiennent des membres du groupe. C'est parce que les membres du groupe sont copiés memberwise par l'opérateur d'affectation qui est fourni par défaut par le compilateur. Si vous définissez l'opérateur d'affectation manuellement pour votre propre structure ou types de classe, vous devez revenir à la reproduction manuelle pour les membres du groupe.

Passage de paramètres

Les tableaux ne peuvent pas être passés par valeur. Vous pouvez passer soit par pointeur ou par référence.

Passage par pointeur

Comme les tableaux eux-mêmes ne peuvent pas être passés par valeur, généralement un pointeur vers son premier élément est passé par valeur. Ceci est souvent appelé "passage par pointeur". Depuis, la taille du tableau n'est pas récupérable via le pointeur, vous devez passer un deuxième paramètre indiquant la taille du tableau (le classique C de la solution) ou un deuxième pointeur pointant après le dernier élément du tableau (le C++ itérateur solution):

#include <numeric>
#include <cstddef>

int sum(const int* p, std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

int sum(const int* p, const int* q)
{
    return std::accumulate(p, q, 0);
}

Comme un syntaxiques alternative, vous pouvez également déclarer les paramètres T p[], et il signifie la même chose que d' T* p dans le cadre de listes de paramètres seulement:

int sum(const int p[], std::size_t n)
{
    return std::accumulate(p, p + n, 0);
}

Vous pouvez penser au compilateur que la réécriture T p[] de T *p dans le cadre de listes de paramètres seulement. Cette règle spéciale est en partie responsable de l'ensemble de la confusion sur les tableaux et les pointeurs. Dans tout autre contexte, déclarer que quelque chose comme un tableau ou un pointeur fait une énorme différence.

Malheureusement, vous pouvez également donner une taille à un paramètre de tableau qui est silencieusement ignoré par le compilateur. Qui est, les trois signatures sont exactement équivalent, comme indiqué par les erreurs du compilateur:

int sum(const int* p, std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[], std::size_t n)

// error: redefinition of 'int sum(const int*, size_t)'
int sum(const int p[8], std::size_t n)   // the 8 has no meaning here

Passage par référence

Les tableaux peuvent également être passés par référence:

int sum(const int (&a)[8])
{
    return std::accumulate(a + 0, a + 8, 0);
}

Dans ce cas, la taille de la matrice est importante. Car l'écriture d'une fonction qui n'accepte que les tableaux d'exactement 8 éléments est de peu d'utilité, les programmeurs l'habitude d'écrire des fonctions, telles que les modèles de:

template <std::size_t n>
int sum(const int (&a)[n])
{
    return std::accumulate(a + 0, a + n, 0);
}

Notez que vous pouvez uniquement appeler une fonction de ce genre de modèle, avec un tableau d'entiers, non pas avec un pointeur sur un entier. La taille de la pile est automatiquement déduit, et pour chaque taille n, une autre fonction est instancié à partir du modèle. Vous pouvez également écrire très utile fonction de modèles abstraits à partir d'un élément de type et de la taille.

75voto

5. Les pièges les plus courants lors de l'utilisation de tableaux.

5.1 Écueil: la Confiance de type dangereux de la liaison.

OK, on vous l'a dit, ou avez trouvé vous-même, que globals (espace de noms portée des variables qui peuvent être accessibles à l'extérieur de l'unité de traduction) sont Evil™. Mais avez-vous vraiment savoir comment le Mal™ ils sont? Envisager l' programme ci-dessous, composé de deux fichiers [main.cpp] et [numbers.cpp]:

// [main.cpp]
#include <iostream>

extern int* numbers;

int main()
{
    using namespace std;
    for( int i = 0;  i < 42;  ++i )
    {
        cout << (i > 0? ", " : "") << numbers[i];
    }
    cout << endl;
}

// [numbers.cpp]
int numbers[42] = {1, 2, 3, 4, 5, 6, 7, 8, 9};

Dans Windows 7 cette compile et des liens bien avec MinGW g++ 4.4.1 et Visual C++ 10.0.

Depuis que les types ne correspondent pas, le programme se bloque lorsque vous exécutez.

The Windows 7 crash dialog

Dans l'explication officielle: le programme a un Comportement Indéfini (UB), et au lieu de s'écraser, il peut donc se pendre, ou peut-être ne rien faire, ou il pouvez envoyer dangereux des e-mails pour les présidents des etats-unis, la Russie, l'Inde, La chine et la Suisse, et de faire Nasale Démons voler hors de votre nez.

En pratique explication: en main.cpp le tableau est considéré comme un pointeur, placé à la même adresse que dans le tableau. Pour 32-bit exécutable cela signifie que le premier int de la valeur dans le tableau, est considéré comme un pointeur. I. e., en main.cpple numbers variable contient ou semble contenir, (int*)1. Cela provoque la programme d'accès mémoire vers le bas au bas de l'espace d'adressage, ce qui est classiquement réservés et de piège à l'origine. Résultat: vous obtenez un crash.

Les compilateurs sont entièrement à l'intérieur de leurs droits de ne pas diagnostiquer cette erreur, parce que le C++11 §3.5/10 dit, à propos de l'exigence de types compatibles pour les déclarations,

[N3290 §3.5/10]
Une violation de cette règle sur le type d'identité n'a pas besoin d'un diagnostic.

Le même paragraphe les détails de la variation qui est autorisé:

... déclarations pour un objet array pouvez spécifier les types de tableau qui diffèrent par la présence ou l'absence d'un tableau majeur lié (8.3.4).

Cela a permis de variation ne comprend pas déclarer un nom comme un tableau dans un l'unité de traduction, et comme un pointeur dans une autre unité de traduction.

5.2 Piège: Faire de l'optimisation prématurée (memset & friends).

Pas encore écrit

5.3 Piège: à l'Aide de la C idiome pour obtenir le nombre d'éléments.

Avec une profonde expérience en C, il est naturel d'écrire ...

#define COUNT_OF( array )   (sizeof( array )/sizeof( array[0] ))

Depuis un array des désintégrations de pointeur vers le premier élément en cas de besoin, l' l'expression sizeof(a)/sizeof(a[0]) peut aussi être écrite sizeof(a)/sizeof(*a). Il signifie la même chose, et n'importe comment il est écrit que c'est le C idiome pour trouver le nombre d'éléments du tableau.

Principal écueil: le C idiome n'est pas typesafe. Par exemple, le code ...

#include <stdio.h>

#define COUNT_OF( array ) (sizeof( array )/sizeof( *array ))

void display( int const a[7] )
{
    int const   n = COUNT_OF( a );          // Oops.
    printf( "%d elements.\n", n );
}

int main()
{
    int const   moohaha[]   = {1, 2, 3, 4, 5, 6, 7};

    printf( "%d elements, calling display...\n", COUNT_OF( moohaha ) );
    display( moohaha );
}

passe un pointeur vers COUNT_OF, et par conséquent plus probablement un mauvais produit résultat. Compilé sous forme d'un exécutable 32 bits de Windows 7, il produit ...

7 éléments, l'appel de l'affichage...
1 éléments.

  1. Le compilateur réécrit int const a[7] juste int const a[].
  2. Le compilateur réécrit int const a[] de int const* a.
  3. COUNT_OF est donc invoquée avec un pointeur.
  4. Pour un exécutable 32 bits sizeof(array) (de la taille d'un pointeur) est de 4.
  5. sizeof(*array) est équivalent à sizeof(int), ce qui pour un exécutable 32 bits est aussi 4.

Afin de détecter cette erreur au moment de l'exécution que vous pouvez faire ...

#include <assert.h>
#include <typeinfo>

#define COUNT_OF( array )       (                               \
    assert((                                                    \
        "COUNT_OF requires an actual array as argument",        \
        typeid( array ) != typeid( &*array )                    \
        )),                                                     \
    sizeof( array )/sizeof( *array )                            \
    )

7 éléments, l'appel de l'affichage...
Échec de l'Assertion: ( "COUNT_OF nécessite un véritable tableau comme argument", typeid( a ) != typeid ( &*) ), fichier runtime_detect ion.rpc, la ligne 16

Cette application a demandé l'Exécution d'y mettre fin d'une manière inhabituelle.
Veuillez communiquer avec la demande de l'équipe de soutien pour plus d'informations.

Le moteur d'exécution de détection d'erreur est mieux que pas de détection, mais ça gâche un peu temps processeur, et peut-être beaucoup plus de temps du programmeur. Mieux avec la détection de l' moment de la compilation! Et si vous êtes heureux de pas en charge les tableaux de types locaux avec C++98, alors vous pouvez le faire:

#include <stddef.h>

typedef ptrdiff_t   Size;

template< class Type, Size n >
Size countOf( Type (&)[n] ) { return n; }

#define COUNT_OF( array )       countOf( array )

La compilation de cette définition substitué dans le premier programme complet, avec g++, Je l'ai eu ...

M:\count> g++ compile_time_detection.cpp
compile_time_detection.rpc: In function 'void afficher(const int*)':
compile_time_detection.rpc:14: error: no matching function for call to 'countOf(const int*&)'

M:\count> _

Comment ça marche: le tableau est transmis par référence à l' countOf, et donc il n' pas de décroissance de pointeur vers le premier élément, et la fonction peut juste retour de la nombre d'éléments spécifié par le type.

Avec C++11 vous pouvez l'utiliser également pour les tableaux de type local, et c'est le type de coffre-fort C++ idiome pour trouver le nombre d'éléments d'un tableau.

Il y a un supplément de raffinement pour le cas où vous voulez un moment de la compilation constante, mais puisque c'est si rarement utilisées (si!) montrant que ferait perdre de l'espace.

73voto

FredOverflow Points 88201

La création de la matrice et de l'initialisation

Comme avec tout autre type d'objet C++, les tableaux peuvent être stockés directement dans les variables nommées (alors que la taille doit être une constante de compilation; C++ ne prend pas en charge VLAs), ou ils peuvent être stockées de manière anonyme sur le tas et accessible indirectement par l'intermédiaire de pointeurs (seulement ensuite que la taille de l'être calculée au moment de l'exécution).

Automatique des tableaux

Automatique de tableaux (tableaux de la vie "sur la pile") sont créés chaque fois que le flux de contrôle passe par la définition d'un non-locale statique de la variable de matrice:

void foo()
{
    int automatic_array[8];
}

L'initialisation est effectuée dans l'ordre croissant. Notez que les valeurs initiales dépendent du type d'élément T:

  • Si T est une GOUSSE (comme int dans l'exemple ci-dessus), pas d'initialisation a lieu.
  • Sinon, la valeur par défaut-constructeur de T initialise tous les éléments.
  • Si T fournit pas accessible par défaut du constructeur, le programme ne compile pas.

Sinon, les valeurs initiales peuvent être spécifiés explicitement dans l' initialiseur de tableau, une liste séparée par des virgules entouré par des accolades:

    int primes[8] = {2, 3, 5, 7, 11, 13, 17, 19};

Puisque dans ce cas le nombre d'éléments dans le tableau de l'initialiseur est égale à la taille de la matrice, en spécifiant la taille manuellement est redondante. Il peut être automatiquement déduit par le compilateur:

    int primes[] = {2, 3, 5, 7, 11, 13, 17, 19};   // size 8 is deduced

Il est également possible de spécifier la taille et la plus courte d'initialiseur de tableau:

    int fibonacci[50] = {0, 1, 1};   // 47 trailing zeros are deduced

Dans ce cas, les éléments restants sont remis à zéro. A noter que C++ permet à un tableau vide de l'initialiseur (tous les éléments sont initialisé à zéro), alors que C89 n'a pas (au moins une valeur est requise). Notez également que les initialiseurs de tableau ne peut être utilisé pour initialiser des tableaux; ils ne peuvent pas être utilisées plus tard dans les affectations.

Les tableaux statiques

Les tableaux statiques (tableaux vivants "dans le segment de données") sont des variables de tableau défini à l' static de mots clés et de variables de tableau à portée espace de noms ("variables globales"):

int global_static_array[8];

void foo()
{
    static int local_static_array[8];
}

(Notez que les variables à l'espace de noms de champ sont implicitement statique. L'ajout de l' static mot-clé à leur définition a un de complètement différent, obsolète sens.)

Voici comment les tableaux statiques se comporter différemment de l'automatique des tableaux:

  • Les tableaux statiques sans un initialiseur de tableau sont initialisé à zéro avant tout autre potentiel de l'initialisation.
  • Statique POD tableaux sont initialisés exactement une fois, et les valeurs initiales sont généralement cuits dans l'exécutable, auquel cas il n'y a pas de coût d'initialisation au moment de l'exécution. Ce n'est pas toujours la plus efficace en terme d'espace de solution, cependant, et il n'est pas requis par la norme.
  • Statique non-POD tableaux sont initialisés la première fois que le flux de contrôle passe par le biais de leur définition. Dans le cas de locaux des tableaux statiques, qui peut ne jamais se produire si la fonction n'est jamais appelée.

(Aucun des ci-dessus est spécifique aux tableaux. Ces règles s'appliquent tout aussi bien à d'autres types d'objets statiques.)

Tableau de données des membres

Tableau de données membres sont créés lorsque leur propriétaire objet est créé. Malheureusement, C++03 fournit pas de moyens pour initialiser les tableaux dans l' initialiseur de membre de la liste, de sorte que l'initialisation doit être truquées avec des affectations:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        primes[0] = 2;
        primes[1] = 3;
        primes[2] = 5;
        // ...
    }
};

Alternativement, vous pouvez définir automatiquement un tableau dans le corps du constructeur et de copier les éléments sur:

class Foo
{
    int primes[8];

public:

    Foo()
    {
        int local_array[] = {2, 3, 5, 7, 11, 13, 17, 19};
        std::copy(local_array + 0, local_array + 8, primes + 0);
    }
};

Dans C++0x, les tableaux peuvent être initialisées dans l'initialiseur de membre de la liste grâce à l'initialisation uniforme:

class Foo
{
    int primes[8];

public:

    Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 }
    {
    }
};

C'est la seule solution qui fonctionne avec les types d'éléments qui n'ont pas de constructeur par défaut.

Les tableaux dynamiques

Tableaux dynamiques n'ont pas de nom, donc le seul moyen d'y accéder est par l'intermédiaire de pointeurs. Parce qu'ils n'ont pas de noms, je vais me référer à eux comme "anonyme tableaux" à partir de maintenant.

En C, anonyme les tableaux sont créés par malloc et les amis. En C++, anonyme les tableaux sont créés à l'aide de l' new T[size] de la syntaxe qui retourne un pointeur sur le premier élément d'un tableau anonyme:

std::size_t size = compute_size_at_runtime();
int* p = new int[size];

La suite de l'ASCII art montre la disposition de la mémoire si la taille est calculée comme 8 au moment de l'exécution:

             +---+---+---+---+---+---+---+---+
(anonymous)  |   |   |   |   |   |   |   |   |
             +---+---+---+---+---+---+---+---+
               ^
               |
               |
             +-|-+
          p: | | |                               int*
             +---+

Évidemment, anonyme tableaux nécessitent plus de mémoire que de tableaux nommés en raison de l'extra pointeur qui doivent être stockés séparément. (Il existe aussi une charge supplémentaire sur le free store.)

Notez qu'il n'existe pas de tableau de pointeur de désintégration qui se passe ici. Bien que l'évaluation de new int[size] n'en fait créer un tableau d'entiers, le résultat de l'expression new int[size] est déjà un pointeur vers un entier (le premier élément), pas un tableau d'entiers ou un pointeur vers un tableau d'entiers de taille inconnue. Ce serait impossible, parce que le système de type statique nécessite tableau des tailles des constantes de compilation. (Donc, je n'ai pas d'annoter les anonymes tableau avec le type statique de l'information dans l'image.)

Concernant les valeurs par défaut pour les éléments, anonyme tableaux se comportent de manière semblable à automatique des tableaux. Normalement, anonyme POD tableaux ne sont pas initialisés, mais il y a une syntaxe particulière qui déclenche la valeur d'initialisation:

int* p = new int[some_computed_size]();

(Notez le point à la paire de parenthèses à droite avant le point-virgule.) Encore une fois, C++0x simplifie les règles et permet de spécifier des valeurs initiales pour les tableaux grâce à l'initialisation uniforme:

int* p = new int[8] { 2, 3, 5, 7, 11, 13, 17, 19 };

Si vous vous êtes fait à l'aide d'un tableau anonyme, vous avez à réinjecter dans le système:

delete[] p;

Vous devez libérer chaque anonymes tableau exactement une fois et de ne jamais toucher à nouveau par la suite. Pas de le relâcher à tous les résultats dans une fuite de mémoire (ou, plus généralement, selon le type d'élément, une fuite de ressources), et en essayant de libérer plusieurs fois les résultats dans un comportement indéfini. À l'aide de la non-forme matricielle delete (ou free) au lieu de delete[] de libérer le tableau est aussi un comportement indéfini.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X