¿Cómo uso arrays en C++?


C++ heredó 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<T> desde C++98 y std::array<T, n> desde C++11), por lo que la necesidad de matrices no surge tan a menudo como en C. Sin embargo, cuando lee código heredado o interactúa con una biblioteca escrita en C, debe tener un firme conocimiento de cómo funcionan las matrices.

Esta FAQ se divide en cinco partes:

  1. matrices en el tipo nivel y elementos de acceso
  2. creación e inicialización de matrices
  3. asignación y paso de parámetros
  4. arrays multidimensionales y arrays de punteros
  5. trampas comunes al usar arrays

Si sientes que falta algo importante en esta FAQ, escribe una respuesta y enlázala aquí como una 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 sintaxis del declarador C. Tenga en cuenta que el uso manual de new y delete como se muestra a continuación es extremadamente peligroso frente a las excepciones, pero ese es el tema de otra FAQ.

(Nota: Esta es una entrada para Stack Overflow's C++ FAQ. Si quieres criticar la idea de proporcionar un FAQ en esta forma, entonces la publicación en meta que comenzó todo esto sería el lugar para hacerlo. Respuestas a eso las preguntas se monitorean en la sala de chat de C++ , donde la idea de preguntas frecuentes comenzó en primer lugar, por lo que es muy probable que su respuesta sea leída por aquellos a quienes se le ocurrió la idea.)

Author: Community, 2011-01-27

5 answers

Matrices en el nivel de tipo

Un tipo de matriz se denota como T[n] donde T es el tipo de elemento y n es un tamaño positivo, el número 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 <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, es decir, los tipos de matriz de diferente tamaño son tipos incompatibles que no tienen absolutamente nada que ver el uno con el otro. sizeof(T[n]) es equivalente a n * sizeof(T).

Decaimiento de matriz a puntero

La única "conexión" entre T[n] y T[m] es que ambos tipos pueden implícitamente ser convertidos 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 un T*, puede proporcionar un T[n], y el compilador proporcionará silenciosamente 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 fuente importante 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 a cualquier otro) de un array, no hay manera de detectar cuán grande es ese array o a dónde apunta exactamente el puntero en relación con los límites del array. Los punteros son extremadamente estúpido.

Los arrays no son punteros

El compilador generará silenciosamente un puntero al primer elemento de una matriz siempre que se considere útil, es decir, cuando 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 resultante del puntero es simplemente la dirección de la matriz. Tenga en cuenta que el puntero es no almacenado como parte de la matriz en sí (o en cualquier otro lugar de la memoria). Un array no es un puntero.

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

Un contexto importante en el que un array hace no decaer en un puntero a su primer elemento es cuando se le aplica el operador &. En ese caso, el operador &produce 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 un array y un puntero a todo el array son tipos completamente distintos:

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

El siguiente arte ASCII explica esta distinción: {[62]]}

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

Tenga en cuenta cómo el puntero al primer elemento solo apunta a un entero único (representado como una caja pequeña), mientras que el puntero a toda la matriz apunta a una matriz de 8 enteros (representado como una caja grande).

La misma situación surge en las clases y es quizás más obvia. Un puntero a un objeto y un puntero a su primer miembro de datos tienen el mismo valor (el misma dirección), sin embargo, son tipos completamente distintos.

Si no está familiarizado con la sintaxis del declarador 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 deberías familiarizarte con ambos.

Aritmética de punteros

Dado un puntero p al primer elemento de un array, la expresión p+i produce un puntero al i-ésimo elemento del array. Al desreferenciar ese puntero después, uno puede acceder a elementos individuales:

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

Si x denota un array , entonces el decaimiento array-to-pointer se activará, porque agregar un array y un entero no tiene sentido (no hay ninguna operación plus en los arrays), pero agregar un puntero y un entero tiene sentido:

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

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

