Como eu uso arrays em C ++?

C ++ herdou matrizes de C, onde elas são usadas virtualmente em todos os lugares. C ++ fornece abstrações que são mais fáceis de usar e menos propensas a erros ( std::vector desde C ++ 98 e std::array desde C ++ 11 ), então a necessidade de arrays não surgem com a mesma freqüência que em C. No entanto, quando você lê código herdado ou interage com uma biblioteca escrita em C, você deve ter uma compreensão firme de como os arrays funcionam.

Esta FAQ é dividida em cinco partes:

  1. matrizes no nível de tipo e acessando elementos
  2. criação e boot de matriz
  3. atribuição e passagem de parâmetros
  4. Matrizes multidimensionais e matrizes de pointers
  5. armadilhas comuns ao usar matrizes

Se você sentir que algo importante está faltando neste FAQ, escreva uma resposta e associe-a aqui como uma parte adicional.

No texto a seguir, “array” significa “matriz C”, não o modelo de class std::array . Conhecimento básico da syntax do declarador C é assumido. Observe que o uso manual de new e delete conforme demonstrado abaixo, é extremamente perigoso em face de exceções, mas esse é o tópico de outra FAQ .

(Nota: Esta é uma input para o C ++ FAQ do Stack Overflow . Se você quiser criticar a idéia de fornecer um FAQ neste formulário, então o post no meta que iniciou tudo isso seria o lugar para fazer isso. essa questão é monitorada na sala de chat do C ++ , onde a ideia do FAQ começou em primeiro lugar, então é muito provável que sua resposta seja lida por aqueles que surgiram com a ideia.)

Matrizes no nível do tipo

Um tipo de matriz é denotado como T[n] onde T é o tipo de elemento e n é um tamanho positivo, o número de elementos na matriz. O tipo de matriz é um tipo de produto do tipo de elemento e o tamanho. Se um ou ambos os ingredientes forem diferentes, você terá um tipo distinto:

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

Observe que o tamanho é parte do tipo, ou seja, tipos de matriz de tamanho diferente são tipos incompatíveis que não têm absolutamente nada a ver um com o outro. sizeof(T[n]) é equivalente a n * sizeof(T) .

Decaimento de ponteiro para ponteiro

A única “conexão” entre T[n] e T[m] é que ambos os tipos podem ser implicitamente convertidos em T* , e o resultado dessa conversão é um ponteiro para o primeiro elemento da matriz. Ou seja, em qualquer lugar que um T* é necessário, você pode fornecer um T[n] , e o compilador fornecerá silenciosamente aquele ponteiro:

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

Essa conversão é conhecida como “decaimento array-a-pointer” e é uma grande fonte de confusão. O tamanho da matriz é perdido nesse processo, já que não faz mais parte do tipo ( T* ). Pro: Esquecer o tamanho de uma matriz no nível do tipo permite que um ponteiro aponte para o primeiro elemento de uma matriz de qualquer tamanho. Con: Dado um ponteiro para o primeiro elemento (ou qualquer outro) de uma matriz, não há como detectar o tamanho dessa matriz ou onde exatamente o ponteiro aponta em relação aos limites da matriz. Ponteiros são extremamente estúpidos .

Arrays não são pointers

O compilador gerará silenciosamente um ponteiro para o primeiro elemento de um array sempre que for considerado útil, isto é, sempre que uma operação falhar em um array, mas tiver sucesso em um ponteiro. Essa conversão de matriz para ponteiro é trivial, já que o valor do ponteiro resultante é simplesmente o endereço da matriz. Observe que o ponteiro não é armazenado como parte do próprio array (ou em qualquer outro lugar na memory). Uma matriz não é um ponteiro.

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

Um contexto importante no qual uma matriz não decai em um ponteiro para seu primeiro elemento é quando o operador & é aplicado a ele. Nesse caso, o operador & produz um ponteiro para o array inteiro , não apenas um ponteiro para o primeiro elemento. Embora, nesse caso, os valores (os endereços) sejam os mesmos, um ponteiro para o primeiro elemento de uma matriz e um ponteiro para a matriz inteira são tipos completamente distintos:

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

A seguinte arte ASCII explica essa distinção:

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

Observe como o ponteiro para o primeiro elemento aponta apenas para um único inteiro (representado como uma pequena checkbox), enquanto o ponteiro para a matriz inteira aponta para uma matriz de 8 inteiros (representada como uma checkbox grande).

