Des tableaux au niveau du type
Un type de pile est notée T[n]
où 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.)