C++ std::array C++11

Na aula Arrays, o array estilo C mostrou seus problemas: não sabe o próprio tamanho fora de onde foi criado, não confere os índices, não pode ser copiado com = nem comparado com ==, e vira ponteiro quando vai para uma função. O std::array, da biblioteca padrão, é o mesmo array de tamanho fixo, igualmente rápido, mas que se comporta como uma variável "normal" e traz ferramentas prontas.

Você vai aprender
  • Como criar um std::array<int, 5> e o que significa cada parte.
  • Como usar .size(), .front() e .back().
  • A diferença entre [i] e .at(i), e a mensagem real de std::out_of_range.
  • Como encher todo o array com um valor usando .fill.
  • Como copiar com = e comparar com == (e o erro real quando se tenta isso com o array estilo C).
  • Como passar um std::array para uma função sem perder o tamanho.

Os programas foram compilados com o g++ 15 na máquina fictícia da Ana, na pasta ~/cpp, com g++ -std=c++23 -Wall -Wextra arquivo.cpp -o programa.


A ideia: a mesma fileira de armários, agora com uma placa

Lembra da fileira de armários numerados da aula Arrays? O std::array é a mesma fileira, com uma placa na ponta dizendo quantos armários existem, e um zelador que, se você pedir com educação (.at()), confere se o armário que você pediu existe antes de abri-lo.

Por dentro, ele guarda exatamente um array estilo C; não gasta memória a mais e é tão rápido quanto. A diferença está só nas ferramentas em volta. Por isso, no C++ moderno, quando a quantidade é fixa, use std::array.


1. Criar e usar

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    std::cout << "Tamanho: " << notas.size() << '\n';
    std::cout << "Primeira: " << notas[0] << '\n';
    std::cout << "Última: " << notas[notas.size() - 1] << '\n';
    std::cout << "front: " << notas.front() << ", back: " << notas.back() << '\n';

    for (int nota : notas) {
        std::cout << nota << ' ';
    }
    std::cout << '\n';
}
#include <array>
O cabeçalho do std::array. Às vezes o programa compila sem ele, porque outro cabeçalho (como o <iostream>) o inclui "por acaso"; não conte com isso, inclua sempre.
std::array
O tipo, da biblioteca padrão (por isso o std::).
<int, 5>
Entre < >, duas informações: o tipo dos elementos (int) e o tamanho (5). O tamanho faz parte do tipo: um std::array<int, 5> e um std::array<int, 6> são tipos diferentes. Ele precisa ser conhecido na hora de compilar.
notas
O nome da variável.
= {7, 9, 6, 8, 10};
Os valores, igual ao array estilo C. Os que faltarem viram 0; com {}, todos viram 0.
notas.size()
O tamanho, perguntado ao próprio array. O ponto (.) chama uma função-membro: uma ferramenta que pertence ao objeto (você já usou com texto.size() em Funções de String).
notas[0]
O acesso por índice que você já conhece, de 0 a size() − 1.
.front() / .back()
Atalhos para o primeiro e o último elemento.
$ g++ -std=c++23 -Wall -Wextra criar.cpp -o criar
$ ./criar
Tamanho: 5
Primeira: 7
Última: 10
front: 7, back: 10
7 9 6 8 10 

Tudo o que funciona com o array estilo C funciona aqui: índices do 0, for com índice, for de intervalo. O .size(), porém, está sempre disponível, até dentro de uma função (seção 6).

Deixe o compilador contar C++17

Desde o C++17, você pode omitir o tipo e o tamanho: std::array precos = {12.5, 8.0, 27.0}; vira um std::array<double, 3>. Útil para listas fixas escritas à mão.


2. [i] × .at(i): o acesso conferido

Há duas formas de acessar um elemento:

  • notas[i]: rápido, e a linguagem não promete conferir o índice. Fora do intervalo, é comportamento indefinido, como no array estilo C.
  • notas.at(i): confere sempre. Se o índice não existir, em vez de ler memória alheia, o programa lança uma exceção: um aviso de erro que interrompe o caminho normal.
#include <array>
#include <iostream>

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    std::cout << "notas.at(2): " << notas.at(2) << '\n';
    std::cout << "notas.at(5): " << notas.at(5) << '\n';
    std::cout << "Fim\n";
}
$ g++ -std=c++23 -Wall -Wextra at.cpp -o at
$ ./at
notas.at(2): 6
terminate called after throwing an instance of 'std::out_of_range'
  what():  array::at: __n (which is 5) >= _Nm (which is 5)
