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.
- 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 destd::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::arraypara 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: umstd::array<int, 5>e umstd::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 comtexto.size()em Funções de String). - notas[0]
- O acesso por índice que você já conhece, de
0asize() − 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).
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.
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álido | O elemento | O elemento |
| Índice inválido | Comportamento indefinido (o g++ 15 sem -O aborta; com -O2, lixo) | Sempre lança std::out_of_range |
| Quando usar | Em laços de 0 a size() − 1, onde o índice é certo | Quando 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ção | Use |
|---|---|
| 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 C | Array estilo C (com cuidado) |
Quando dá errado
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.
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).
#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};.
#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.
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
| Forma | O 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 < b | Compara 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:
2. O acesso conferido
Complete para ler o elemento da posição i de forma conferida:
3. Zerar o placar
Complete para colocar 0 em todas as posições de 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çastd::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 assignmente 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.