Si, por otro lado, x denota un puntero al primer elemento (o a cualquier otro) de un array, entonces no es necesario el decaimiento array-to-pointer, porque el puntero sobre el que i se va a añadir ya existe:

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

Tenga en cuenta que en el caso representado, x es un puntero variable (discernible por la pequeña caja junto a x), pero también 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

Dado 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:{[62]]}

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] es generalmente no equivalente a x. El primero es un puntero, el segundo un array. Solo cuando el contexto desencadena la desintegración de matriz a puntero se pueden usar x y &x[0] indistintamente. 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 tiene éxito. En la segunda línea, detecta una asignación de un array a un puntero. Dado que esto no tiene sentido (pero puntero a la asignación de puntero tiene sentido), el decaimiento de matriz a puntero se inicia como de costumbre.

Rangos

Una matriz de tipo T[n] tiene elementos n, indexados de 0 a n-1; no hay elemento n. Y sin embargo, para soportar rangos medio abiertos (donde el principio 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 un array, ambos de los siguientes funcionarían igualmente 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)invoca técnicamente 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 conciso para mi gusto, y también hace deducción de argumento de plantilla un poco más difícil para el compilador, porque en ese caso el primer argumento es una matriz, pero el segundo argumento es un puntero. (De nuevo, el decaimiento array-to-pointer se activa.)

 267
Author: fredoverflow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:26:24

Los programadores a menudo confunden arrays multidimensionales con arrays de punteros.

Arreglos multidimensionales

La mayoría de los programadores están familiarizados con los arrays multidimensionales nombrados, pero muchos desconocen el hecho de que los arrays multidimensionales también se pueden crear de forma anónima. Los arrays multidimensionales a menudo se conocen como" arrays de arrays "o"true arrays multidimensionales".

Arreglos multidimensionales con nombre

Cuando se usa named multidimensional arrays, todas las dimensiones deben conocerse en tiempo de compilació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 un array multidimensional en 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 mayor de fila. 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);

Matrices multidimensionales anónimas

Con matrices multidimensionales anónimas, todas las dimensiones excepto la primera deben conocerse en tiempo de compilació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 solo bloque en la memoria.

Matrices de punteros

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

Matrices de punteros con nombre

Aquí hay una matriz con nombre 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, ver matrices 2D como matrices 1D ya no funciona.

Matrices anónimas 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

El decaimiento de matriz a puntero se extiende naturalmente a 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 tal conversión implícita existiera, 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 ninguna parte de la memoria todavía. Si desea una conversión de este tipo, debe crear y rellenar 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 en su lugar, 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;
 128
Author: fredoverflow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-05-28 09:20:29

Asignación

Por ninguna razón en particular, los arrays no se pueden asignar entre sí. Use std::copy en su lugar:

#include <algorithm>

// ...

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 que lo que la asignación de matrices verdaderas podría proporcionar porque es posible copiar sectores de matrices más grandes en matrices más pequeñas. std::copy se especializa generalmente para los tipos primitivos para dar el máximo rendimiento. Es poco probable que std::memcpy funcione mejor. En caso de duda, mida.

Aunque no puede asignar matrices directamente, puedeasignar estructuras y clases que contienen miembros del array. Esto se debe a que los miembros del array son copiados memberwise por el operador de asignación que es proporcionado por defecto por el compilador. Si define el operador de asignación manualmente para sus propios tipos de estructura o clase, debe recurrir a la copia manual para los miembros de la matriz.

Paso de parámetros

Los arrays no se pueden pasar por valor. Puede pasarlos por puntero o por referencia.

Pasar por puntero

Dado que los arrays mismos no se pueden pasar por valor, normalmente un puntero a su primer elemento se pasa por valor en su lugar. Esto a menudo se llama "pasar por puntero". Dado que el tamaño de la matriz no es recuperable a través de ese puntero, debe pasar un segundo parámetro que indica el tamaño de la matriz (la solución clásica de C) o un segundo puntero que apunta después del último elemento de la matriz (la solución iteradora de C++):

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

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

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