A mesma situação surge nas aulas e é talvez mais óbvia. Um ponteiro para um object e um ponteiro para seu primeiro membro de dados têm o mesmo valor (o mesmo endereço), mas são tipos completamente distintos.

Se você não estiver familiarizado com a syntax do declarador C, os parênteses no tipo int(*)[8] são essenciais:

  • int(*)[8] é um ponteiro para um array de 8 inteiros.
  • int*[8] é uma matriz de 8 pointers, cada elemento do tipo int* .

Acessando elementos

C ++ fornece duas variações sintáticas para acessar elementos individuais de uma matriz. Nenhum deles é superior ao outro, e você deve se familiarizar com ambos.

Aritmética ponteiro

Dado um ponteiro p para o primeiro elemento de uma matriz, a expressão p+i produz um ponteiro para o i-ésimo elemento da matriz. Ao desreferenciar esse ponteiro depois, pode-se acessar elementos individuais:

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

Se x denota um array , então o decaimento array-para-ponteiro será ativado, porque adicionar um array e um inteiro não tem sentido (não há operação plus em arrays), mas adicionar um ponteiro e um inteiro faz sentido:

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

(Note que o ponteiro gerado implicitamente não tem nome, então escrevi x+0 para identificá-lo.)

Se, por outro lado, x denotar um ponteiro para o primeiro (ou qualquer outro) elemento de um array, então o decaimento array-para-ponteiro não é necessário, porque o ponteiro no qual i será adicionado já existe:

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

Observe que, no caso representado, x é uma variável de ponteiro (discernível pela pequena checkbox ao lado de x ), mas poderia ser o resultado de uma function que retorna um ponteiro (ou qualquer outra expressão do tipo T* ).

Operador de indexação

Como a syntax *(x+i) é um pouco desajeitada, o C ++ fornece a syntax alternativa x[i] :

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

Devido ao fato de que a adição é comutativa, o código a seguir faz exatamente o mesmo:

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

A definição do operador de indexação leva à seguinte equivalência interessante:

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

No entanto, &x[0] geralmente não é equivalente a x . O primeiro é um ponteiro, o segundo é um array. Somente quando o contexto desencadeia decaimento entre array e ponteiro, x e &x[0] ser usados ​​de forma intercambiável. Por exemplo:

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

Na primeira linha, o compilador detecta uma atribuição de um ponteiro para um ponteiro, o que é trivialmente bem-sucedido. Na segunda linha, ele detecta uma atribuição de uma matriz para um ponteiro. Como isso é insignificante (mas o ponteiro para a atribuição de ponteiro faz sentido), o decaimento entre array e ponteiro entra em ação como de costume.

Gamas

Uma matriz do tipo T[n] possui n elementos, indexados de 0 a n-1 ; não há elemento n . E, no entanto, para suportar intervalos semiabertos (em que o início é inclusivo e o final é exclusivo ), C ++ permite o cálculo de um ponteiro para o (n-existente) n-ésimo elemento, mas é ilegal desreferenciar esse ponteiro:

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

Por exemplo, se você quiser classificar uma matriz, as duas opções a seguir funcionariam igualmente bem:

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

Note que é ilegal fornecer &x[n] como o segundo argumento, já que isso é equivalente a &*(x+n) , e a sub-expressão *(x+n) tecnicamente invoca o comportamento indefinido em C ++ (mas não em C99) ).

Observe também que você poderia simplesmente fornecer x como o primeiro argumento. Isso é um pouco curto demais para o meu gosto, e também torna a dedução de argumentos de template um pouco mais difícil para o compilador, porque nesse caso o primeiro argumento é uma matriz, mas o segundo argumento é um ponteiro. (Novamente, o decaimento array-a-ponteiro entra em ação.)

Os programadores geralmente confundem matrizes multidimensionais com matrizes de pointers.

Matrizes multidimensionais

A maioria dos programadores está familiarizada com os arrays multidimensionais nomeados, mas muitos desconhecem o fato de que o array multidimensional também pode ser criado anonimamente. Matrizes multidimensionais são muitas vezes referidas como “matrizes de matrizes” ou ” verdadeiras matrizes multidimensionais”.

Nomeados arrays multidimensionais

Ao usar matrizes multidimensionais nomeadas, todas as dimensões devem ser conhecidas em tempo de compilation:

 int H = read_int(); int W = read_int(); int connect_four[6][7]; // okay int connect_four[H][7]; // ISO C++ forbids variable length array int connect_four[6][W]; // ISO C++ forbids variable length array int connect_four[H][W]; // ISO C++ forbids variable length array 