Abortado                   (imagem do núcleo gravada) ./at
$ echo $?
134

Linha por linha:

terminate called after throwing an instance of 'std::out_of_range'
"O programa foi encerrado depois de lançar uma exceção do tipo std::out_of_range" (out of range = fora do intervalo). Como ninguém tratou a exceção, o C++ encerrou o programa.
what(): array::at: __n (which is 5) >= _Nm (which is 5)
A explicação: no array::at, o índice pedido (__n, que é 5) é maior ou igual ao tamanho (_Nm, que é 5). Os nomes com sublinhado são os nomes internos da biblioteca.
Abortado (imagem do núcleo gravada)
Mensagem do terminal (o Bash): o programa foi abortado. A "imagem do núcleo" (core dump) é uma cópia da memória para depuração; você pode ignorá-la.
134
O código de saída de um programa abortado (128 + 6, o número do sinal de "abortar").

O Fim nunca apareceu. Parece ruim, mas compare com o array estilo C da aula anterior, que mostrou um 0 inventado e seguiu em frente como se nada tivesse acontecido. Um programa que para e diz onde errou é muito melhor que um que continua com dados errados.

No editor do guia, a mensagem é outra

O editor usa o Clang com outra biblioteca padrão (a libc++; o g++ usa a libstdc++). A exceção é a mesma, mas o texto muda: libc++abi: terminating due to uncaught exception of type std::out_of_range: array::at ("encerrando por causa de uma exceção não tratada do tipo std::out_of_range"), sem os números do índice e do tamanho.

Tratando a exceção

Uma exceção pode ser "pega" com try e catch, e aí o programa decide o que fazer em vez de morrer. Isso tem aula própria (Exceções); veja só a ideia:

#include <array>
#include <iostream>
#include <stdexcept>

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    int posicao = 7;
    try {
        std::cout << notas.at(posicao) << '\n';
    } catch (const std::out_of_range& erro) {
        std::cout << "Posição inválida: " << erro.what() << '\n';
    }
    std::cout << "O programa continua.\n";
}
$ g++ -std=c++23 -Wall -Wextra trycatch.cpp -o trycatch
$ ./trycatch
Posição inválida: array::at: __n (which is 7) >= _Nm (which is 5)
O programa continua.

E o [i] fora do intervalo?

