¿Cómo uso las matrices en C ++?

C ++ hereda las matrices de C, donde se usan prácticamente en todas partes. C ++ proporciona abstracciones que son más fáciles de usar y menos propensas a errores ( std::vector desde C ++ 98 y std::array desde C ++ 11 ), por lo que la necesidad de matrices no surgen con la misma frecuencia que en C. Sin embargo, cuando lee código heredado o interactúa con una biblioteca escrita en C, debe tener una comprensión firme de cómo funcionan las matrices.

Esta pregunta frecuente se divide en cinco partes:

  1. matrices en el nivel de tipo y acceder a los elementos
  2. creación e inicialización de matriz
  3. asignación y paso de parámetros
  4. matrices multidimensionales y matrices de punteros
  5. errores comunes cuando se utilizan matrices

Si cree que falta algo importante en esta pregunta frecuente, escriba una respuesta y vincule aquí como parte adicional.

En el siguiente texto, “array” significa “C array”, no la plantilla de clase std::array . Se asume el conocimiento básico de la syntax del declarante C. Tenga en cuenta que el uso manual de new y delete como se muestra a continuación es extremadamente peligroso a pesar de las excepciones, pero ese es el tema de otras preguntas frecuentes .

(Nota: Esto debe ser una entrada a las preguntas frecuentes de C ++ de Stack Overflow . Si desea criticar la idea de proporcionar una pregunta frecuente en este formulario, entonces la publicación en meta que inició todo esto sería el lugar para hacerlo). esa pregunta se monitorea en la sala de chat de C ++ , donde la idea de las preguntas frecuentes comenzó en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se les ocurrió la idea).

Arrays en el nivel de tipo

Un tipo de matriz se denota como T[n] donde T es el tipo de elemento n es un tamaño positivo, la cantidad de elementos en la matriz. El tipo de matriz es un tipo de producto del tipo de elemento y el tamaño. Si uno o ambos de esos ingredientes difieren, obtienes un tipo distinto:

 #include  static_assert(!std::is_same::value, "distinct element type"); static_assert(!std::is_same::value, "distinct size"); 

Tenga en cuenta que el tamaño es parte del tipo, es decir, los tipos de matriz de diferentes tamaños son tipos incompatibles que no tienen absolutamente nada que ver entre sí. sizeof(T[n]) es equivalente a n * sizeof(T) .

Desintegración de matriz a punta

La única “conexión” entre T[n] y T[m] es que ambos tipos se pueden convertir implícitamente a T* , y el resultado de esta conversión es un puntero al primer elemento de la matriz. Es decir, en cualquier lugar donde se requiera una T* , puede proporcionar una T[n] , y el comstackdor proporcionará en silencio ese puntero:

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

Esta conversión se conoce como “decaimiento de matriz a puntero” y es una gran fuente de confusión. El tamaño de la matriz se pierde en este proceso, ya que ya no es parte del tipo ( T* ). Pro: Olvidar el tamaño de una matriz en el nivel de tipo permite que un puntero apunte al primer elemento de una matriz de cualquier tamaño. Con: dado un puntero al primer elemento (o cualquier otro) de una matriz, no hay manera de detectar qué tan grande es esa matriz o hacia dónde señala exactamente el puntero en relación con los límites de la matriz. Los indicadores son extremadamente estúpidos .

Las matrices no son punteros

El comstackdor generará silenciosamente un puntero al primer elemento de una matriz siempre que se considere útil, es decir, siempre que una operación falle en una matriz pero tenga éxito en un puntero. Esta conversión de matriz a puntero es trivial, ya que el valor del puntero resultante es simplemente la dirección de la matriz. Tenga en cuenta que el puntero no se almacena como parte de la matriz en sí (ni en ningún otro lugar de la memoria). Una matriz no es un puntero.

 static_assert(!std::is_same::value, "an array is not a pointer"); 

Un contexto importante en el que una matriz no se descompone en un puntero a su primer elemento es cuando se le aplica el operador & . En ese caso, el operador & arroja un puntero a toda la matriz, no solo un puntero a su primer elemento. Aunque en ese caso los valores (las direcciones) son los mismos, un puntero al primer elemento de una matriz y un puntero a toda la matriz son tipos completamente distintos:

 static_assert(!std::is_same::value, "distinct element type"); 