É assim que um array multidimensional nomeado se parece na memory:

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

Observe que as grades 2D, como as acima, são apenas visualizações úteis. Do ponto de vista do C ++, a memory é uma sequência “simples” de bytes. Os elementos de uma multidimensional array são armazenados na ordem principal da linha. Ou seja, connect_four[0][6] e connect_four[1][0] são vizinhos na memory. De fato, connect_four[0][7] e connect_four[1][0] denotam o mesmo elemento! Isso significa que você pode usar matrizes multidimensionais e tratá-las como matrizes grandes e unidimensionais:

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

Matrizes multidimensionais anônimas

Com matrizes multidimensionais anônimas, todas as dimensões, exceto a primeira, devem ser conhecidas em tempo de compilation:

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

É assim que uma multidimensional array anônima se parece na memory:

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

Observe que a matriz em si ainda está alocada como um único bloco na memory.

Matrizes de pointers

Você pode superar a restrição de largura fixa introduzindo outro nível de indireção.

Nomeado arrays de pointers

Aqui está uma matriz nomeada de cinco pointers que são inicializados com matrizes anônimas de diferentes comprimentos:

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

E aqui está como parece na memory:

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

Como cada linha é alocada individualmente agora, a visualização de matrizes 2D como matrizes 1D não funciona mais.

Matrizes anônimas de pointers

Aqui está uma matriz anônima de 5 (ou qualquer outro número de) pointers que são inicializados com matrizes anônimas de diferentes comprimentos:

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

E aqui está como parece na memory:

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

Conversões

O decaimento entre array e ponteiro naturalmente se estende a arrays de arrays e arrays de pointers:

 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; 

No entanto, não há conversão implícita de T[h][w] para T** . Se tal conversão implícita existisse, o resultado seria um ponteiro para o primeiro elemento de uma matriz de pointers h para T (cada um apontando para o primeiro elemento de uma linha na matriz 2D original), mas essa matriz de ponteiro não existe em qualquer lugar na memory ainda. Se você quiser tal conversão, você deve criar e preencher manualmente a matriz de pointers 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; 

Observe que isso gera uma visão da multidimensional array original. Se você precisar de uma cópia, crie matrizes extras e copie os dados por conta própria:

 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; 

Tarefa

Por nenhum motivo específico, as matrizes não podem ser atribuídas umas às outras. Use std::copy vez disso:

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

Isso é mais flexível do que a atribuição de matriz verdadeira poderia fornecer, pois é possível copiar fatias de matrizes maiores em matrizes menores. std::copy é normalmente especializado para tipos primitivos para oferecer desempenho máximo. É improvável que o std::memcpy desempenho melhor. Em caso de dúvida, meça.

Embora não seja possível atribuir matrizes diretamente, você pode atribuir estruturas e classs que contenham membros da matriz. Isso ocorre porque os membros da matriz são copiados membro pelo operador de atribuição que é fornecido como padrão pelo compilador. Se você definir o operador de atribuição manualmente para seus próprios tipos de class ou struct, você deve retornar à cópia manual para os membros da matriz.

Passagem de parâmetros

Arrays não podem ser passados ​​por valor. Você pode passá-los por ponteiro ou por referência.

Passe pelo ponteiro