Pela regra da linguagem, notas[5] é comportamento indefinido. Na prática, depende de como você compila. O g++ 15, sem otimização (o comando padrão do guia), liga verificações extras na biblioteca, e o programa para com uma mensagem:

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    std::cout << "notas[5]: " << notas[5] << '\n';
    std::cout << "Fim\n";
}
$ g++ -std=c++23 -Wall -Wextra colchete.cpp -o colchete
$ ./colchete
/usr/include/c++/15/array:210: constexpr std::array<_Tp, _Nm>::value_type& std::array<_Tp, _Nm>::operator[](size_type) [with _Tp = int; long unsigned int _Nm = 5; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed.
Abortado                   (imagem do núcleo gravada) ./colchete
$ echo $?
134

"Asserção __n < this->size() falhou": o índice não é menor que o tamanho. No editor do guia (Clang com libc++), essa verificação não está ligada: o mesmo programa mostra um número qualquer e segue até o Fim. E no próprio g++, basta compilar com otimização (-O2, o que se faz na versão final de um programa) para a verificação sumir e o comportamento indefinido voltar:

$ g++ -std=c++23 -Wall -Wextra -O2 colchete.cpp -o colchete
colchete.cpp: In function ‘int main()’:
colchete.cpp:6:41: warning: array subscript 5 is outside array bounds of ‘std::array<int, 5> [1]’ [-Warray-bounds=]
    6 |     std::cout << "notas[5]: " << notas[5] << '\n';
      |                                         ^
colchete.cpp:5:24: note: at offset 20 into object ‘notas’ of size 20
    5 |     std::array<int, 5> notas = {7, 9, 6, 8, 10};
      |                        ^~~~~
$ ./colchete
notas[5]: 0
Fim

Aqui o compilador até percebeu o índice 5 escrito no código e avisou; com um índice calculado durante a execução, nem isso. Conclusão: não conte com a verificação do [i]. Quando o índice vem de fora (digitado pelo usuário, lido de um arquivo, calculado), use .at(i).

notas[i]notas.at(i)
Índice válidoO elementoO elemento
Índice inválidoComportamento indefinido (o g++ 15 sem -O aborta; com -O2, lixo)Sempre lança std::out_of_range
Quando usarEm laços de 0 a size() − 1, onde o índice é certoQuando o índice vem de fora ou pode estar errado

3. Encher tudo: .fill

.fill(valor) coloca o mesmo valor em todas as posições, de uma vez:

#include <array>
#include <iostream>

int main() {
    std::array<int, 6> placar{};
    for (int x : placar) std::cout << x << ' ';
    std::cout << '\n';

    placar.fill(3);
    for (int x : placar) std::cout << x << ' ';
    std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra fill.cpp -o fill
$ ./fill
0 0 0 0 0 0
3 3 3 3 3 3 

placar{} (sem o =, também vale) cria tudo zerado. O .fill(3) substitui um laço inteiro: útil para "reiniciar" um placar, limpar um tabuleiro (.fill(' ')) ou começar todos os estoques com o mesmo valor.

Missão: a lojinha recebeu 10 unidades de cada um dos 5 produtos. Use .fill para colocar 10 em todo o estoque e depois some tudo com um laço. Deve aparecer Soma: 50.

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> estoque{};
    // encha o estoque aqui

    int soma = 0;
    for (int qtd : estoque) {
        soma += qtd;
    }
    std::cout << "Soma: " << soma << '\n';
}
#include <array>
#include <iostream>

int main() {
    std::array<int, 5> estoque{};
    estoque.fill(10);
    int soma = 0;
    for (int qtd : estoque) {
        soma += qtd;
    }
    std::cout << "Soma: " << soma << '\n';
}

4. Copiar com =

Com std::array, copiar é como copiar um int: um = e pronto. A cópia é independente: mudar uma não muda a outra.

#include <array>
#include <iostream>

int main() {
    std::array<int, 3> original = {7, 9, 6};
    std::array<int, 3> copia = original;
    copia[0] = 10;

    std::cout << "original: ";
    for (int x : original) std::cout << x << ' ';
    std::cout << "\ncopia:    ";
    for (int x : copia) std::cout << x << ' ';
    std::cout << '\n';

    std::array<int, 3> outra = {0, 0, 0};
    outra = original;
    std::cout << "outra:    ";
    for (int x : outra) std::cout << x << ' ';
    std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra copiar.cpp -o copiar
$ ./copiar
original: 7 9 6
copia:    10 9 6
outra:    7 9 6 

Funciona ao criar (copia = original na declaração) e depois (outra = original;). Os dois precisam ter o mesmo tipo e o mesmo tamanho.

Com o array estilo C, isso nem compila:

int main() {
    int original[3] = {7, 9, 6};
    int copia[3] = {};
    copia = original;
}
$ g++ -std=c++23 -Wall -Wextra copiarc.cpp -o copiarc
copiarc.cpp: In function ‘int main()’:
copiarc.cpp:4:11: error: invalid array assignment
    4 |     copia = original;
      |     ~~~~~~^~~~~~~~~~

"Atribuição de array inválida". Um array estilo C não pode receber outro com =; seria preciso copiar elemento por elemento num laço. O Clang do editor diz error: array type 'int[3]' is not assignable ("o tipo array int[3] não aceita atribuição").


5. Comparar com ==

== entre dois std::array compara elemento por elemento: são iguais se todos os valores forem iguais, na mesma ordem. != é o contrário:

#include <array>
#include <iostream>

int main() {
    std::array<int, 3> a = {7, 9, 6};
    std::array<int, 3> b = {7, 9, 6};
    std::array<int, 3> c = {7, 9, 5};
    std::cout << std::boolalpha;
    std::cout << "a == b: " << (a == b) << '\n';
    std::cout << "a == c: " << (a == c) << '\n';
    std::cout << "a != c: " << (a != c) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra comparar.cpp -o comparar
$ ./comparar
a == b: true
a == c: false
a != c: true

(std::boolalpha faz o std::cout mostrar true/false em vez de 1/0, veja Booleanos.) Também funcionam < e >, que comparam como num dicionário: o primeiro elemento diferente decide.

Com o array estilo C, == compila, mas faz outra coisa:

#include <iostream>

int main() {
    int a[3] = {7, 9, 6};
    int b[3] = {7, 9, 6};
    std::cout << std::boolalpha << (a == b) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra compararc.cpp -o compararc
compararc.cpp: In function ‘int main()’:
compararc.cpp:6:39: warning: comparison between two arrays is deprecated in C++20 [-Warray-compare]
    6 |     std::cout << std::boolalpha << (a == b) << '\n';
      |                                     ~~^~~~
compararc.cpp:6:39: note: use unary ‘+’ which decays operands to pointers or ‘&a[0] == &b[0]’ to compare the addresses
$ ./compararc
false

Os valores são iguais, e a resposta foi false. Os dois nomes "decaíram" para os endereços dos arrays, e dois arrays diferentes moram em endereços diferentes. O g++ avisa que essa comparação está "obsoleta desde o C++20" e sugere, na nota, como escrever se você quisesse mesmo comparar endereços. É mais um motivo para usar std::array.


6. Em funções, o tamanho vai junto

O array estilo C perdia o tamanho ao ir para uma função. O std::array não: o tamanho faz parte do tipo. Funções têm aula própria (Funções); por ora, repare que .size() funciona lá dentro:

#include <array>
#include <iostream>

void mostrar(const std::array<int, 5>& notas) {
    std::cout << notas.size() << " notas: ";
    for (int nota : notas) {
        std::cout << nota << ' ';
    }
    std::cout << '\n';
}

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    mostrar(notas);
}
$ g++ -std=c++23 -Wall -Wextra funcao.cpp -o funcao
$ ./funcao
5 notas: 7 9 6 8 10 

O const ... & no parâmetro é a mesma ideia do const auto& do for de intervalo: a função lê o array original, sem copiá-lo e sem poder alterá-lo (veja Por Valor × Por Referência).


Qual usar?

SituaçãoUse
A quantidade é fixa e conhecida ao escrever o programa (dias da semana, casas do tabuleiro, 4 provas)std::array
A quantidade muda durante a execução (itens digitados, linhas de um arquivo)std::vector
Código antigo ou bibliotecas da linguagem CArray estilo C (com cuidado)

Quando dá errado

"terminate called after throwing an instance of 'std::out_of_range'"

O que aconteceu: um .at(i) recebeu um índice que não existe, e ninguém tratou a exceção (seção 2). A linha what() diz o índice pedido (__n) e o tamanho (_Nm).

Como consertar: confira de onde vem o índice (um laço com <=? um número digitado?). Se o índice pode mesmo vir errado, teste antes (if (i < notas.size())) ou trate a exceção com try/catch.

"Assertion '__n < this->size()' failed"

O que aconteceu: um [i] com índice inválido, pego pela verificação que o g++ 15 liga sem otimização. Com -O2, o mesmo erro passaria em silêncio.

Como consertar: a primeira linha da mensagem diz o arquivo da biblioteca, não o seu; procure no seu código o [ ] com o índice errado (o gdb mostra a linha exata).

"wrong number of template arguments (1, should be 2)"
#include <array>

int main() {
    std::array<int> notas = {7, 9, 6};
}
$ g++ -std=c++23 -Wall -Wextra semtam.cpp -o semtam
semtam.cpp: In function ‘int main()’:
semtam.cpp:4:19: error: wrong number of template arguments (1, should be 2)
    4 |     std::array<int> notas = {7, 9, 6};
      |                   ^
In file included from /usr/include/c++/15/bits/stl_algobase.h:64,
                 from /usr/include/c++/15/array:45,
                 from semtam.cpp:1:
/usr/include/c++/15/bits/stl_pair.h:99:12: note: provided for ‘template<class _Tp, long unsigned int _Nm> struct std::array’
   99 |     struct array;
      |            ^~~~~
semtam.cpp:4:21: error: scalar object ‘notas’ requires one element in initializer
    4 |     std::array<int> notas = {7, 9, 6};
      |                     ^~~~~

O que significa: "número errado de argumentos de modelo (1, deveriam ser 2)". O std::array precisa do tipo e do tamanho. A nota mostra a "receita" dele: template<class _Tp, long unsigned int _Nm>, um tipo e um número. O segundo erro é consequência do primeiro.

Como consertar: std::array<int, 3>, ou deixe o compilador deduzir tudo: std::array notas = {7, 9, 6};.

"the value of ‘n’ is not usable in a constant expression"
#include <array>

int main() {
    int n = 5;
    std::array<int, n> notas{};
}
$ g++ -std=c++23 -Wall -Wextra variavel.cpp -o variavel
variavel.cpp: In function ‘int main()’:
variavel.cpp:5:22: error: the value of ‘n’ is not usable in a constant expression
    5 |     std::array<int, n> notas{};
      |                      ^
variavel.cpp:4:9: note: ‘int n’ is not const
    4 |     int n = 5;
      |         ^
variavel.cpp:5:22: note: in template argument for type ‘long unsigned int’
    5 |     std::array<int, n> notas{};
      |                      ^
variavel.cpp:5:24: warning: unused variable ‘notas’ [-Wunused-variable]
    5 |     std::array<int, n> notas{};
      |                        ^~~~~

O que significa: "o valor de n não pode ser usado numa expressão constante"; a nota explica: "int n não é const". O tamanho de um std::array é decidido na compilação, e uma variável comum só tem valor durante a execução.

Como consertar: se o tamanho é fixo, constexpr int n = 5; (veja Constantes). Se ele só é conhecido durante a execução (digitado pelo usuário), use std::vector.

"invalid array assignment" / comparação que dá false com valores iguais

O que aconteceu: você usou = ou == com arrays estilo C, que não copiam nem comparam valores.

Como consertar: troque int a[3] por std::array<int, 3> a; o resto do código continua igual.


Referência

FormaO que faz
std::array<T, N> a = {…};Array de N elementos do tipo T (#include <array>).
std::array<T, N> a{};Todos os elementos zerados.
std::array a = {1, 2, 3};C++17 Tipo e tamanho deduzidos.
a.size()Quantidade de elementos.
a[i]Elemento i, sem garantia de conferência.
a.at(i)Elemento i, conferido: lança std::out_of_range.
a.front() / a.back()Primeiro / último elemento.
a.fill(v)Coloca v em todas as posições.
b = a;Copia todos os elementos (mesmo tipo e tamanho).
a == b, a != b, a < bCompara elemento por elemento.

A lista completa de funções-membro dos containers está em Referência: Containers.


Exercícios

1. Declare

Complete para criar um std::array de 7 double chamado temperaturas, todo zerado:

std::array<> temperaturas{};

2. O acesso conferido

Complete para ler o elemento da posição i de forma conferida:

int x = notas.;

3. Zerar o placar

Complete para colocar 0 em todas as posições de placar:

placar.;

4. std::array<int, 3> a = {1, 2, 3}; auto b = a; b[0] = 9; Quanto vale a[0]?

5. O que acontece com notas.at(10) num std::array<int, 5>, sem try/catch?

6. Com int a[3] = {1, 2, 3}; int b[3] = {1, 2, 3};, o que a == b compara?

Missão final: o backup das notas

Missão: antes de revisar uma nota, a Ana guarda uma cópia. Crie backup como cópia de notas (com =), depois troque notas[1] para 10. Por fim, mostre Nota antiga: 9 (lida do backup) e, comparando os dois arrays com ==, mostre Houve revisão de nota se forem diferentes (ou Nada mudou se forem iguais).

#include <array>
#include <iostream>

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    // 1. crie o backup aqui

    // 2. revise a nota da posição 1

    // 3. mostre a nota antiga e compare

}
#include <array>
#include <iostream>

int main() {
    std::array<int, 5> notas = {7, 9, 6, 8, 10};
    std::array<int, 5> backup = notas;
    notas[1] = 10;
    std::cout << "Nota antiga: " << backup[1] << '\n';
    if (notas == backup) {
        std::cout << "Nada mudou\n";
    } else {
        std::cout << "Houve revisão de nota\n";
    }
}

Resumo

  • std::array<int, 5> é um array de tamanho fixo que sabe o próprio tamanho (.size()), sem custo extra.
  • [i] não tem conferência garantida; .at(i) sempre confere e lança std::out_of_range.
  • .fill(v) enche tudo; .front() e .back() dão as pontas.
  • Copia com = e compara com == elemento por elemento; o array estilo C dá invalid array assignment e compara endereços.
  • Em funções, o tamanho vai junto, porque faz parte do tipo.
  • O tamanho precisa ser constante. Se ele só é conhecido ao rodar, use std::vector.