El siguiente arte ASCII explica esta distinción:

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

Observe cómo el puntero al primer elemento solo apunta a un entero único (representado como un pequeño recuadro), mientras que el puntero al conjunto completo apunta a una matriz de 8 enteros (representada como un recuadro grande).

La misma situación se presenta en las clases y es quizás más obvio. 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 tipos completamente distintos.

Si no está familiarizado con la syntax del declarante C, los paréntesis en el tipo int(*)[8] son esenciales:

  • int(*)[8] es un puntero a una matriz de 8 enteros.
  • int*[8] es una matriz de 8 punteros, cada elemento de tipo int* .

Acceso a elementos

C ++ proporciona dos variaciones sintácticas para acceder a elementos individuales de una matriz. Ninguno de ellos es superior al otro, y debe familiarizarse con ambos.

Aritmética del puntero

Dado un puntero p al primer elemento de una matriz, la expresión p+i arroja un puntero al elemento i-ésimo de la matriz. Al desreferenciar ese puntero después, se puede acceder a elementos individuales:

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

Si x denota una matriz , la disminución de la matriz al puntero se activará, porque agregar una matriz y un entero no tiene sentido (no hay operación positiva en las matrices), pero tiene sentido agregar un puntero y un entero:

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

(Tenga en cuenta que el puntero generado implícitamente no tiene nombre, así que escribí x+0 para identificarlo).

Si, por otro lado, x denota un puntero al primer elemento (o cualquier otro) de una matriz, entonces la desintegración de matriz a punta no es necesaria, porque el puntero sobre el cual voy a agregar ya existe:

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

Tenga en cuenta que en el caso representado, x es una variable de puntero (discernible por el cuadro pequeño al lado de x ), pero podría ser el resultado de una función que devuelve un puntero (o cualquier otra expresión de tipo T* ).

Operador de indexación

Como la syntax *(x+i) es un poco torpe, C ++ proporciona la syntax alternativa x[i] :

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

Debido al hecho de que la adición es conmutativa, el siguiente código hace exactamente lo mismo:

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

La definición del operador de indexación conduce a la siguiente equivalencia interesante:

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

Sin embargo, &x[0] generalmente no es equivalente a x . El primero es un puntero, el último una matriz. Solo cuando el contexto desencadena la desintegración de matriz a punta se pueden usar x y &x[0] 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 comstackdor detecta una asignación de un puntero a un puntero, que tiene éxito trivial. En la segunda línea, detecta una asignación de una matriz a un puntero. Como esto no tiene sentido (pero el puntero a la asignación del puntero tiene sentido), la desintegración de matriz a puntero se activa como siempre.

Rangos

Una matriz de tipo T[n] tiene n elementos, indexados de 0 a n-1 ; no hay elemento n . Y, sin embargo, para admitir rangos semiabiertos (donde el comienzo es inclusivo y el final es exclusivo ), C ++ permite el cálculo de un puntero al elemento n-ésimo (inexistente), pero es ilegal desreferenciar ese puntero:

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

Por ejemplo, si desea ordenar una matriz, ambas cosas funcionarían 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 el segundo argumento ya que esto es equivalente a &*(x+n) , y la subexpresión *(x+n) invoca técnicamente un comportamiento indefinido en C ++ (pero no en C99 )

También tenga en cuenta que simplemente podría proporcionar x como primer argumento. Eso es un poco demasiado escueto para mi gusto, y también hace que la deducción del argumento de la plantilla sea un poco más difícil para el comstackdor, porque en ese caso el primer argumento es una matriz pero el segundo argumento es un puntero. (Nuevamente, la degradación de matriz a punta se activa).

Los progtwigdores suelen confundir matrices multidimensionales con matrices de punteros.

Arrays multidimensionales

La mayoría de los progtwigdores están familiarizados con las matrices multidimensionales nombradas, pero muchos desconocen el hecho de que la matriz multidimensional también se puede crear de forma anónima. Las matrices multidimensionales a menudo se denominan “matrices de matrices” o “matrices multidimensionales verdaderas “.

Nombrado matrices multidimensionales

