C++ Arrays
A Ana tem 5 alunos. Para guardar as notas, ela poderia criar nota1, nota2, nota3, nota4, nota5… e se a turma tiver 40 alunos? E como fazer um laço passar por variáveis com nomes diferentes? A solução é um array (em português, "arranjo" ou "vetor"): uma variável que guarda vários valores do mesmo tipo, lado a lado, cada um numa posição numerada.
- Como criar um array:
int notas[5] = {7, 9, 6, 8, 10};. - Por que as posições (os índices) começam do 0.
- Como ler, trocar e percorrer os valores com
for. - Como perguntar o tamanho com
std::sizeC++17. - O que acontece quando você acessa uma posição que não existe, e como o
-fsanitize=addresspega isso. - Os limites do array "cru": tamanho fixo e esquecimento do tamanho nas funções. E por que o C++ moderno prefere
std::arrayestd::vector.
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.
No C++ moderno, para listas você vai usar quase sempre std::array (tamanho fixo) ou std::vector (tamanho que cresce). Mas o array estilo C, herdado da linguagem C, aparece em muito código existente, em exemplos da internet e por baixo dos próprios std::array e std::vector. Entender como ele funciona, e onde ele é perigoso, explica por que as alternativas existem.
A ideia: uma fileira de armários numerados
Pense numa fileira de armários de escola, todos iguais, colados uns nos outros. A fileira inteira tem um nome ("armários do 3º ano"), e cada armário tem um número na porta. Para pegar algo, você diz o nome da fileira e o número do armário.
| Na escola | No array | Exemplo |
|---|---|---|
| O nome da fileira | O nome do array | notas |
| Quantos armários a fileira tem | O tamanho (fixo!) | 5 |
| O número na porta | O índice | 0, 1, 2, 3, 4 |
| O que está dentro | O elemento (o valor) | 7, 9, 6… |
| "Todos os armários são do mesmo modelo" | Todos os elementos têm o mesmo tipo | int |
Uma diferença importante: os armários do C++ começam do número 0, não do 1.
1. Criar um array e usar os elementos
#include <iostream>
int main() {
int notas[5] = {7, 9, 6, 8, 10};
std::cout << "Primeira nota: " << notas[0] << '\n';
std::cout << "Terceira nota: " << notas[2] << '\n';
std::cout << "Última nota: " << notas[4] << '\n';
notas[2] = 7;
std::cout << "Terceira nota corrigida: " << notas[2] << '\n';
}
- int
- O tipo de cada elemento. Todos os 5 são
int. - notas
- O nome do array inteiro.
- [5]
- O tamanho: quantos elementos cabem. Fica fixo para sempre: um array de 5 nunca vira de 6.
- = {7, 9, 6, 8, 10};
- Os valores iniciais, na ordem das posições: o 7 vai para a posição 0, o 9 para a 1…
- notas[2]
- Usar um elemento: o nome, e entre colchetes o índice.
notas[2]é o terceiro (posições 0, 1, 2). Funciona como uma variável comum: dá para mostrar, usar em contas e atribuir (notas[2] = 7;).
$ g++ -std=c++23 -Wall -Wextra notas.cpp -o notas $ ./notas Primeira nota: 7 Terceira nota: 6 Última nota: 10 Terceira nota corrigida: 7
Por que o índice começa do 0?
O índice não é "o número do aluno", e sim a distância desde o começo do array. O primeiro elemento está a 0 passos do começo; o segundo, a 1 passo; o último de um array de 5, a 4 passos. Por isso:
| Índice | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| Valor | 7 | 9 | 6 | 8 | 10 |
| Em português | 1º | 2º | 3º | 4º | 5º (último) |
Num array de tamanho N, os índices válidos vão de 0 a N − 1. O índice N não existe. Grave isso: é a fonte do erro mais perigoso desta aula.
2. Formas de criar
#include <iostream>
int main() {
int a[5] = {1, 2};
int b[5] = {};
int c[] = {4, 5, 6};
for (int x : a) std::cout << x << ' ';
std::cout << '\n';
for (int x : b) std::cout << x << ' ';
std::cout << '\n';
for (int x : c) std::cout << x << ' ';
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra iniciar.cpp -o iniciar $ ./iniciar 1 2 0 0 0 0 0 0 0 0 4 5 6
| Forma | Resultado |
|---|---|
int a[5] = {1, 2}; | Os que faltam viram 0: 1 2 0 0 0. |
int b[5] = {}; | Todos 0. A forma certa de criar um array "zerado". |
int c[] = {4, 5, 6}; | Sem o tamanho: o compilador conta os valores (aqui, 3). |
int d[5]; | Sem valores: os elementos ficam com lixo. Evite! |
(Os laços acima são o for de intervalo, que funciona com arrays também. Escrevemos cada um em uma linha, sem chaves, só para o exemplo caber; no seu código, use chaves.)
O que é "lixo"? Um array sem valores iniciais fica com o que estava na memória antes, restos de outros programas ou de outras partes deste. Veja uma execução real:
#include <iostream>
int main() {
int d[5];
for (int x : d) std::cout << x << ' ';
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra lixo.cpp -o lixo $ ./lixo 1 0 1075516120 32765 73728
Esses números não significam nada e podem mudar a cada execução ou em outro computador. Nem o -Wall -Wextra avisou. Por isso a regra do guia: sempre inicialize, nem que seja com {}.
Arrays de outros tipos
Qualquer tipo serve: double precos[3] = {12.5, 8.0, 27.0};, char vogais[5] = {'a', 'e', 'i', 'o', 'u'}; ou a lista de compras da Ana em std::string:
#include <iostream>
#include <string>
int main() {
std::string lista[4] = {"arroz", "feijão", "café", "leite"};
for (const auto& item : lista) {
std::cout << "- " << item << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra nomes.cpp -o nomes $ ./nomes - arroz - feijão - café - leite
3. Percorrer com for
A grande vantagem do array é poder usar um laço para passar por todos os elementos. O índice vira a variável do for: do 0 até o tamanho, com < (o "jeito do C++" daquela aula):
#include <iostream>
int main() {
constexpr int TOTAL = 5;
int notas[TOTAL] = {7, 9, 6, 8, 10};
for (int i = 0; i < TOTAL; i++) {
std::cout << "Aluno " << i + 1 << ": " << notas[i] << '\n';
}
double soma = 0.0;
for (int nota : notas) {
soma += nota;
}
std::cout << "Média: " << soma / TOTAL << '\n';
}
- constexpr int TOTAL = 5;
- O tamanho guardado numa constante (veja Constantes). Se a turma mudar para 6 alunos, você troca um número só. O tamanho de um array precisa ser conhecido na hora de compilar, e é isso que o
constexprgarante. - i < TOTAL
- De 0 a 4. Com
<=, o laço tentaria lernotas[5], que não existe. - notas[i]
- O elemento da posição
i. Em cada volta,imuda, e o mesmo código lê um elemento diferente. - i + 1
- Para a tela, a numeração "de gente" (Aluno 1, não Aluno 0).
- for (int nota : notas)
- Quando você não precisa do índice, o for de intervalo é mais simples e não tem como errar o tamanho.
$ g++ -std=c++23 -Wall -Wextra percorrer.cpp -o percorrer $ ./percorrer Aluno 1: 7 Aluno 2: 9 Aluno 3: 6 Aluno 4: 8 Aluno 5: 10 Média: 8
Missão: a turma da tarde tem 4 alunos. Some as notas com um laço e mostre Média: 8.5.
#include <iostream>
int main() {
constexpr int TOTAL = 4;
int notas[TOTAL] = {8, 9, 7, 10};
double soma = 0.0;
// some as notas aqui
std::cout << "Média: " << soma / TOTAL << '\n';
}
#include <iostream>
int main() {
constexpr int TOTAL = 4;
int notas[TOTAL] = {8, 9, 7, 10};
double soma = 0.0;
for (int i = 0; i < TOTAL; i++) {
soma += notas[i];
}
std::cout << "Média: " << soma / TOTAL << '\n';
}
4. Perguntar o tamanho: std::size C++17
Em vez de guardar o tamanho numa constante, você pode perguntar ao array com std::size, que vem do cabeçalho <iterator>:
#include <iostream>
#include <iterator>
int main() {
int notas[] = {7, 9, 6, 8, 10};
std::cout << "A turma tem " << std::size(notas) << " notas\n";
}
$ g++ -std=c++23 -Wall -Wextra tamanho.cpp -o tamanho $ ./tamanho A turma tem 5 notas
Agora, acrescentar um valor nas chaves já atualiza o tamanho. Antes do C++17, a forma tradicional era sizeof(notas) / sizeof(notas[0]) (o tamanho em bytes do array inteiro dividido pelo de um elemento), que você ainda vai encontrar em código antigo.
Um detalhe: std::size devolve um número sem sinal (std::size_t, que nunca é negativo). Compará-lo com um int num for gera um aviso:
#include <iostream>
#include <iterator>
int main() {
int notas[] = {7, 9, 6, 8, 10};
for (int i = 0; i < std::size(notas); i++) {
std::cout << notas[i] << ' ';
}
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra sinal.cpp -o sinal
sinal.cpp: In function ‘int main()’:
sinal.cpp:6:23: warning: comparison of integer expressions of different signedness: ‘int’ and ‘std::size_t’ {aka ‘long unsigned int’} [-Wsign-compare]
6 | for (int i = 0; i < std::size(notas); i++) {
| ~~^~~~~~~~~~~~~~~~~~
"Comparação de inteiros com sinais diferentes: int e std::size_t". Aqui o programa funciona, mas comparar números com e sem sinal pode dar resultados absurdos com valores negativos (veja Inteiros). Três saídas: use o for de intervalo (sem índice, sem problema), declare o índice como std::size_t i, ou guarde o tamanho numa constante int, como na seção 3. O mesmo aviso aparece com o .size() do std::vector.
5. Fora do intervalo: o erro mais perigoso
O que acontece se você pedir notas[5] num array de 5 elementos? Em muitas linguagens, o programa para com uma mensagem clara. Em C++, com arrays crus, ninguém confere. O programa simplesmente lê (ou escreve!) a memória que estiver logo depois do array, seja ela o que for. Este laço tem o erro do "um a mais" (<= no lugar de <, veja for):
#include <iostream>
int main() {
int notas[5] = {7, 9, 6, 8, 10};
for (int i = 0; i <= 5; i++) {
std::cout << notas[i] << ' ';
}
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra fora.cpp -o fora $ ./fora 7 9 6 8 10 0
Nenhum erro, nenhum aviso, e um 0 que não é nota de ninguém apareceu no fim. Esse 0 é o que estava na memória logo depois do array, nesta execução, neste computador. Isso se chama comportamento indefinido (em inglês, undefined behavior): a linguagem não define o que acontece. Pode aparecer um número qualquer, o programa pode travar, ou pode "funcionar" hoje e falhar amanhã. Escrever fora do array é ainda pior: estraga outras variáveis e é a origem de muitas falhas de segurança famosas. O assunto tem aula própria: Comportamento Indefinido.
Pegando o erro com -fsanitize=address
Como o erro é silencioso, existem ferramentas que vigiam a memória enquanto o programa roda. A mais usada é o AddressSanitizer ("higienizador de endereços"), ligado com a opção -fsanitize=address do g++. O -g acrescenta as informações que permitem ao relatório citar o arquivo e a linha:
$ g++ -std=c++23 -Wall -Wextra -g -fsanitize=address fora.cpp -o fora
$ ./fora
=================================================================
==8==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x74898bdf0034 at pc 0x64f3413be447 bp 0x7ffdd98b4da0 sp 0x7ffdd98b4d90
READ of size 4 at 0x74898bdf0034 thread T0
#0 0x64f3413be446 in main /home/ana/cpp/fora.cpp:6
#1 0x78898de2a600 in __libc_start_call_main ../sysdeps/nptl/libc_start_call_main.h:59
#2 0x78898de2a717 in __libc_start_main_impl ../csu/libc-start.c:360
#3 0x64f3413be164 in _start (/home/ana/cpp/fora+0x1164) (BuildId: edb3d6275155e381074227e67ed3cca84cbb56a0)
Address 0x74898bdf0034 is located in stack of thread T0 at offset 52 in frame
#0 0x64f3413be238 in main /home/ana/cpp/fora.cpp:3
This frame has 1 object(s):
[32, 52) 'notas' (line 4) <== Memory access at offset 52 overflows this variable
HINT: this may be a false positive if your program uses some custom stack unwind mechanism, swapcontext or vfork
(longjmp and C++ exceptions *are* supported)
SUMMARY: AddressSanitizer: stack-buffer-overflow /home/ana/cpp/fora.cpp:6 in main
Shadow bytes around the buggy address: « daqui em diante, um mapa da memória (cortado)
==8==ABORTING
$ echo $?
1
Assusta, mas só quatro linhas importam:
- stack-buffer-overflow
- "Estouro de um bloco de memória na pilha": acesso além do fim de um array local.
- READ of size 4
- Foi uma leitura de 4 bytes (o tamanho de um
int). - #0 … in main /home/ana/cpp/fora.cpp:6
- Onde: na função
main, linha 6 dofora.cpp, ostd::cout << notas[i]. - [32, 52) 'notas' (line 4) <== … overflows this variable
- Qual variável: o array
notas, criado na linha 4, ocupa os bytes 32 a 51; o acesso foi no byte 52, logo depois do fim.
O programa foi interrompido no momento do erro (ABORTING, código de saída 1). Nem as notas válidas apareceram: elas estavam esperando para ir para a tela quando o programa foi abortado. Use -fsanitize=address sempre que estiver testando programas com arrays; a aula Sanitizers mostra todos os detalhes.
O relatório acima vem do g++ no terminal. O editor do guia roda o programa de outro jeito (em WebAssembly, veja O Editor do Guia), então o mesmo fora.cpp pode mostrar outro número no lugar do 0. Isso é exatamente o que "comportamento indefinido" quer dizer: não há resultado certo. Para ver o relatório do AddressSanitizer, compile no seu computador como acima.
6. Os limites do array estilo C
Tamanho fixo
O tamanho é decidido quando você escreve o programa e não muda nunca. Não dá para "acrescentar o sexto aluno" num array de 5. Se a quantidade só é conhecida durante a execução (quantos produtos o usuário vai digitar?), o array estilo C não serve. Isso é trabalho para o std::vector.
Você pode encontrar na internet código como int n; std::cin >> n; int notas[n];. Isso não é C++ padrão (chama-se VLA, variable length array, um recurso da linguagem C). O g++ aceita em silêncio, como extensão; o Clang do editor avisa: warning: variable length arrays in C++ are a Clang extension [-Wvla-cxx-extension]. Não use: com um n grande, o programa quebra sem aviso.
Mostrar o array inteiro não funciona
#include <iostream>
int main() {
int notas[5] = {7, 9, 6, 8, 10};
std::cout << notas << '\n';
}
$ g++ -std=c++23 -Wall -Wextra cout.cpp -o cout $ ./cout 0x7ffcf485b480
Em vez das notas, apareceu um número em hexadecimal: o endereço do array na memória (muda a cada execução). Na maioria das situações, o nome de um array "vira" o endereço do primeiro elemento. Essa conversão tem nome, decaimento (decay), e é explicada em Ponteiros e Arrays. Para mostrar os valores, use um laço.
O array esquece o tamanho quando vai para uma função
Esta é a ideia mais importante da seção, mesmo que você ainda não tenha estudado funções. Quando um array é entregue a uma função, por causa do decaimento, a função recebe só o endereço do começo. O tamanho fica para trás. Por isso, este código nem compila:
#include <iostream>
#include <iterator>
void mostrar(int notas[]) {
for (std::size_t i = 0; i < std::size(notas); i++) {
std::cout << notas[i] << ' ';
}
}
int main() {
int notas[5] = {7, 9, 6, 8, 10};
mostrar(notas);
}
$ g++ -std=c++23 -Wall -Wextra funcao.cpp -o funcao
funcao.cpp: In function ‘void mostrar(int*)’:
funcao.cpp:5:42: error: no matching function for call to ‘size(int*&)’
5 | for (std::size_t i = 0; i < std::size(notas); i++) {
| ~~~~~~~~~^~~~~~~
funcao.cpp:5:42: note: there are 2 candidates « e mais umas 20 linhas de notas
Repare na primeira linha: In function ‘void mostrar(int*)’. Você escreveu int notas[], mas o compilador entende int* (um ponteiro para int), e um ponteiro não tem tamanho. O jeito "estilo C" é passar o tamanho junto, num segundo parâmetro, e torcer para ninguém passar o número errado:
#include <iostream>
#include <iterator>
void mostrar(int notas[], int tamanho) {
for (int i = 0; i < tamanho; i++) {
std::cout << notas[i] << ' ';
}
std::cout << '\n';
}
int main() {
int notas[5] = {7, 9, 6, 8, 10};
mostrar(notas, 5);
}
$ g++ -std=c++23 -Wall -Wextra funcao2.cpp -o funcao2 $ ./funcao2 7 9 6 8 10
Se alguém chamar mostrar(notas, 6), volta o problema da seção 5.
Por que preferir std::array e std::vector
Array estilo Cint a[5] | std::arraystd::array<int, 5> | std::vectorstd::vector<int> | |
|---|---|---|---|
| Tamanho | Fixo | Fixo | Cresce e diminui |
| Sabe o próprio tamanho | Só onde foi criado | Sempre (.size()) | Sempre (.size()) |
| Acesso conferido | Nunca | Com .at(i) | Com .at(i) |
Copiar com =, comparar com == | Não | Sim | Sim |
| Vai para funções inteiro | Não (vira ponteiro) | Sim | Sim |
| Quando usar | Código antigo, casos especiais | Quantidade fixa e conhecida | O padrão para listas |
Tudo o que você aprendeu aqui (índices do 0, [i], laços, o perigo do "fora do intervalo") vale igualzinho para os dois. Eles só acrescentam segurança e comodidade. Próximos passos: Arrays Multidimensionais (tabelas), std::array e std::vector.
Quando dá errado
$ ./fora 7 9 6 8 10 0
O que aconteceu: o laço leu uma posição que não existe (seção 5). Quase sempre é <= no lugar de <, ou um laço que começa do 1 em vez do 0.
Como confirmar: compile com -g -fsanitize=address; o relatório aponta a linha.
Como consertar: índices de 0 a tamanho − 1: for (int i = 0; i < TOTAL; i++). Melhor ainda: for de intervalo.
int main() {
int notas[3] = {7, 9, 6, 8};
}
$ g++ -std=c++23 -Wall -Wextra demais.cpp -o demais
demais.cpp: In function ‘int main()’:
demais.cpp:2:31: error: too many initializers for ‘int [3]’
2 | int notas[3] = {7, 9, 6, 8};
| ^
demais.cpp:2:9: warning: unused variable ‘notas’ [-Wunused-variable]
2 | int notas[3] = {7, 9, 6, 8};
| ^~~~~
O que significa: "valores iniciais demais para int [3]" (um array de 3 int). Você declarou 3 posições e passou 4 valores. O aviso de "variável não usada" é só porque o exemplo não usa o array. O Clang do editor diz error: excess elements in array initializer.
Como consertar: aumente o tamanho, tire o valor sobrando ou omita o tamanho (int notas[] = {…}) e deixe o compilador contar.
0x7ffc…
O que aconteceu: std::cout << notas mostra o endereço do array, não os valores.
Como consertar: mostre com um laço, elemento por elemento.
O que aconteceu: o array foi criado sem valores iniciais (int d[5];) e guarda lixo.
Como consertar: int d[5] = {}; zera tudo.
O que aconteceu: você usou std::size num array que foi passado para uma função, onde ele já virou um ponteiro e perdeu o tamanho.
Como consertar: passe o tamanho como parâmetro ou, melhor, use std::array ou std::vector, que levam o tamanho com eles.
Referência
| Forma | O que faz |
|---|---|
int a[5] = {1, 2, 3, 4, 5}; | Cria um array de 5 int com valores. |
int a[5] = {}; | Cria com todos os elementos valendo 0. |
int a[] = {1, 2, 3}; | O tamanho (3) é contado pelo compilador. |
a[i] | O elemento da posição i (de 0 a tamanho − 1). Sem conferência! |
a[i] = valor; | Troca o elemento da posição i. |
std::size(a) | C++17 O tamanho (<iterator>). Só onde o array foi criado. |
for (int i = 0; i < N; i++) | Percorre com índice. |
for (int x : a) | Percorre sem índice. |
g++ … -g -fsanitize=address | Detecta acesso fora do intervalo ao rodar. |
Exercícios
1. O último elemento
Num array int v[8], qual é o índice do último elemento?
2. Criar zerado
Complete para criar um array de 10 int, todos valendo 0:
3. A condição certa
Complete o laço que percorre int precos[6] sem sair do intervalo:
4. Depois de int a[4] = {5, 6};, quanto vale a[3]?
5. O que o C++ faz quando você lê notas[5] num array de 5 elementos?
6. Por que, no C++ moderno, se prefere std::vector ao array estilo C para uma lista de produtos digitados pelo usuário?
Missão final: a maior nota
Missão: percorra o array com um for com índice e descubra a maior nota e em que posição ela está. Mostre Maior nota: 10 (aluno 4); lembre-se de que o aluno 4 está no índice 3. Dica: comece com maior = notas[0] e posicao = 0.
#include <iostream>
int main() {
constexpr int TOTAL = 5;
int notas[TOTAL] = {7, 9, 6, 10, 8};
int maior = notas[0];
int posicao = 0;
// procure a maior nota aqui
std::cout << "Maior nota: " << maior << " (aluno " << posicao + 1 << ")\n";
}
#include <iostream>
int main() {
constexpr int TOTAL = 5;
int notas[TOTAL] = {7, 9, 6, 10, 8};
int maior = notas[0];
int posicao = 0;
for (int i = 1; i < TOTAL; i++) {
if (notas[i] > maior) {
maior = notas[i];
posicao = i;
}
}
std::cout << "Maior nota: " << maior << " (aluno " << posicao + 1 << ")\n";
}
Resumo
- Um array guarda vários valores do mesmo tipo:
int notas[5] = {7, 9, 6, 8, 10};. - Os índices vão de
0atamanho − 1;notas[0]é o primeiro. - Sempre inicialize:
{}zera tudo; sem valores, sobra lixo. - Percorra com
for (int i = 0; i < N; i++)ou, sem índice, comfor (int x : notas).std::size(notas)dá o tamanho C++17. - Acesso fora do intervalo não é conferido: é comportamento indefinido. Teste com
-g -fsanitize=address. - O tamanho é fixo,
std::cout << notasmostra um endereço, e numa função o array vira ponteiro e esquece o tamanho. - Por isso, prefira std::array (tamanho fixo) e std::vector (o padrão para listas).