C++ std::vector
Arrays e std::array têm tamanho fixo: você decide quantos elementos cabem quando escreve o programa. Mas quantos itens a Ana vai pôr na lista de compras hoje? Quantos produtos o cliente vai colocar no carrinho? Não dá para saber. O std::vector (vector = vetor) é uma lista que cresce e diminui enquanto o programa roda. Ele é o container padrão do C++: quando precisar de uma lista e não tiver um bom motivo para outra coisa, use std::vector.
- Como criar um vetor vazio, com valores ou com N elementos.
- Como acrescentar no fim (
push_back) e tirar do fim (pop_back). - Como saber o tamanho (
size), se está vazio (empty) e como esvaziar (clear). - Como acessar com
[i],at(i),front()eback(). - Como percorrer, inclusive o aviso de "sinais diferentes" com
size(). - Como inserir e apagar no meio com
inserteerase(o básico debegin()). - A ideia de
capacityereserve.
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. Você já viu o std::vector de passagem no for de intervalo; agora é a vez dele.
A ideia: um caderno de lista de compras
O array é como uma cartela de ovos: tem 12 buracos e pronto. O std::vector é como uma lista num caderno:
- começa vazia, ou com alguns itens;
- você escreve um item novo no fim sempre que lembra de algo;
- pode riscar o último, ou um do meio;
- a qualquer momento, conta quantos itens há.
E, quando a página enche, o caderno "vira a folha" sozinho: o vetor arruma mais espaço automaticamente, sem você precisar se preocupar.
1. Criar um vetor
#include <iostream>
#include <vector>
int main() {
std::vector<int> vazio;
std::vector<int> notas = {7, 9, 6};
std::vector<int> zeros(5);
std::vector<int> setes(3, 7);
std::cout << "vazio: " << vazio.size() << " itens\n";
std::cout << "notas: " << notas.size() << " itens\n";
std::cout << "zeros: ";
for (int x : zeros) std::cout << x << ' ';
std::cout << "\nsetes: ";
for (int x : setes) std::cout << x << ' ';
std::cout << '\n';
}
- #include <vector>
- O cabeçalho do
std::vector. - std::vector<int>
- "Um vetor de
int". Entre< >vai só o tipo dos elementos. Não há tamanho: ele muda durante a execução. - vazio;
- Sem nada depois: um vetor vazio, com 0 elementos. Diferente de
int x;, aqui não há lixo: um vetor sempre começa num estado válido. - notas = {7, 9, 6};
- Com valores iniciais, entre chaves.
- zeros(5)
- Com parênteses: 5 elementos, todos 0.
- setes(3, 7)
- 3 elementos, todos valendo 7.
$ g++ -std=c++23 -Wall -Wextra criar.cpp -o criar $ ./criar vazio: 0 itens notas: 3 itens zeros: 0 0 0 0 0 setes: 7 7 7
Com std::vector, (5) quer dizer "5 elementos" e {5} quer dizer "um elemento que vale 5":
#include <iostream>
#include <vector>
int main() {
std::vector<int> a(5);
std::vector<int> b{5};
std::cout << "a(5): " << a.size() << " itens\n";
std::cout << "b{5}: " << b.size() << " item, que vale " << b[0] << '\n';
}
$ g++ -std=c++23 -Wall -Wextra chaves.cpp -o chaves
$ ./chaves
a(5): 5 itens
b{5}: 1 item, que vale 5
Regra prática: chaves para listar os valores; parênteses para dizer quantos.
2. Acrescentar no fim: push_back
O jeito mais comum de usar um vetor é começar vazio e ir acrescentando. push_back(valor) ("empurrar para trás") coloca o valor no fim da lista, e o tamanho aumenta em 1:
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas;
std::cout << "Começo: " << notas.size() << " notas\n";
notas.push_back(7);
notas.push_back(9);
notas.push_back(6);
std::cout << "Depois de 3 push_back: " << notas.size() << " notas\n";
for (int nota : notas) {
std::cout << nota << ' ';
}
std::cout << '\n';
}
$ g++ -std=c++23 -Wall -Wextra push.cpp -o push $ ./push Começo: 0 notas Depois de 3 push_back: 3 notas 7 9 6
.size() responde quantos elementos há agora. Os elementos ficam na ordem em que foram acrescentados.
A lista de compras da Ana
Juntando push_back com o while (true) e o break da aula break e continue, e o getline (que lê uma linha inteira, com espaços), sai um programa de verdade: a Ana digita os itens, um por linha, e uma linha vazia termina a lista.
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> lista;
std::string item;
std::cout << "Digite os itens (linha vazia para terminar):\n";
while (true) {
std::cout << "Item: ";
std::getline(std::cin, item);
if (item.empty()) {
break;
}
lista.push_back(item);
}
std::cout << "\nLista de compras (" << lista.size() << " itens):\n";
for (std::size_t i = 0; i < lista.size(); i++) {
std::cout << i + 1 << ". " << lista[i] << '\n';
}
}
$ g++ -std=c++23 -Wall -Wextra lista.cpp -o lista $ ./lista Digite os itens (linha vazia para terminar): Item: arroz Item: feijão Item: café com leite Item: detergente Item: Lista de compras (4 itens): 1. arroz 2. feijão 3. café com leite 4. detergente
std::vector<std::string>: um vetor de textos. Qualquer tipo serve.item.empty()é verdadeiro quando a linha veio vazia (só Enter): é a saída do laço.- Antes de rodar, o programa não sabe quantos itens haverá. Com um array, você teria que chutar um tamanho máximo; com o vetor, tanto faz se forem 2 ou 2000.
- O laço final usa índice para numerar. O tipo
std::size_té o tipo que o.size()devolve; a seção 5 explica por quê.
Missão: o vetor lista começa vazio. Acrescente "arroz", "feijão" e "café" com push_back. O resto do programa mostra o tamanho e os itens.
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> lista;
// acrescente os itens aqui
std::cout << lista.size() << " itens\n";
for (const auto& item : lista) {
std::cout << "- " << item << '\n';
}
}
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> lista;
lista.push_back("arroz");
lista.push_back("feijão");
lista.push_back("café");
std::cout << lista.size() << " itens\n";
for (const auto& item : lista) {
std::cout << "- " << item << '\n';
}
}
3. Acessar: [i], at(i), front(), back()
O acesso é igual ao do std::array: [i] sem garantia de conferência, .at(i) sempre conferido. Os índices vão de 0 a size() − 1:
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas = {7, 9, 6};
notas[1] = 10;
std::cout << "notas[1]: " << notas[1] << '\n';
std::cout << "notas.at(2): " << notas.at(2) << '\n';
std::cout << "notas.at(5): " << notas.at(5) << '\n';
}
$ g++ -std=c++23 -Wall -Wextra acesso.cpp -o acesso $ ./acesso notas[1]: 10 notas.at(2): 6 terminate called after throwing an instance of 'std::out_of_range' what(): vector::_M_range_check: __n (which is 5) >= this->size() (which is 3) Abortado (imagem do núcleo gravada) ./acesso $ echo $? 134
A mensagem diz: na conferência de intervalo do vetor (_M_range_check), o índice pedido (5) é maior ou igual ao tamanho (3). Como no std::array, a exceção pode ser tratada com try/catch (veja std::array e Exceções).
No editor do guia, que usa o Clang com a biblioteca libc++, a mesma exceção aparece como libc++abi: terminating due to uncaught exception of type std::out_of_range: vector.
[i] não cria elementos
Num vetor vazio, notas[0] = 7; não acrescenta nada: a posição 0 não existe, e escrever nela é comportamento indefinido. O g++ 15 sem otimização pega o erro e aborta (veja Quando dá errado); com -O2, ninguém confere, e no editor do guia também não: lá, o programa parece funcionar, o que é o pior tipo de erro. Para acrescentar, sempre push_back.
As pontas e tirar do fim
.front() é o primeiro elemento; .back(), o último. .pop_back() ("tirar de trás") remove o último, como tirar o prato de cima de uma pilha:
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> pilha = {"prato azul", "prato verde", "prato branco"};
std::cout << "Primeiro: " << pilha.front() << '\n';
std::cout << "Último: " << pilha.back() << '\n';
pilha.pop_back();
std::cout << "Depois do pop_back, último: " << pilha.back()
<< " (" << pilha.size() << " itens)\n";
}
$ g++ -std=c++23 -Wall -Wextra pontas.cpp -o pontas $ ./pontas Primeiro: prato azul Último: prato branco Depois do pop_back, último: prato verde (2 itens)
Repare que pop_back() não devolve o elemento removido: se você precisa dele, leia com back() antes. E nunca chame pop_back(), front() ou back() num vetor vazio; confira com empty() antes.
4. Vazio ou não: empty e clear
#include <iostream>
#include <vector>
int main() {
std::vector<int> carrinho = {3, 1, 2};
std::cout << std::boolalpha;
std::cout << "Vazio? " << carrinho.empty() << " (" << carrinho.size() << " itens)\n";
carrinho.clear();
std::cout << "Vazio? " << carrinho.empty() << " (" << carrinho.size() << " itens)\n";
if (carrinho.empty()) {
std::cout << "O carrinho está vazio.\n";
}
}
$ g++ -std=c++23 -Wall -Wextra limpar.cpp -o limpar $ ./limpar Vazio? false (3 itens) Vazio? true (0 itens) O carrinho está vazio.
.empty()pergunta "está vazio?" e devolvetrueoufalse. É a forma recomendada (mais clara quesize() == 0)..clear()apaga todos os elementos. O vetor continua existindo, pronto para receber outros.
Missão: a entrada traz as notas 7 9 6 8 e um 0 no fim. Leia num laço, guardando cada nota no vetor com push_back até vir o 0. Depois, se o vetor não estiver vazio, mostre quantas notas há e a maior: 4 notas, maior: 9.
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas;
// leia as notas até o 0
if (!notas.empty()) {
int maior = notas.front();
for (int n : notas) {
if (n > maior) {
maior = n;
}
}
std::cout << notas.size() << " notas, maior: " << maior << '\n';
}
}
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas;
while (true) {
int n = 0;
std::cin >> n;
if (n == 0) {
break;
}
notas.push_back(n);
}
if (!notas.empty()) {
int maior = notas.front();
for (int n : notas) {
if (n > maior) {
maior = n;
}
}
std::cout << notas.size() << " notas, maior: " << maior << '\n';
}
}
5. Percorrer
Tudo o que você viu nas aulas de laços vale para o vetor:
- Sem índice (o mais comum):
for (const auto& item : lista)para ler,for (auto& item : lista)para alterar (veja for de intervalo). - Com índice:
for (std::size_t i = 0; i < lista.size(); i++), quando você precisa da posição.
Por que std::size_t e não int? Porque .size() devolve um número sem sinal, e o g++ avisa quando você compara os dois:
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas = {7, 9, 6};
for (int i = 0; i < notas.size(); 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::vector<int>::size_type’ {aka ‘long unsigned int’} [-Wsign-compare]
6 | for (int i = 0; i < notas.size(); i++) {
| ~~^~~~~~~~~~~~~~
"Comparação de inteiros com sinais diferentes: int e o size_type do vetor (que é long unsigned int)". O programa funciona, mas o aviso é bem-vindo: misturar com e sem sinal dá resultados absurdos com números negativos (por exemplo, i < notas.size() - 1 num vetor vazio, veja Inteiros). As saídas: std::size_t i, ou, se não precisa do índice, o for de intervalo.
6. Inserir e apagar no meio: insert e erase
push_back e pop_back trabalham no fim. Para mexer no meio, existem insert (inserir) e erase (apagar). Eles não recebem um índice, e sim um iterador: um "marcador de posição" dentro do vetor. Por enquanto, basta saber dois:
lista.begin(): marca o primeiro elemento.lista.begin() + 2marca o elemento de índice 2.lista.end(): marca a posição depois do último (não é um elemento!).lista.end() - 1marca o último.
#include <iostream>
#include <string>
#include <vector>
void mostrar(const std::vector<std::string>& lista) {
for (const auto& item : lista) {
std::cout << item << " | ";
}
std::cout << '\n';
}
int main() {
std::vector<std::string> lista = {"arroz", "feijão", "café"};
mostrar(lista);
lista.insert(lista.begin(), "pão");
mostrar(lista);
lista.insert(lista.begin() + 2, "leite");
mostrar(lista);
lista.erase(lista.begin() + 1);
mostrar(lista);
lista.erase(lista.end() - 1);
mostrar(lista);
}
$ g++ -std=c++23 -Wall -Wextra inserir.cpp -o inserir $ ./inserir arroz | feijão | café | pão | arroz | feijão | café | pão | arroz | leite | feijão | café | pão | leite | feijão | café | pão | leite | feijão |
- insert(lista.begin(), "pão")
- Insere
"pão"antes da posição marcada: no começo. Todos os outros andam uma casa para a frente. - insert(lista.begin() + 2, "leite")
- Insere antes do índice 2: o
"leite"vira o novo índice 2. - erase(lista.begin() + 1)
- Apaga o elemento de índice 1 (o
"arroz", naquele momento). Os seguintes andam uma casa para trás. - erase(lista.end() - 1)
- Apaga o último (o mesmo que
pop_back()).
A função mostrar existe só para não repetir o laço cinco vezes; const std::vector<std::string>& quer dizer "leia este vetor sem copiá-lo" (veja Funções e Por Valor × Por Referência). Os iteradores têm aula própria: Iteradores.
Para abrir espaço no começo de um vetor de 1 milhão de itens, o computador precisa mover 1 milhão de itens uma casa para a frente. No fim (push_back/pop_back), não há nada para mover. Por isso o vetor é ótimo para acrescentar e tirar do fim, e menos bom para mexer no começo. Se o seu programa vive inserindo no começo, existem outros containers (list e deque).
7. Por dentro: capacity e reserve
Como o vetor "cresce"? Ele reserva um bloco de memória com espaço para alguns elementos. Quando o bloco enche, ele pede um bloco maior (no g++, o dobro), copia tudo para lá e libera o antigo. .size() diz quantos elementos existem; .capacity() ("capacidade") diz para quantos há espaço reservado:
#include <iostream>
#include <vector>
int main() {
std::vector<int> v;
for (int i = 1; i <= 10; i++) {
v.push_back(i);
std::cout << "size " << v.size() << ", capacity " << v.capacity() << '\n';
}
std::vector<int> w;
w.reserve(100);
std::cout << "Depois do reserve(100): size " << w.size()
<< ", capacity " << w.capacity() << '\n';
}
$ g++ -std=c++23 -Wall -Wextra capacidade.cpp -o capacidade $ ./capacidade size 1, capacity 1 size 2, capacity 2 size 3, capacity 4 size 4, capacity 4 size 5, capacity 8 size 6, capacity 8 size 7, capacity 8 size 8, capacity 8 size 9, capacity 16 size 10, capacity 16 Depois do reserve(100): size 0, capacity 100
A capacidade pulou 1 → 2 → 4 → 8 → 16: cada pulo é uma "mudança de casa". Como os pulos dobram, eles ficam raros conforme o vetor cresce, e o push_back continua rápido em média. Se você já sabe que vão vir uns 100 elementos, .reserve(100) reserva o espaço de uma vez: o tamanho continua 0, mas os próximos 100 push_back não precisam mudar de casa. É só uma otimização; programas pequenos não precisam dela. (Os números exatos da capacidade dependem da biblioteca; estes são os do g++.)
O container padrão
| Situação | Use |
|---|---|
| Uma lista qualquer, de tamanho que muda (o caso comum) | std::vector |
| Quantidade fixa, conhecida ao escrever o programa | std::array |
| Uma tabela de linhas e colunas de tamanho variável | std::vector<std::vector<T>> (veja Arrays Multidimensionais) |
| Achar um valor por uma chave (nome → telefone) | std::map |
| Muitas inserções no começo e no fim | std::deque |
Na dúvida, comece com std::vector: ele é simples, rápido e é o que a maioria dos programas C++ usa. A lista completa de funções do vetor (e dos outros containers) está em Referência: Containers, e o assunto continua em O que é a STL e em Algoritmos, onde você vai ordenar e procurar em vetores com uma linha.
Quando dá errado
O que aconteceu: um .at(5) num vetor de 3 elementos lançou std::out_of_range, e o programa foi encerrado (código 134).
Como consertar: confira de onde vem o índice; índices válidos vão de 0 a size() − 1. Se o índice pode estar errado, teste antes (if (i < v.size())) ou trate a exceção.
[ ])
#include <iostream>
#include <vector>
int main() {
std::vector<int> notas;
notas[0] = 7;
std::cout << notas[0] << '\n';
}
$ g++ -std=c++23 -Wall -Wextra colchete.cpp -o colchete $ ./colchete /usr/include/c++/15/bits/stl_vector.h:1263: constexpr std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::reference std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::operator[](size_type) [with _Tp = int; _Alloc = std::allocator<int>; reference = int&; size_type = long unsigned int]: Assertion '__n < this->size()' failed. Abortado (imagem do núcleo gravada) ./colchete $ echo $? 134
O que significa: no meio do texto comprido, o que importa é o fim: operator[] (o [ ]) e Assertion '__n < this->size()' failed, "a conferência de que o índice é menor que o tamanho falhou". O vetor está vazio; não existe posição 0. Essa conferência é do g++ 15 sem otimização; com -O2, o programa seguiria escrevendo em memória que não é dele.
Como consertar: para acrescentar, notas.push_back(7);. Se queria um vetor com posições prontas, crie com tamanho: std::vector<int> notas(5);.
pop_back num vetor vazio)
#include <iostream>
#include <vector>
int main() {
std::vector<int> v;
v.pop_back();
std::cout << "Fim\n";
}
$ g++ -std=c++23 -Wall -Wextra popvazio.cpp -o popvazio $ ./popvazio /usr/include/c++/15/bits/stl_vector.h:1459: constexpr void std::vector< <template-parameter-1-1>, <template-parameter-1-2> >::pop_back() [with _Tp = int; _Alloc = std::allocator<int>]: Assertion '!this->empty()' failed. Abortado (imagem do núcleo gravada) ./popvazio $ echo $? 134
O que significa: "a conferência de que o vetor não está vazio falhou", dentro do pop_back(). Não há o que tirar. O mesmo vale para front() e back() num vetor vazio. Com -O2, é comportamento indefinido sem aviso.
Como consertar: if (!v.empty()) { v.pop_back(); }.
#include <vector>
int main() {
std::vector<int> notas;
notas.push_back("sete");
}
$ g++ -std=c++23 -Wall -Wextra tipo.cpp -o tipo
tipo.cpp: In function ‘int main()’:
tipo.cpp:5:20: error: no matching function for call to ‘push_back(const char [5])’
5 | notas.push_back("sete");
| ~~~~~~~~~~~~~~~^~~~~~~~
tipo.cpp:5:20: note: there are 2 candidates « (as notas sobre os 2 candidatos foram cortadas)
tipo.cpp:5:21: error: invalid conversion from ‘const char*’ to ‘std::vector<int>::value_type’ {aka ‘int’} [-fpermissive]
5 | notas.push_back("sete");
| ^~~~~~
| |
| const char*
O que significa: "nenhuma função push_back aceita um texto". O vetor é de int (value_type, o tipo dos elementos, é int), e "sete" é um texto. O compilador lista as versões de push_back que existem e explica por que nenhuma serve: não dá para converter texto em int.
Como consertar: acrescente um valor do tipo certo (notas.push_back(7);) ou, se a lista é de textos, declare std::vector<std::string>.
O que aconteceu: um int comparado com .size(), que é sem sinal (seção 5).
Como consertar: std::size_t i no laço, ou for de intervalo.
O que aconteceu: std::vector<int> v{5}; cria um elemento que vale 5; std::vector<int> v(5); cria 5 zeros.
Como consertar: chaves para listar valores, parênteses para dizer quantos.
O que acontece: fazer push_back, insert ou erase no mesmo vetor dentro de um for de intervalo que o percorre é comportamento indefinido: se o vetor "mudar de casa" (seção 7), o laço continua olhando a casa antiga.
Como evitar: junte as mudanças num segundo vetor e aplique depois do laço. Formas seguras de apagar vários itens estão em Algoritmos.
Referência
| Forma | O que faz |
|---|---|
std::vector<T> v; | Vetor vazio (#include <vector>). |
std::vector<T> v = {a, b, c}; | Com valores. |
std::vector<T> v(n); / v(n, x) | n elementos zerados / valendo x. |
v.push_back(x) | Acrescenta x no fim. |
v.pop_back() | Remove o último (vetor não pode estar vazio). |
v.size() | Quantos elementos há (sem sinal). |
v.empty() | true se não há elementos. |
v.clear() | Remove todos. |
v[i] / v.at(i) | Elemento i: sem conferência garantida / conferido (std::out_of_range). |
v.front() / v.back() | Primeiro / último. |
v.insert(v.begin() + i, x) | Insere x antes do índice i. |
v.erase(v.begin() + i) | Apaga o elemento de índice i. |
v.capacity() / v.reserve(n) | Espaço reservado / reservar espaço para n. |
b = a;, a == b | Copia / compara elemento por elemento (como no std::array). |
Referência completa: Containers.
Exercícios
1. Acrescentar
Complete para acrescentar 42 no fim de v:
2. Está vazio?
Complete o teste para mostrar a mensagem só se o carrinho estiver vazio:
3. Apagar o primeiro
Complete para apagar o primeiro elemento de lista:
4. Quanto mede?
Depois de std::vector<int> v = {1, 2, 3}; v.push_back(4); v.pop_back(); v.pop_back();, quanto vale v.size()?
5. O que std::vector<int> v(3); cria?
6. Num vetor vazio, v[0] = 5;…
7. Para uma lista cujo tamanho só se sabe quando o programa roda, o container recomendado é…
Missão final: riscar um item da lista
Missão: o "sabão" entrou por engano na lista de compras, na posição de índice 1. Apague-o com erase e lista.begin(). A lista deve ficar arroz, feijão, café, nessa ordem.
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> lista = {"arroz", "sabão", "feijão", "café"};
// apague o sabão aqui
for (const auto& item : lista) {
std::cout << "- " << item << '\n';
}
}
#include <iostream>
#include <string>
#include <vector>
int main() {
std::vector<std::string> lista = {"arroz", "sabão", "feijão", "café"};
lista.erase(lista.begin() + 1);
for (const auto& item : lista) {
std::cout << "- " << item << '\n';
}
}
Resumo
std::vector<T>é uma lista que cresce e diminui durante a execução; é o container padrão do C++.- Crie vazio, com
{valores}, ou com(quantidade); chaves e parênteses são diferentes. push_backacrescenta no fim;pop_backtira do fim;inserteerasemexem no meio, combegin() + i.size()conta,empty()pergunta se está vazio,clear()esvazia.[i]não tem conferência garantida;at(i)lançastd::out_of_range.[i]nunca acrescenta elementos.- Percorra com
for (const auto& x : v); com índice, usestd::size_tpara evitar o aviso de sinais. capacityé o espaço reservado;reserve(n)reserva antes. Referência completa: Containers.