Al usar matrices multidimensionales con nombre, todas las dimensiones deben conocerse en tiempo de comstackción:

 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 

Así es como se ve una matriz multidimensional en la memoria:

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

Tenga en cuenta que las cuadrículas 2D como las anteriores son meramente visualizaciones útiles. Desde el punto de vista de C ++, la memoria es una secuencia “plana” de bytes. Los elementos de una matriz multidimensional se almacenan en orden de fila mayor. Es decir, connect_four[0][6] y connect_four[1][0] son vecinos en la memoria. De hecho, connect_four[0][7] y connect_four[1][0] denotan el mismo elemento. Esto significa que puede tomar matrices multidimensionales y tratarlas como matrices grandes y unidimensionales:

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

Conjuntos multidimensionales anónimos

Con matrices multidimensionales anónimas, todas las dimensiones, excepto la primera, deben conocerse en tiempo de comstackción:

 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 

Así es como se ve una matriz multidimensional anónima en la memoria:

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

Tenga en cuenta que la matriz en sí todavía se asigna como un único bloque en la memoria.

Matrices de punteros

Puede superar la restricción del ancho fijo introduciendo otro nivel de indirección.

Conjuntos de punteros con nombre

Aquí hay una matriz nombrada de cinco punteros que se inicializan con matrices anónimas de diferentes longitudes:

 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]; } 

Y así es como se ve en la memoria:

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

Dado que cada línea se asigna individualmente ahora, la visualización de matrices 2D como matrices 1D ya no funciona.

Conjuntos anónimos de punteros

Aquí hay una matriz anónima de 5 (o cualquier otro número de punteros) que se inicializan con matrices anónimas de diferentes longitudes:

 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[] ! 

Y así es como se ve en la memoria:

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

Conversiones

La desintegración de matriz a puntero se extiende naturalmente a las matrices de matrices y matrices de punteros:

 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; 

Sin embargo, no hay conversión implícita de T[h][w] a T** . Si existiera dicha conversión implícita, el resultado sería un puntero al primer elemento de una matriz de h punteros a T (cada uno apuntando al primer elemento de una línea en la matriz 2D original), pero esa matriz de puntero no existe en cualquier lugar de la memoria todavía. Si desea una conversión de este tipo, debe crear y completar manualmente la matriz de puntero requerida:

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

Tenga en cuenta que esto genera una vista de la matriz multidimensional original. Si necesita una copia, debe crear matrices adicionales y copiar los datos usted mismo:

 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; 

Asignación

Sin ningún motivo en particular, las matrices no se pueden asignar entre sí. Use std::copy lugar:

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

Esto es más flexible de lo que podría proporcionar la verdadera asignación de matriz porque es posible copiar sectores de matrices más grandes en matrices más pequeñas. std::copy generalmente está especializado para tipos primitivos para dar el máximo rendimiento. Es poco probable que std::memcpy mejor rendimiento. Si tiene dudas, mida.

Aunque no puede asignar matrices directamente, puede asignar estructuras y clases que contengan miembros de matriz. Esto se debe a que los miembros de la matriz son copiados en forma de miembro por el operador de asignación que el comstackdor proporciona como predeterminado. Si define manualmente el operador de asignación para sus propios tipos de estructura o clase, debe recurrir a la copia manual para los miembros de la matriz.

Paso de parámetros

Las matrices no se pueden pasar por valor. Puede pasarlos por puntero o por referencia.

Pase por el puntero

Como las matrices en sí mismas no se pueden pasar por valor, normalmente un puntero a su primer elemento se pasa por valor. Esto a menudo se llama “pasar por el puntero”. Como el tamaño de la matriz no se puede recuperar a través de ese puntero, debe pasar un segundo parámetro que indique el tamaño de la matriz (la solución C clásica) o un segundo puntero después del último elemento de la matriz (la solución del iterador C ++) :

 #include  #include  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); } 

Como alternativa sintáctica, también puede declarar parámetros como T p[] , y significa exactamente lo mismo que T* p en el contexto de listas de parámetros solamente :

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

Puede pensar que el comstackdor reescribe T p[] a T *p en el contexto de listas de parámetros . Esta regla especial es en parte responsable de toda la confusión sobre matrices y punteros. En cualquier otro contexto, declarar algo como una matriz o como un puntero hace una gran diferencia.