Como as matrizes em si não podem ser passadas por valor, geralmente um ponteiro para o primeiro elemento é passado por valor. Isso geralmente é chamado de “passar pelo ponteiro”. Como o tamanho da matriz não é recuperável através desse ponteiro, você precisa passar um segundo parâmetro indicando o tamanho da matriz (a solução C clássica) ou um segundo ponteiro apontando após o último elemento da matriz (a solução do 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ática, você também pode declarar parâmetros como T p[] , e isso significa exatamente a mesma coisa que T* p no contexto de listas de parâmetros apenas :

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

Você pode pensar no compilador como reescrevendo T p[] para T *p no contexto das listas de parâmetros . Essa regra especial é parcialmente responsável por toda a confusão sobre matrizes e pointers. Em todos os outros contextos, declarar algo como uma matriz ou como um ponteiro faz uma enorme diferença.

Infelizmente, você também pode fornecer um tamanho em um parâmetro de matriz que é silenciosamente ignorado pelo compilador. Ou seja, as três assinaturas a seguir são exatamente equivalentes, conforme indicado pelos erros do 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 

Passe por referência

Matrizes também podem ser passadas por referência:

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

Nesse caso, o tamanho da matriz é significativo. Como escrever uma function que aceita apenas matrizes de exatamente 8 elementos é de pouca utilidade, os programadores geralmente escrevem funções como templates:

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

Observe que você só pode chamar esse modelo de function com uma matriz real de inteiros, não com um ponteiro para um inteiro. O tamanho da matriz é inferido automaticamente e, para cada tamanho n , uma function diferente é instanciada a partir do modelo. Você também pode escrever modelos de function bastante úteis que sejam abstratos tanto do tipo de elemento quanto do tamanho.

Criação e boot de matriz

Como com qualquer outro tipo de object C ++, matrizes podem ser armazenadas diretamente em variables ​​nomeadas (então o tamanho deve ser uma constante de tempo de compilation; C ++ não suporta VLAs ), ou elas podem ser armazenadas anonimamente no heap e acessadas indiretamente via pointers (só então o tamanho pode ser calculado em tempo de execução).

Matrizes automáticas

Matrizes automáticas (matrizes que vivem “na pilha”) são criadas cada vez que o stream de controle passa pela definição de uma variável de matriz local não-estática:

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

Inicialização é executada em ordem crescente. Observe que os valores iniciais dependem do tipo de elemento T :

  • Se T é um POD (como int no exemplo acima), nenhuma boot ocorre.
  • Caso contrário, o construtor padrão de T inicializa todos os elementos.
  • Se T não fornece um construtor padrão acessível, o programa não compila.

Como alternativa, os valores iniciais podem ser explicitamente especificados no inicializador de matriz , uma lista separada por vírgula entre chaves.

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

Como neste caso o número de elementos no inicializador da matriz é igual ao tamanho da matriz, especificar o tamanho manualmente é redundante. Ele pode ser automaticamente deduzido pelo compilador:

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

Também é possível especificar o tamanho e fornecer um inicializador de matriz mais curto:

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

Nesse caso, os elementos restantes são inicializados com zero . Observe que o C ++ permite um inicializador de matriz vazia (todos os elementos são inicializados com zero), enquanto o C89 não (pelo menos um valor é necessário). Observe também que os inicializadores de array só podem ser usados ​​para inicializar matrizes; eles não podem mais ser usados ​​em atribuições.

Matrizes estáticas

Matrizes estáticas (matrizes que vivem “no segmento de dados”) são variables ​​de matriz local definidas com a palavra-chave static e as variables ​​de matriz no escopo do namespace (“variables ​​globais”):

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

(Observe que as variables ​​no escopo do namespace são implicitamente estáticas. Adicionar a palavra-chave static à sua definição tem um significado completamente diferente e obsoleto .)

Veja como os arrays estáticos se comportam de maneira diferente dos arrays automáticos:

  • Matrizes estáticas sem um inicializador de matriz são inicializadas com zero antes de qualquer nova boot potencial.
  • Os arrays POD estáticos são inicializados exatamente uma vez e os valores iniciais são tipicamente integrados ao executável, caso em que não há custo de boot no tempo de execução. Essa nem sempre é a solução mais eficiente em termos de espaço, e não é exigida pelo padrão.
  • Matrizes não-POD estáticas são inicializadas na primeira vez que o stream de controle passa por sua definição. No caso de matrizes estáticas locais, isso pode nunca acontecer se a function nunca for chamada.

(Nenhuma das opções acima é específica para matrizes. Essas regras se aplicam igualmente bem a outros tipos de objects estáticos.)

Membros de dados de matriz

Os membros de dados da matriz são criados quando seu object proprietário é criado. Infelizmente, o C ++ 03 não fornece meios para inicializar matrizes na lista de inicializadores de membros , portanto, a boot deve ser falsificada com atribuições:

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

Como alternativa, você pode definir uma matriz automática no corpo do construtor e copiar os elementos:

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

Em C ++ 0x, as matrizes podem ser inicializadas na lista de inicializadores de membros graças à boot uniforme :

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

Essa é a única solução que funciona com tipos de elementos que não possuem um construtor padrão.

Matrizes dinâmicas

Arrays dynamics não possuem nomes, portanto, o único meio de acessá-los é através de pointers. Porque eles não têm nomes, vou me referir a eles como “matrizes anônimas” a partir de agora.

Em C, matrizes anônimas são criadas via malloc e amigos. Em C ++, matrizes anônimas são criadas usando a new T[size] syntax new T[size] que retorna um ponteiro para o primeiro elemento de uma matriz anônima:

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

A seguinte arte ASCII descreve o layout da memory se o tamanho for calculado como 8 no tempo de execução:

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

Obviamente, as matrizes anônimas exigem mais memory que as matrizes nomeadas devido ao ponteiro extra que deve ser armazenado separadamente. (Há também alguma sobrecarga adicional na loja gratuita.)

Observe que não decaimento entre array e ponteiro acontecendo aqui. Embora a avaliação new int[size] realmente crie uma matriz de inteiros, o resultado da expressão new int[size] é um ponteiro para um único inteiro (o primeiro elemento), não uma matriz de inteiros ou um ponteiro para um matriz de inteiros de tamanho desconhecido. Isso seria impossível, porque o sistema de tipo estático exige que os tamanhos de matriz sejam constantes de tempo de compilation. (Por isso, não anotei a matriz anônima com informações de tipo estático na imagem.)

Com relação aos valores padrão dos elementos, os arrays anônimos se comportam de maneira semelhante aos arrays automáticos. Normalmente, as matrizes POD anônimas não são inicializadas, mas há uma syntax especial que aciona a boot do valor:

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

(Observe o par de parênteses à direita antes do ponto-e-vírgula.) Novamente, o C ++ 0x simplifica as regras e permite especificar valores iniciais para matrizes anônimas graças à boot uniforme:

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

Se você acabou de usar um array anônimo, você tem que liberá-lo de volta para o sistema:

 delete[] p; 

Você deve liberar cada array anônimo exatamente uma vez e depois nunca mais tocá-lo novamente. Não liberá-lo em todos os resultados em um memory leaks (ou mais geralmente, dependendo do tipo de elemento, um vazamento de resources) e tentar liberá-lo várias vezes resulta em comportamento indefinido. Usar o formulário não-matriz delete (ou free ) em vez de delete[] para liberar o array também é um comportamento indefinido .

5. armadilhas comuns ao usar matrizes.

5.1 Pitfall: Confiando em links inseguros.

OK, você foi informado, ou descobriu, que globals (variables ​​de escopo de namespace que podem ser acessadas fora da unidade de tradução) são Evil ™. Mas você sabia como eles são realmente malvados? Considere o programa abaixo, consistindo de dois arquivos [main.cpp] e [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}; 

No Windows 7, isso compila e liga bem tanto o MinGW g ++ 4.4.1 quanto o Visual C ++ 10.0.

Como os tipos não correspondem, o programa trava quando você o executa.

O diálogo de falha do Windows 7

Explicação formal: o programa tem Comportamento Indefinido (UB) e, em vez de travar, ele pode simplesmente travar, ou talvez não fazer nada, ou enviar e-mails para os presidentes dos EUA, Rússia, Índia, China e Suíça, e faça Daemons Nasais voarem para fora do seu nariz.

Explicação na prática: em main.cpp a matriz é tratada como um ponteiro, colocada no mesmo endereço da matriz. Para o executável de 32 bits, isso significa que o primeiro valor int da matriz é tratado como um ponteiro. Ou seja, em main.cpp a variável de numbers contém, ou parece conter, (int*)1 . Isso faz com que o programa acesse a memory na parte inferior do espaço de endereço, que é convencionalmente reservado e causa interferência. Resultado: você sofre um acidente.

Os compiladores estão totalmente dentro de seus direitos para não diagnosticar este erro, porque o C ++ 11 §3.5 / 10 diz, sobre a exigência de tipos compatíveis para as declarações,

[N3290 §3.5 / 10]
Uma violação desta regra no tipo de identidade não requer um diagnóstico.

O mesmo parágrafo detalha a variação permitida:

… Declarações para um object de matriz podem especificar tipos de matriz que diferem pela presença ou ausência de um limite de matriz principal (8.3.4).

Essa variação permitida não inclui declarar um nome como um array em uma unidade de tradução e como um ponteiro em outra unidade de tradução.

5.2 Armadilha: Fazendo uma otimização prematura ( memset & friends).

Ainda não escrito

5.3 Pitfall: Usando o idioma C para obter o número de elementos.

Com profunda experiência em C, é natural escrever…

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

Uma vez que um array decai para ponteiro para o primeiro elemento quando necessário, a expressão sizeof(a)/sizeof(a[0]) também pode ser escrita como sizeof(a)/sizeof(*a) . Significa o mesmo, e não importa como esteja escrito, é o idioma C para encontrar os elementos numéricos do array.

Principal armadilha: o idioma C não é seguro. Por exemplo, o 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 ); } 