Puede pensar en el compilador como reescribir T p[] para T *p en el contexto de las listas de parámetros solo . Esta regla especial es en parte responsable de toda la confusión acerca de los arrays y los punteros. En cualquier otro contexto, declarar algo como una matriz o como un puntero hace un enorme diferencia.

Desafortunadamente, también puede proporcionar un tamaño en un parámetro de matriz que es ignorado silenciosamente por el compilador. Es decir, las siguientes tres firmas son exactamente equivalentes, como lo indican los errores del compilador:

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

Los arrays 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 del array es significativo. Dado que escribir una función que solo acepta matrices de exactamente 8 elementos es de poca utilidad, los programadores normalmente escribe tales funciones como plantillas:

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

Tenga en cuenta que solo puede llamar a una plantilla de función con una matriz real de enteros, no con un puntero a un entero. El tamaño de la matriz se infiere 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 función bastante útiles que abstraigan tanto del tipo de elemento como del tamaño.

 82
Author: fredoverflow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:47:20

Creación e inicialización de matrices

Al igual que con cualquier otro tipo de objeto C++, los arrays se pueden almacenar directamente en variables con nombre (entonces el tamaño debe ser una constante en tiempo de compilación; C++ no soporta VLAs), o se pueden almacenar de forma anónima en el montón y acceder indirectamente a través de punteros (solo entonces se puede calcular el tamaño en tiempo de ejecución).

Matrices automáticas

Los arrays automáticos (arrays que viven "en la pila") 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 tipo de elemento T:

  • Si T es un POD (como int en el ejemplo anterior), no se lleva a cabo la inicialización.
  • De lo contrario, el constructor por defecto de T inicializa todos los elementos.
  • Si T no proporciona ningún constructor predeterminado accesible, el programa lo hace no compilar.

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

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

Dado que en este caso el número de elementos en el inicializador del array es igual al tamaño del array, especificar el tamaño manualmente es redundante. Puede ser deducido automáticamente por el compilador:

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

También es posible especificar el tamaño y proporcionar una matriz más corta inicializador:

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

En ese caso, los elementos restantes son cero-inicializados. Tenga en cuenta que C++ permite un inicializador de matriz vacía (todos los elementos se inicializan a cero), mientras que C89 no lo hace (se requiere al menos un valor). También tenga en cuenta que los inicializadores de matriz solo se pueden usar para inicializar matrices; no se pueden usar más tarde en asignaciones.

Matrices estáticas

Los arrays estáticos (arrays que viven "en el segmento de datos") son variables de array locales definidas con la palabra clave static y 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.)

Así es como los arrays estáticos se comportan de manera diferente a los arrays automáticos:

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

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

Miembros de datos de matriz

Los miembros de datos del array se crean cuando se crea su objeto propietario. Desafortunadamente, C++03 no proporciona ningún medio para inicializar matrices en la lista de inicializadores de miembros , por lo que la inicialización debe falsificarse con asignaciones:

class Foo
{
    int primes[8];

public:

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

Alternativamente, puede definir un array automático en el cuerpo del constructor y copia 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, los arrays pueden inicializarse en la lista de inicializadores de miembros gracias a 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 constructor predeterminado.

Matrices dinámicas

Los arrays dinámicos no tienen nombres, por lo tanto, el único medio de acceder a ellos es a través de punteros. Porque no tienen nombres, me referiré a ellos como "matrices anónimas" de ahora en adelante.

En C, los arrays anónimos se crean a través de malloc y amigos. En C++, los arrays anónimos se crean usando la sintaxis new T[size] que devuelve un puntero al primer elemento de un array anónimo:

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

El siguiente arte ASCII representa el diseño de memoria si el tamaño se calcula como 8 en tiempo de ejecución:

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

Obviamente, los arrays anónimos requieren más memoria que los arrays nombrados debido al puntero adicional que debe almacenarse por separado. (También hay algunos gastos adicionales en la tienda gratuita.)

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

En cuanto a los valores por defecto para los elementos, los arrays anónimos se comportan de manera similar a los arrays automáticos. Normalmente, las matrices de POD anónimos no se inicializan, pero hay una sintaxis especial que activa la inicialización de valores:

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

(Observe el par final de paréntesis justo antes del punto y coma.) Una vez más, 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 liberarla de nuevo al sistema:

delete[] p;

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