Desafortunadamente, también puede proporcionar un tamaño en un parámetro de matriz que el comstackdor ignora silenciosamente. Es decir, las tres firmas siguientes son exactamente equivalentes, como lo indican los compiler errors:

 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 

Pase por referencia

Las matrices también se pueden pasar por referencia:

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

En este caso, el tamaño de la matriz es significativo. Como la escritura de una función que solo acepta matrices de exactamente 8 elementos es de poca utilidad, los progtwigdores suelen escribir funciones como plantillas:

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

Tenga en cuenta que solo puede llamar a dicha plantilla de función con una matriz real de enteros, no con un puntero a un entero. El tamaño de la matriz se deduce automáticamente, y para cada tamaño n , se crea una instancia de una función diferente de la plantilla. También puede escribir plantillas de funciones bastante útiles que se resumen tanto del tipo de elemento como del tamaño.

Creación e inicialización de matriz

Como con cualquier otro tipo de objeto C ++, las matrices pueden almacenarse directamente en variables con nombre (entonces el tamaño debe ser una constante en tiempo de comstackción; C ++ no admite VLA ), o pueden almacenarse de forma anónima en el montón y accederse indirectamente a través de punteros (solo entonces se puede calcular el tamaño en tiempo de ejecución).

Matrices automáticas

Las matrices automáticas (matrices que viven “en la stack”) se crean cada vez que el flujo de control pasa a través de la definición de una variable de matriz local no estática:

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

La inicialización se realiza en orden ascendente. Tenga en cuenta que los valores iniciales dependen del elemento tipo T :

  • Si T es un POD (como int en el ejemplo anterior), no tiene lugar ninguna inicialización.
  • De lo contrario, el constructor predeterminado de T inicializa todos los elementos.
  • Si T no proporciona ningún constructor por defecto accesible, el progtwig no comstack.

Alternativamente, los valores iniciales se pueden especificar explícitamente en el inicializador de la matriz , una lista separada por comas rodeada de llaves:

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

Como en este caso el número de elementos en el inicializador de la matriz es igual al tamaño de la matriz, especificar el tamaño manualmente es redundante. Puede ser deducido automáticamente por el comstackdor:

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

También es posible especificar el tamaño y proporcionar un inicializador de matriz más corto:

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

En ese caso, los elementos restantes se inicializan en cero . Tenga en cuenta que C ++ permite un inicializador de matriz vacío (todos los elementos tienen cero inicialización), mientras que C89 no (se requiere al menos un valor). También tenga en cuenta que los inicializadores de matriz solo se pueden utilizar para inicializar matrices; no pueden usarse luego en asignaciones.

Matrices estáticas

Las matrices estáticas (matrices que viven “en el segmento de datos”) son variables de matriz locales definidas con la palabra clave static y las variables de matriz en el ámbito del espacio de nombres (“variables globales”):

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

(Tenga en cuenta que las variables en el ámbito del espacio de nombres son implícitamente estáticas. Agregar la palabra clave static a su definición tiene un significado completamente diferente y obsoleto ).

Aquí se muestra cómo las matrices estáticas se comportan de forma diferente a las matrices automáticas:

  • Las matrices estáticas sin un inicializador de matriz se inicializan a cero antes de cualquier inicialización potencial adicional.
  • Las matrices de POD estáticas se inicializan exactamente una vez , y los valores iniciales normalmente se procesan en el ejecutable, en cuyo caso no hay costo de inicialización en el tiempo de ejecución. Sin embargo, esta no siempre es la solución más eficiente en términos de espacio, y no es requerida por el estándar.
  • Los arreglos estáticos que no son POD se inicializan la primera vez que el flujo de control pasa a través de su definición. En el caso de las matrices estáticas locales, eso nunca puede suceder si la función nunca se llama.

(Ninguno de los anteriores es específico de las matrices. Estas reglas se aplican igualmente a otros tipos de objetos estáticos).

Miembros de datos de matriz