passa um ponteiro para N_ITEMS e, portanto, provavelmente produz um resultado errado. Compilado como um executável de 32 bits no Windows 7…

7 elementos, chamando a exibição ...
1 elementos

  1. O compilador reescreve int const a[7] para apenas int const a[] .
  2. O compilador reescreve int const a[] para int const* a .
  3. N_ITEMS é, portanto, invocado com um ponteiro.
  4. Para um tamanho de executável de 32 bits sizeof(array) (tamanho de um ponteiro) é então 4.
  5. sizeof(*array) é equivalente a sizeof(int) , que para um executável de 32 bits também é 4.

Para detectar esse erro em tempo de execução, você pode…

 #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, chamando a exibição ...
Assertion failed: ("N_ITEMS requer uma matriz real como argumento", typeid (a)! = Typeid (& * a)), arquivo runtime_detect ion.cpp, linha 16

Este aplicativo solicitou o Runtime para finalizá-lo de uma maneira incomum.
Entre em contato com a equipe de suporte do aplicativo para obter mais informações.

A detecção de erros em tempo de execução é melhor do que nenhuma detecção, mas gasta um pouco de tempo do processador e talvez muito mais tempo do programador. Melhor com detecção em tempo de compilation! E se você está feliz em não suportar matrizes de tipos locais com o C ++ 98, então você pode fazer isso:

 #include  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 definição substituída no primeiro programa completo, com g + +, eu consegui…