 68
Author: fredoverflow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:50

5. Errores comunes al usar arrays.

5.1 Trampa: Confiando en el tipo-enlaces inseguros.

OK, se le ha dicho, o ha descubierto usted mismo, que globals (namespace variables de ámbito a las que se puede acceder fuera de la unidad de traducción) son Mal™. Pero, ¿sabías lo malvados que son? Considere la programa a continuación, que consta de dos archivos [principal.cpp] y [números.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};

En Windows 7 esto compila y enlaza bien con MinGW g++ 4.4.1 y Visual C++ 10.0.

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

El diálogo de bloqueo de Windows 7

Explicación in-the-formal: el programa tiene un Comportamiento Indefinido (UB), y en su lugar de estrellarse, por lo tanto, solo puede colgar, o tal vez no hacer nada, o puede enviar e-mails amenazantes a los presidentes de los EE.UU., Rusia, India, China y Suiza, y hacer que los Demonios Nasales salgan volando de tu nariz.

Explicación en la práctica: en main.cpp el array es tratado como puntero, colocado en la misma dirección que la matriz. Para ejecutable de 32 bits esto significa que la primera int valor en la matriz, se trata como un puntero. I. e., in main.cpp the numbers la variable contiene, o parece contener, (int*)1. Esto causa la programa para acceder a la memoria en la parte inferior del espacio de direcciones, que es convencionalmente reservado y causante de trampas. Resultado: se produce un accidente.

Los compiladores están plenamente en su derecho de no diagnosticar este error, porque C++11 §3.5/10 dice, sobre el 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 se permite: {[54]]}

Declarations las declaraciones para un objeto array pueden especificar tipos de array que se diferencian por la presencia o ausencia de una matriz mayor enlazada (8.3.4).

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

5.2 Trampa: Hacer optimización prematura (memset y amigos).

Aún no está escrito

5.3 Pitfall: Usar el modismo C para obtener el número de elementos.

Con una profunda experiencia en C es natural escribir {{[54]]}

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

Dado que un array decae a puntero al primer elemento cuando es necesario, el expression sizeof(a)/sizeof(a[0]) también se puede escribir como sizeof(a)/sizeof(*a). Significa lo mismo, y no importa cómo es escrito es el modismo C para encontrar los elementos numéricos de la matriz.

Escollo principal: el modismo C no es typesafe. Por ejemplo, el código …

#include <stdio.h>

#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 lo más probable es que produzca un error resultado. Compilado como un ejecutable de 32 bits en Windows 7 produce {