Los miembros de datos de matriz se crean cuando se crea su objeto propietario. Desafortunadamente, C ++ 03 no proporciona medios para inicializar matrices en la lista de inicializadores de miembros , por lo que la inicialización debe ser simulada con asignaciones:

 class Foo { int primes[8]; public: Foo() { primes[0] = 2; primes[1] = 3; primes[2] = 5; // ... } }; 

Alternativamente, puede definir una matriz automática en el cuerpo constructor y copiar los elementos sobre:

 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); } }; 

En C ++ 0x, las matrices se pueden inicializar en la lista de inicializadores de miembros gracias a la inicialización uniforme :

 class Foo { int primes[8]; public: Foo() : primes { 2, 3, 5, 7, 11, 13, 17, 19 } { } }; 

Esta es la única solución que funciona con tipos de elementos que no tienen un constructor predeterminado.

Arrays dynamics

Las matrices dinámicas no tienen nombres, por lo tanto, el único medio para acceder a ellas es a través de punteros. Como no tienen nombres, a partir de ahora me referiré a ellos como “matrices anónimas”.

En C, las matrices anónimas se crean a través de malloc y amigos. En C ++, las matrices anónimas se crean utilizando la new T[size] syntax new T[size] que devuelve un puntero al primer elemento de una matriz anónima:

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

La siguiente ilustración ASCII muestra el diseño de la memoria si el tamaño se calcula como 8 en tiempo de ejecución:

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

Obviamente, las matrices anónimas requieren más memoria que las matrices con nombre debido al puntero adicional que debe almacenarse por separado. (También hay algunos gastos generales adicionales en la tienda gratuita).

Tenga en cuenta que no hay decaimiento de matriz a puntero aquí. Aunque la evaluación de una new int[size] sí crea una matriz de enteros, el resultado de la expresión new int[size] ya es un puntero a un entero (el primer elemento), no una matriz de enteros o un puntero a una matriz de enteros de tamaño desconocido. Eso sería imposible, porque el sistema de tipo estático requiere tamaños de matriz para ser constantes de tiempo de comstackción. (Por lo tanto, no anoté la matriz anónima con información de tipo estático en la imagen).

Con respecto a los valores predeterminados para los elementos, las matrices anónimas se comportan de manera similar a las matrices automáticas. Normalmente, las matrices POD anónimas no se inicializan, pero hay una syntax especial que desencadena la inicialización del valor:

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

(Tenga en cuenta el par de paréntesis final justo antes del punto y coma.) De nuevo, C ++ 0x simplifica las reglas y permite especificar valores iniciales para matrices anónimas gracias a la inicialización uniforme:

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

Si ha terminado de usar una matriz anónima, tiene que volver a liberarla en el sistema:

 delete[] p; 

Debe liberar cada matriz anónima exactamente una vez y luego nunca volver a tocarla. No liberarlo en absoluto da como resultado una pérdida de memoria (o más generalmente, dependiendo del tipo de elemento, una fuga de recursos), y tratar de liberarlo varias veces da como resultado un comportamiento indefinido. El uso de la forma no-matriz delete (o free ) en lugar de delete[] para liberar la matriz también es un comportamiento indefinido .

5. Errores comunes al usar arreglos.

5.1 Peligro: confianza en el enlace inseguro.

De acuerdo, se le ha dicho, o se ha enterado, que las variables globales (variables de scope del espacio de nombres a las que se puede acceder fuera de la unidad de traducción) son Evil ™. ¿Pero sabías cuán verdaderamente Evil ™ son? Considere el siguiente progtwig, que consta de dos archivos [main.cpp] y [numbers.cpp]:

 // [main.cpp] #include  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}; 

En Windows 7, comstack y enlaza bien con MinGW g ++ 4.4.1 y Visual C ++ 10.0.

Dado que los tipos no coinciden, el progtwig se bloquea cuando lo ejecuta.

El cuadro de diálogo de bloqueo de Windows 7

Explicación formal: el progtwig tiene Undefined Behavior (UB), y en lugar de colgarse, puede colgar, o quizás no hacer nada, o puede enviar correos electrónicos amenazantes a los presidentes de los EE. UU., Rusia, India, China y Suiza, y hacen que Nasal Daemons salga volando de tu nariz.