M: \ count> g ++ compile_time_detection.cpp
compile_time_detection.cpp: Na function 'void display (const int *)':
compile_time_detection.cpp: 14: erro: nenhuma function correspondente para chamar a 'n_items (const int * &)'

M: \ count> _

Como funciona: a matriz é passada por referência a n_items e, portanto, não n_items ponteiro para o primeiro elemento, e a function pode retornar apenas o número de elementos especificado pelo tipo.

Com o C ++ 11, você pode usar isso também para matrizes de tipo local, e é o tipo de linguagem C ++ segura para localizar o número de elementos de uma matriz.

5.4 C ++ 11 & C ++ 14 pitfall: Usando uma function de tamanho de array constexpr .

Com C ++ 11 e posterior é natural, mas como você verá perigoso !, para replace a function C ++ 03

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

com

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

onde a mudança significativa é o uso de constexpr , que permite que essa function produza uma constante de tempo de compilation .

Por exemplo, em contraste com a function C ++ 03, essa constante de tempo de compilation pode ser usada para declarar uma matriz do mesmo tamanho que outra:

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

Mas considere este código usando a versão 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 ); } 

A armadilha: a partir de julho de 2015, o acima compila com MinGW-64 5.1.0 com -pedantic-errors , e, testando com os compiladores on-line em gcc.godbolt.org/ , também com clang 3.0 e clang 3.2, mas não com clang 3,3, 3,4,1, 3,5,0, 3,5,1, 3,6 (rc1) ou 3,7 (experimental). E importante para a plataforma Windows, ela não é compilada com o Visual C ++ 2015. A razão é uma instrução C ++ 11 / C ++ 14 sobre o uso de referências em expressões constexpr :

C ++ 11 C ++ 14 $ 5.19 / 2 nove traço

Uma expressão condicional e é uma expressão constante central a menos que a avaliação de e , seguindo as regras da máquina abstrata (1.9), avalie uma das seguintes expressões:

  • uma expressão id que se refere a uma variável ou membro de dados do tipo de referência, a menos que a referência tenha uma boot anterior e
    • é inicializado com uma expressão constante ou
    • é um membro de dados não estático de um object cuja duração começou dentro da avaliação de e;

Pode-se sempre escrever o mais detalhado

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

… Mas isso falha quando a Collection não é um array bruto.

Para lidar com collections que podem ser não-arrays, é necessária a capacidade de sobrecarga de uma function n_items , mas também, para uso em tempo de compilation, é necessária uma representação em tempo de compilation do tamanho da matriz. E a solução clássica C ++ 03, que funciona bem também em C ++ 11 e C ++ 14, é deixar a function reportar seu resultado não como um valor, mas através de seu tipo de resultado de function. Por exemplo, assim:

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