Matrices en el nivel de tipo de
Un tipo de matriz se denota como T[n]
donde T
es el tipo de elemento y n
, es positivo el tamaño, el número de elementos en la matriz. El tipo de matriz es un tipo de producto de el tipo de elemento y el tamaño. Si uno o ambos de estos ingredientes diferentes, se obtiene un tipo distinto:
#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");
Tenga en cuenta que el tamaño es parte del tipo, esto es, la matriz de tipos de diferente tamaño son incompatibles tipos que no tienen absolutamente nada que ver uno con otro. sizeof(T[n])
es equivalente a n * sizeof(T)
.
Matriz-a-puntero de la caries
La única "relación" entre T[n]
y T[m]
es que ambos tipos pueden implícitamente ser convertidos a T*
, y el resultado de esta transformación es un puntero al primer elemento de la matriz. Es decir, en cualquier lugar de una T*
es necesario, puede proporcionar un T[n]
, y el compilador se silenciosamente proporcionar ese puntero:
+---+---+---+---+---+---+---+---+
the_actual_array: | | | | | | | | | int[8]
+---+---+---+---+---+---+---+---+
^
|
|
|
| pointer_to_the_first_element int*
Esta conversión se conoce como "matriz-a-puntero de la corrupción", y es una importante fuente de confusión. El tamaño de la matriz se pierde en este proceso, puesto que ya no forma parte del tipo (T*
). Pro: el Olvido del tamaño de una matriz en el nivel del tipo permite un puntero para que apunte al primer elemento de una matriz de cualquier tamaño. Con: Dado un puntero a la primera (o cualquier otro) de los elementos de una matriz, no hay manera de detectar cómo es de grande que la matriz es o dónde está exactamente el puntero que señala a la relativa a los límites de la matriz. Los punteros son extremadamente estúpido.
Matrices no son punteros
El compilador silenciosamente generar un puntero al primer elemento de una matriz, siempre que lo considere útil, que es, cada vez que una operación podría fallar en un array, pero tener éxito en un puntero. Esta conversión de la matriz de puntero es trivial, ya que el puntero de valor es simplemente la dirección de la matriz. Tenga en cuenta que el puntero no se almacena como parte de la propia matriz (o en cualquier otro lugar en la memoria). Un array es un puntero.
static_assert(!std::is_same<int[8], int*>::value, "an array is not a pointer");
Un importante contexto en el que una matriz no no decaer en un puntero a su primer elemento es cuando la &
operador es aplicado. En ese caso, la &
operador obtiene un puntero a la totalidad de la matriz, no sólo un puntero a su primer elemento. Aunque en ese caso, los valores (las direcciones) son el mismo, un puntero al primer elemento de un array y un puntero a la totalidad de la matriz son completamente distintos tipos:
static_assert(!std::is_same<int*, int(*)[8]>::value, "distinct element type");
Los siguientes arte ASCII explica esta distinción:
+-----------------------------------+
| +---+---+---+---+---+---+---+---+ |
+---> | | | | | | | | | | | int[8]
| | +---+---+---+---+---+---+---+---+ |
| +---^-------------------------------+
| |
| |
| |
| | pointer_to_the_first_element int*
|
| pointer_to_the_entire_array int(*)[8]
Nota cómo el puntero al primer elemento sólo apunta a un entero (representado como un pequeño recuadro), mientras que el puntero a la totalidad de la matriz de puntos de una matriz de 8 enteros (representado como un gran cuadro).
La misma situación se produce en las clases y es quizá más evidente. Un puntero a un objeto y un puntero a su primer miembro de datos tienen el mismo valor (la misma dirección), sin embargo, son completamente distintos tipos.
Si usted no está familiarizado con el C declarador de sintaxis, el paréntesis en el tipo int(*)[8]
son esenciales:
int(*)[8]
es un puntero a un array de 8 enteros.
int*[8]
es una matriz de 8 punteros, cada uno de los elemento del tipo int*
.
El acceso a los elementos
C++ dispone de dos variaciones sintácticas para acceder a los elementos individuales de una matriz.
Ninguno de ellos es superior al otro, y debe familiarizarse con ambos.
Aritmética de punteros
Dado un puntero p
para el primer elemento de un array, la expresión p+i
obtiene un puntero al i-ésimo elemento de la matriz. Por eliminar ese puntero después, uno se puede acceder a elementos individuales:
std::cout << *(x+3) << ", " << *(x+7) << std::endl;
Si x
denota una matriz, entonces la matriz-a-puntero caries patada, ya que la adición de una matriz y un entero es de sentido (no hay más que la operación en las matrices), pero la adición de un puntero y un entero tiene sentido:
+---+---+---+---+---+---+---+---+
x: | | | | | | | | | int[8]
+---+---+---+---+---+---+---+---+
^ ^ ^
| | |
| | |
| | |
x+0 | x+3 | x+7 | int*
(Tenga en cuenta que implícitamente se genera puntero no tiene nombre, así que escribí x+0
con el fin de identificar.)
Si, por otro lado, x
denota un puntero a la primera (o cualquier otro) de los elementos de una matriz, entonces la matriz-a-puntero de la caries no es necesario, porque el puntero en que i
va a ser añadido ya existe:
+---+---+---+---+---+---+---+---+
| | | | | | | | | int[8]
+---+---+---+---+---+---+---+---+
^ ^ ^
| | |
| | |
+-|-+ | |
x: | | | x+3 | x+7 | int*
+---+
Tenga en cuenta que en la representación de caso, x
es un puntero a variable (perceptible por la pequeña caja junto a x
), pero también puede ser el resultado de una función que devuelve un puntero (o cualquier otra expresión de tipo T*
).
Operador de indexación
Ya que la sintaxis *(x+i)
es un poco torpe, C++ proporciona la sintaxis alternativa x[i]
:
std::cout << x[3] << ", " << x[7] << std::endl;
Debido al hecho de que la suma es conmutativa, el siguiente código hace exactamente lo mismo:
std::cout << 3[x] << ", " << 7[x] << std::endl;
La definición de la indexación operador conduce a la siguiente interesante de equivalencia:
&x[i] == &*(x+i) == x+i
Sin embargo, &x[0]
generalmente no equivalente a x
. El primero es un puntero, el último de una matriz. Sólo cuando el contexto de los desencadenantes de la matriz-a-puntero desintegrarse x
y &x[0]
utilizarse de manera intercambiable. Por ejemplo:
T* p = &array[0]; // rewritten as &*(array+0), decay happens due to the addition
T* q = array; // decay happens due to the assignment
En la primera línea, el compilador detecta una asignación de un puntero a un puntero, que trivialmente se realiza correctamente. En la segunda línea, se detecta una asignación a partir de una matriz a un puntero. Ya que esto es de sentido (pero puntero a puntero de asignación de sentido), la matriz-a-puntero de caries activa como de costumbre.
Rangos de
Una matriz de tipo T[n]
ha n
elementos, indexado desde 0
a n-1
; no hay ningún elemento n
. Y, sin embargo, para apoyar a la mitad-abra rangos (donde el principio es inclusiva y el final es exclusivo), C++ permite el cálculo de un puntero a la (inexistente) de n-ésimo elemento, pero es ilegal para eliminar la referencia que el puntero:
+---+---+---+---+---+---+---+---+....
x: | | | | | | | | | . int[8]
+---+---+---+---+---+---+---+---+....
^ ^
| |
| |
| |
x+0 | x+8 | int*
Por ejemplo, si desea ordenar una matriz, ambos de los siguientes podría funcionar igual de bien:
std::sort(x + 0, x + n);
std::sort(&x[0], &x[0] + n);
Tenga en cuenta que es ilegal proporcionar &x[n]
, como segundo argumento, ya que esto es equivalente a &*(x+n)
, y la sub-expresión *(x+n)
técnicamente invoca un comportamiento indefinido en C++ (pero no en C99).
También tenga en cuenta que usted simplemente podría proveer x
como primer argumento. Que es un poco demasiado breve para mi gusto, y también hace que el argumento de plantilla de la deducción de un poco más difícil para el compilador, porque en ese caso el primer argumento es un array, pero el segundo argumento es un puntero. (De nuevo, la matriz-a-puntero de caries activa.)