7 elementos, llamando a display...
1 elementos.

  1. El compilador reescribe int const a[7] a solo int const a[].
  2. El compilador reescribe int const a[] a int const* a.
  3. por lo tanto, N_ITEMS se invoca con un puntero.
  4. Para un 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 tiempo de ejecución, puede hacer {

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

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

7 elementos, llamando a display...
Assertion failed: ("N_ITEMS requiere un array real como argumento", typeid (a ) != typeid( &*a ) ), archivo runtime_detect ion.cpp, línea 16

Esta aplicación ha solicitado al Tiempo de ejecución que lo termine de una manera inusual.
Póngase en contacto con el equipo de soporte de la aplicación para obtener más información.

La detección de errores en tiempo de ejecución es mejor que ninguna detección, pero desperdicia un poco tiempo de procesador, y quizás mucho más tiempo de programador. Mejor con detección en ¡hora de compilar! Y si está feliz de no admitir matrices de tipos locales con C++98, entonces puedes hacer eso:

#include <stddef.h>

typedef ptrdiff_t   Size;

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

#define N_ITEMS( array )       n_items( array )

Compilando esta definición sustituida en el primer programa completo, con g++, Tengo ...

M:\count> g++ compile_time_detection.cpp
compile_time_detection.cpp: En función ' void display (const int*)':
compile_time_detection.cpp: 14: error: no hay función coincidente para la llamada a' n_items(const int*&) '

M:\count> _

Cómo funciona: el array se pasa por referencia a n_items, y así lo hace no decaer al puntero al primer elemento, y la función solo 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 seguro C++ modi para encontrar el número de elementos de una matriz.

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

Con C++11 y posteriores es natural,pero como verás peligroso!, a reemplazar el C++03 function

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 esta función produce una constante de tiempo de compilación.

Por ejemplo, en contraste con la función C++03, tal constante de tiempo de compilación se puede usar para declarar un array del mismo tamaño que otro:

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

La trampa: a partir de julio de 2015, lo anterior compila con MinGW-64 5.1.0 con -pedantic-errors, y, probando con los compiladores 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 compila con Visual C++ 2015. La razón es una declaración de C++11/C++14 sobre el uso de referencias en expresiones constexpr:

C++11 C++14 $5.19/2 nueveth dash

A expresión condicional e es un expresión constante del núcleo a menos que la evaluación de e, siguiendo las reglas de la máquina abstracta (1.9), evaluaría uno de los 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 anterior y
    • se inicializa con una expresión constante o
    • es un miembro de datos no estático de una objeto cuya vida comenzó dentro la evaluación de e;

Siempre se puede escribir el más detallado{[54]]}

// 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
}

But pero esto falla cuando Collection no es una matriz raw.

Para tratar con colecciones que pueden no ser arrays se necesita la sobrecarga de un n_items función, sino también, para el uso de tiempo de compilación se necesita un tiempo de compilación representación del tamaño del array. Y la solución clásica de C++03, que funciona bien también en C++11 y C++14, es deje que la función informe su resultado no como un valor pero a través de su función result type. Por ejemplo:

// Example 4 - OK (not ideal, but portable and safe)

#include <array>
#include <stddef.h>

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<n>;
// No implementation, is used only at compile time.

template< class Type, size_t n >        // size_t for g++
auto static_n_items( std::array<Type, n> const& )
    -> Size_carrier<n>;
// No implementation, is used only at compile time.

#define STATIC_N_ITEMS( c ) \
    static_cast<Size>( 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<int, 43> y;
    foo( x );
    foo( y );
}

Acerca de la elección del tipo de retorno para static_n_items: este código no utiliza std::integral_constant porque con std::integral_constant se representa el resultado directamente como un valor constexpr, reintroduciendo el problema original. En su lugar de una clase Size_carrier uno puede dejar que la función devuelva directamente una referencia a una matriz. Sin embargo, no todo el mundo está familiarizado con esa sintaxis.

Acerca del nombramiento: parte de esta solución a constexpr - invalid-due-to-reference el problema es hacer explícita la elección de la constante de tiempo de compilación.

Esperemos que el ups-there-was-a-reference-involved-in-your - constexpr problema se solucionará con C++17, pero hasta entonces una macro como la STATIC_N_ITEMS anterior produce portabilidad, por ejemplo, a los compiladores clang y Visual C++, manteniendo la seguridad de tipo.

Relacionado: las macros no respetan los ámbitos, por lo que para evitar colisiones de nombres puede ser un buena idea usar un prefijo de nombre, p. ej. MYLIB_STATIC_N_ITEMS.

 65
Author: Cheers and hth. - Alf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-07-31 00:15:12