Explicación práctica: en main.cpp la matriz se trata como un puntero, ubicado en la misma dirección que la matriz. Para el ejecutable de 32 bits, esto significa que el primer valor int en la matriz se trata como un puntero. Es decir, en main.cpp la variable de numbers contiene, o parece contener, (int*)1 . Esto hace que el progtwig acceda a la memoria en la parte inferior del espacio de direcciones, que es convencionalmente reservado y que causa trampas. Resultado: obtiene un choque.

Los comstackdores tienen pleno derecho a no diagnosticar este error, porque C ++ 11 §3.5 / 10 dice, acerca del requisito de tipos compatibles para las declaraciones,

[N3290 §3.5 / 10]
Una violación de esta regla sobre la identidad de tipo no requiere un diagnóstico.

El mismo párrafo detalla la variación que está permitida:

... las declaraciones para un objeto de matriz pueden especificar tipos de matriz que difieren por la presencia o ausencia de una matriz principal encuadernada (8.3.4).

Esta variación permitida no incluye declarar un nombre como una matriz en una unidad de traducción, y como un puntero en otra unidad de traducción.

5.2 Peligro: hacer una optimización prematura ( memset y amigos).

Aún no escrito

5.3 Peligro: Usar la expresión C para obtener el número de elementos.

Con la experiencia C profunda, es natural escribir ...

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

Como una array desintegra al puntero al primer elemento donde sea necesario, la expresión sizeof(a)/sizeof(a[0]) también se puede escribir como sizeof(a)/sizeof(*a) . Significa lo mismo, y no importa cómo esté escrito, es la expresión C para encontrar los elementos numéricos de la matriz.

Peligro principal: la expresión C no es segura. Por ejemplo, el código ...

 #include  #define N_ITEMS( array ) (sizeof( array )/sizeof( *array )) void display( int const a[7] ) { int const n = N_ITEMS( 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", N_ITEMS( moohaha ) ); display( moohaha ); } 

pasa un puntero a N_ITEMS , y por lo tanto es muy probable que produzca un resultado incorrecto. Comstackdo como un ejecutable de 32 bits en Windows 7 produce ...

7 elementos, pantalla de llamada ...
1 elementos

  1. El comstackdor reescribe int const a[7] para simplemente int const a[] .
  2. El comstackdor reescribe int const a[] a int const* a .
  3. N_ITEMS se invoca por lo tanto con un puntero.
  4. Para un tamaño ejecutable de 32 bits sizeof(array) (tamaño de un puntero) es entonces 4.
  5. sizeof(*array) es equivalente a sizeof(int) , que para un ejecutable de 32 bits también es 4.

Para detectar este error en el tiempo de ejecución, puede hacer ...

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

7 elementos, pantalla de llamada ...
La aserción falló: ("N_ITEMS requiere una matriz real como argumento", typeid (a)! = Typeid (& * a)), archivo runtime_detect ion.cpp, línea 16

Esta aplicación ha solicitado que Runtime lo rescinda de forma inusual.
Por favor, póngase en contacto con el equipo de soporte de la aplicación para obtener más información.

La detección de errores de tiempo de ejecución es mejor que no detectarla, pero desperdicia un poco de tiempo de procesador, y tal vez mucho más tiempo de progtwigdor. ¡Mejor con detección en tiempo de comstackción! Y si está contento de no admitir matrices de tipos locales con C ++ 98, puede hacerlo:

 #include  typedef ptrdiff_t Size; template< class Type, Size n > Size n_items( Type (&)[n] ) { return n; } #define N_ITEMS( array ) n_items( array ) 

Al comstackr esta definición sustituida en el primer progtwig completo, con g ++, obtuve ...

M: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: En la función 'void display (const int *)':
compile_time_detection.cpp: 14: error: ninguna función de coincidencia para llamar a 'n_items (const int * &)'

M: \ count> _

Cómo funciona: la matriz se pasa por referencia a n_items , por lo que no decae a puntero al primer elemento, y la función puede devolver el número de elementos especificados por el tipo.

Con C ++ 11 puede usar esto también para matrices de tipo local, y es el tipo de lenguaje C ++ seguro para encontrar el número de elementos de una matriz.

5.4 C ++ 11 y C ++ 14 trampa: Usar una función de tamaño de matriz constexpr .

Con C ++ 11 y más tarde es natural, pero como verá peligroso !, para reemplazar la función C ++ 03

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

