Por que usar vector em vez de um array nativo
Um array nativo tem um tamanho fixo definido em tempo de compilação e esquece quantos elementos tem assim que você o passa para uma função. std::vector resolve os dois problemas: é um array redimensionável que controla o próprio comprimento, cresce sob demanda e limpa a própria memória. No C++ moderno, vector é o contêiner padrão - recorra a um array nativo só quando tiver um motivo específico para não usá-lo.
Inclua <vector> e então declare um com o tipo do elemento entre colchetes angulares:
scores.size() sempre informa o comprimento atual - sem um int n separado para manter sincronizado e sem truques com sizeof. O {90, 75, 100, 60} é um inicializador entre chaves; o vector descobre que precisa de quatro posições.
Criando e inicializando um vector
Há várias formas de construir um, dependendo do que você sabe de antemão:
Cuidado com a armadilha de parênteses versus chaves: vector<int> tens(5, 10) cria cinco cópias de 10, enquanto vector<int> tens{5, 10} cria um vector de dois elementos contendo 5 e 10. Parênteses significam "tamanho e valor de preenchimento"; chaves significam "estes elementos literais".
Adicionando e removendo elementos
A graça de um vector é que ele cresce. push_back acrescenta no fim e pop_back remove do fim:
back() retorna o último elemento e front() o primeiro - mais limpo do que v[v.size() - 1] e v[0]. Desde o C++11 você também pode usar emplace_back(args...) para construir um elemento no local, o que evita uma cópia temporária para tipos mais pesados.
Um erro comum de iniciante é chamar front() ou back() em um vector vazio. Isso é comportamento indefinido, não um erro - sempre proteja antes com if (!v.empty()).
Lendo elementos: [] versus at()
Você indexa um vector exatamente como um array com []. Mas [] não faz verificação de limites - um índice fora do intervalo é comportamento indefinido, que pode ler lixo silenciosamente ou travar mais tarde em um lugar confuso:
vector<int> v = {1, 2, 3};
cout << v[10]; // COMPORTAMENTO INDEFINIDO - sem verificação, sem erro
Quando quiser segurança, use at(). Ele verifica o índice e lança std::out_of_range em um acesso inválido, então você obtém uma falha clara em vez de corrupção:
Regra geral: use [] em laços apertados onde você já provou que o índice é válido, e at() nas fronteiras onde uma entrada inválida poderia escapar.
Percorrendo um vector
A forma mais limpa de percorrer um vector é o laço for baseado em intervalo. Pegue os elementos por const auto& para ler sem copiar, ou por auto& para editá-los no local:
Se você realmente precisa do índice (para comparar vizinhos, por exemplo), use um laço de contagem clássico - mas note que size() retorna um tipo sem sinal (size_t). Comparar um int i com sinal contra ele pode disparar avisos do compilador e estouros surpreendentes, então prefira size_t i ou um laço baseado em intervalo quando puder:
for (size_t i = 0; i < v.size(); i++) { // size_t, não int
cout << v[i];
}
size, capacity e reserve
Um vector mantém dois números: size() (quantos elementos ele contém) e capacity() (quantos ele pode conter antes de precisar crescer). Quando um push_back excede a capacidade, o vector aloca um bloco maior, copia todos os elementos e libera o bloco antigo. É por isso que push_back repetido é barato de forma amortizada, mas cada realocação individual não é gratuita:
Se você sabe mais ou menos quantos elementos vai adicionar, chame reserve() primeiro para pular as realocações repetidas. Note que reserve() muda a capacidade, não o tamanho - o vector ainda tem zero elementos até você inseri-los.
Essa realocação também é a origem do bug mais cruel dos vectors. Como crescer move o armazenamento, qualquer ponteiro, referência ou iterador que você salvou para dentro do vector fica pendente após um push_back que realoca:
vector<int> v = {1, 2, 3};
int& first = v[0]; // referência para dentro do vector
v.push_back(4); // pode realocar...
cout << first; // PENDENTE - pode apontar para memória liberada
O mesmo vale para iteradores: não faça push_back nem erase enquanto itera com um iterador salvo. Se você precisa remover itens durante o laço, use o valor de retorno de erase, ou o idiom erase-remove com std::remove.
Próximo: map
Um vector é perfeito quando você procura coisas por posição - elemento 0, elemento 1, e assim por diante. Mas muitas vezes você quer procurar as coisas por uma chave: um nome de usuário, um ID de produto, uma palavra. É para isso que serve o std::map. A seguir vamos cobrir map, o contêiner chave-valor do C++, incluindo como inserir, buscar e iterar entradas - e a pegadinha de que [] cria um valor padrão, que pega quase todo mundo de surpresa.
Perguntas frequentes
O que é um vector em C++?
Um std::vector é um array dinâmico (redimensionável) da Biblioteca Padrão do C++. Diferente de um array nativo, ele conhece o próprio tamanho, cresce automaticamente quando você adiciona elementos com push_back e libera a memória por você. Inclua <vector> e escreva vector<int> v; para criar um.
Qual é a diferença entre [] e at() em um vector do C++?
v[i] não faz verificação de limites - um índice fora do intervalo é comportamento indefinido (travamento ou corrupção silenciosa). v.at(i) verifica o índice e lança std::out_of_range se ele for inválido. Use [] em laços de desempenho crítico onde você já validou o índice, e at() quando quiser uma falha segura e fácil de depurar.
push_back invalida ponteiros e referências para dentro de um vector do C++?
Sim, potencialmente. Quando um vector fica sem capacidade, push_back realoca seu armazenamento para um novo bloco, o que invalida todos os ponteiros, referências e iteradores para os elementos antigos. Não guarde uma referência a um elemento ao longo de um push_back, e chame reserve() de antemão quando puder para evitar realocações inesperadas.