con

 using Size = ptrdiff_t; template< class Type, Size n > constexpr auto n_items( Type (&)[n] ) -> Size { return n; } 

donde el cambio significativo es el uso de constexpr , que permite que esta función produzca una constante de tiempo de comstackción .

Por ejemplo, en contraste con la función C ++ 03, dicha constante de tiempo de comstackción se puede usar para declarar una matriz del mismo tamaño que otra:

 // Example 1 void foo() { int const x[] = {3, 1, 4, 1, 5, 9, 2, 6, 5, 4}; constexpr Size n = n_items( x ); int y[n] = {}; // Using y here. } 

Pero considere este código usando la versión constexpr :

 // Example 2 template< class Collection > void foo( Collection const& c ) { constexpr int n = n_items( c ); // Not in C++14! // Use c here } auto main() -> int { int x[42]; foo( x ); } 

El escollo: desde julio de 2015, lo anterior comstack con MinGW-64 5.1.0 con -pedantic-errors y prueba con los comstackdores en línea en gcc.godbolt.org/ , también con clang 3.0 y clang 3.2, pero no con clang 3.3, 3.4.1, 3.5.0, 3.5.1, 3.6 (rc1) o 3.7 (experimental). E importante para la plataforma Windows, no se comstack con Visual C ++ 2015. La razón es una statement C ++ 11 / C ++ 14 sobre el uso de referencias en expresiones constexpr :

C ++ 11 C ++ 14 $ 5.19 / 2 novena carrera

Una expresión condicional e es una expresión constante central a menos que la evaluación de e , siguiendo las reglas de la máquina abstracta (1.9), evalúe una de las siguientes expresiones:

  • una expresión id que se refiere a una variable o miembro de datos del tipo de referencia a menos que la referencia tenga una inicialización precedente y
    • se inicializa con una expresión constante o
    • es un miembro de datos no estático de un objeto cuya vida útil comenzó en la evaluación de e;

Uno siempre puede escribir el más detallado

 // Example 3 -- limited using Size = ptrdiff_t; template< class Collection > void foo( Collection const& c ) { constexpr Size n = std::extent< decltype( c ) >::value; // Use c here } 

... pero esto falla cuando Collection no es una matriz en bruto.

Para tratar con colecciones que pueden ser no-matrices, se necesita la n_items de sobrecarga de una función n_items , pero también, para el tiempo de comstackción, se necesita una representación en tiempo de comstackción del tamaño de la matriz. Y la solución clásica C ++ 03, que también funciona bien en C ++ 11 y C ++ 14, es permitir que la función informe su resultado no como un valor sino a través de su tipo de resultado de función. Por ejemplo, así:

 // Example 4 - OK (not ideal, but portable and safe) #include  #include  using Size = ptrdiff_t; template< Size n > struct Size_carrier { char sizer[n]; }; template< class Type, Size n > auto static_n_items( Type (&)[n] ) -> Size_carrier; // No implementation, is used only at compile time. template< class Type, size_t n > // size_t for g++ auto static_n_items( std::array const& ) -> Size_carrier; // No implementation, is used only at compile time. #define STATIC_N_ITEMS( c ) \ static_cast( sizeof( static_n_items( c ).sizer ) ) template< class Collection > void foo( Collection const& c ) { constexpr Size n = STATIC_N_ITEMS( c ); // Use c here (void) c; } auto main() -> int { int x[42]; std::array y; foo( x ); foo( y ); } 

About the choice of return type for static_n_items : this code doesn't use std::integral_constant because with std::integral_constant the result is represented directly as a constexpr value, reintroducing the original problem. Instead of a Size_carrier class one can let the function directly return a reference to an array. However, not everybody is familiar with that syntax.

About the naming: part of this solution to the constexpr -invalid-due-to-reference problem is to make the choice of compile time constant explicit.

Hopefully the oops-there-was-a-reference-involved-in-your- constexpr issue will be fixed with C++17, but until then a macro like the STATIC_N_ITEMS above yields portability, eg to the clang and Visual C++ compilers, retaining type safety.

Related: macros do not respect scopes, so to avoid name collisions it can be a good idea to use a name prefix, eg MYLIB_STATIC_N_ITEMS .