Vetores dinâmicos em C++ com std::vector
Em programação, um vetor é uma estrutura que armazena diversos valores de um mesmo tipo e permite acessá-los por meio de posições. Os vetores tradicionais de C++ possuem tamanho fixo e previamente definido. Entretanto, em muitas aplicações reais não é possível saber antecipadamente quantos elementos serão necessários. Por exemplo, imagine uma varejista de produtos eletrônicos onde novos produtos podem ser cadastrados ou removidos e seus preços podem ser atualizados continuamente.
Para esse tipo de situação, a biblioteca padrão de C++ oferece std::vector, disponível no cabeçalho <vector>. Trata-se de um contêiner que implementa um arranjo de tamanho variável, mantendo seus elementos em posições contíguas de memória e realizando automaticamente o gerenciamento do espaço necessário (CPP Reference, 2026).
A ideia corresponde ao conceito de tabela dinâmica discutido por Cormen et al. (2012). Nela, quando o espaço disponível se torna insuficiente, uma nova região de memória maior pode ser alocada e os elementos existentes transferidos para ela. Essa estratégia permite que a estrutura cresça conforme a demanda da aplicação.
O que é o std::vector?
Um vector é uma coleção ordenada de elementos de um mesmo tipo cujo tamanho pode variar durante a execução do programa. Diferentemente de um vetor estático, não é necessário estabelecer uma quantidade máxima de elementos no momento da compilação (CPP Reference, 2026).
Para armazenar preços do tipo double, por exemplo, podemos declarar:
vector<double> precos;Também podemos iniciar o vetor com valores:
vector<double> precos = {3499.90, 4599.00, 799.90};Nesse caso, o vetor possui inicialmente três elementos. Os índices continuam seguindo a convenção utilizada nos arranjos tradicionais de C++, ou seja, eles começam em 0. Portanto:
Índice: 0 1 2
Preço: 3499.90 4599.00 799.90
Essa característica mantém o acesso aleatório em tempo constante, isto é, O(1), característica importante dos arranjos e preservada pelo std::vector (CPP Reference, 2026).
Tamanho e capacidade
Dois conceitos importantes precisam ser diferenciados: size() e capacity(). O método size() informa quantos elementos efetivamente existem no vetor. Já o método capacity() indica quantos elementos podem ser armazenados atualmente na memória reservada, antes que uma nova alocação seja necessária (CPP Reference, 2026).
Por exemplo:
cout << precos.size();
cout << precos.capacity();Um vetor pode apresentar:
size = 4
capacity = 8Isso significa que existem quatro preços cadastrados, mas a estrutura possui memória suficiente para armazenar oito elementos antes de precisar crescer novamente.
Essa separação é consequência da estratégia usada por vetores dinâmicos. Se uma nova alocação fosse realizada a cada inserção, o custo seria elevado. Por isso, normalmente é reservado espaço adicional para inserções futuras (CPP Reference, 2026).
Cormen et al. (2012) explicam que uma estratégia comum para tabelas dinâmicas consiste em aumentar significativamente a capacidade quando o armazenamento fica cheio, evitando realocações a cada operação.
Inserção de elementos com push_back()
O método push_back() acrescenta um elemento ao final do vetor, conforme o exemplo abaixo.
precos.push_back(1299.90);Se houver capacidade disponível, o novo valor é simplesmente colocado após o último elemento. Caso contrário, o vetor precisará obter uma região maior de memória e transferir seus elementos para ela (CPP Reference, 2026).
Reserva antecipada de memória
Quando existe uma estimativa razoável da quantidade de elementos, pode-se utilizar reserve():
precos.reserve(100);Isso solicita capacidade para pelo menos 100 elementos, reduzindo a possibilidade de realocações durante as primeiras inserções. Entretanto, reserve() não cria 100 elementos: apenas reserva armazenamento. Portanto, o valor retornado por size() permanece inalterado (CPP Reference, 2026).
Assim, caso façamos o código a seguir:
vector<double> precos;
precos.reserve(100);
cout << precos.size(); // 0
cout << precos.capacity(); // pelo menos 100teremos o resultado indicando quanto de espaço há disponível, apesar de não termos nenhum elemento no vetor em si.
Acesso e alteração dos valores
O acesso pode ser realizado utilizando os símbolos de colchetes – [] – e o número da posição do elemento:
cout << precos[0];Um preço também pode ser alterado diretamente:
precos[0] = 3299.90;Existe também o método at() que nos permite acessar as posições:
cout << precos.at(0);A principal diferença em usar o at() decorre do fato de que ele verifica se o índice solicitado pertence ao intervalo válido do vetor e lança uma exceção caso isso não aconteça. O operador [] não realiza essa verificação, ou seja, o método at() tem uma vantagem.
Remoção de elementos
Um elemento pode ser removido utilizando erase():
precos.erase(precos.begin() + 1);Essa instrução remove o elemento de índice 1.
Considere o conjunto:
[3499.90, 4599.00, 799.90]
Após remover a posição 1, ficamos com:
[3499.90, 799.90]
Como os elementos de vector são armazenados sequencialmente, o acesso direto por índice possui complexidade O(1). Contudo, embora o acesso por índice seja constante, uma remoção no início ou no meio pode exigir o deslocamento dos elementos posteriores. Consequentemente, segundo especificação da CPP Reference (2026), inserções e remoções arbitrárias em um vector podem apresentar complexidade O(n).
Portanto, o contêiner vector é particularmente eficiente quando a maior parte das inserções ocorre no final da estrutura.
Percorrendo um vetor
Um vetor pode ser percorrido pelo índice tradicional:
for (size_t i = 0; i < precos.size(); i++) {
cout << precos[i] << endl;
}Também é possível utilizar um range-based for:
for (double preco : precos) {
cout << preco << endl;
}A escolha depende da necessidade do algoritmo. Quando a posição é relevante, o índice costuma ser conveniente. Quando apenas os valores interessam, o segundo formato produz código mais simples.
Implementação completa
O programa a seguir reúne os principais conceitos em um único exemplo. O cenário representa de forma simplória os preços dos produtos cadastrados por uma varejista de eletrônicos.
#include <iostream>
#include <vector>
using namespace std;
void exibirPrecos(const vector<double>& precos) {
cout << "Precos cadastrados:\n";
for (size_t i = 0; i < precos.size(); i++) {
cout << "Produto " << i + 1
<< ": R$ " << precos[i] << '\n';
}
}
bool alterarPreco(vector<double>& precos, size_t indice, double novoPreco) {
if (indice >= precos.size()) {
return false;
}
precos[indice] = novoPreco;
return true;
}
bool removerPreco(vector<double>& precos, size_t indice) {
if (indice >= precos.size()) {
return false;
}
precos.erase(precos.begin() + indice);
return true;
}
void exibirMatrizPrecos(const vector<vector<double>>& historico) {
cout << "\nHistorico de precos por produto e semana:" << endl;
for (size_t produto = 0; produto < historico.size(); produto++) {
cout << "Produto " << produto + 1 << ": ";
for (double preco : historico[produto]) {
cout << "R$ " << preco << " ";
}
cout << endl;
}
}
int main() {
vector<double> precos = {3499.99, 4599.52, 799.93};
cout << "=== VETOR DINAMICO DE PRECOS ===\n";
exibirPrecos(precos);
cout << "\nQuantidade de elementos (size): " << precos.size() << endl;
cout << "Capacidade alocada (capacity): " << precos.capacity() << endl;
cout << "\nReservando espaco para ate 10 precos..." << endl;
precos.reserve(10);
cout << "Quantidade de elementos (size): " << precos.size() << endl;
cout << "Capacidade alocada (capacity): " << precos.capacity() << endl;
cout << "\nAdicionando um novo produto...\n";
precos.push_back(1299.95);
exibirPrecos(precos);
cout << "\nAlterando o preco do segundo produto...\n";
if (!alterarPreco(precos, 1, 4399.00)) {
cout << "Indice invalido.\n";
}
exibirPrecos(precos);
cout << "\nRemovendo o primeiro produto...\n";
if (!removerPreco(precos, 0)) {
cout << "Indice invalido.\n";
}
exibirPrecos(precos);
vector<vector<double>> historicoPrecos = {
{3499.91, 3399.92, 3299.93},
{4599.94, 4499.95, 4399.96},
{799.97, 749.98, 699.99}
};
exibirMatrizPrecos(historicoPrecos);
return 0;
}O código foi compilado e executado utilizando o padrão C++17 ou superior.
Passagem do vector para funções
Observe a assinatura:
void adicionarPreco(vector<double>& precos, double novoPreco)O símbolo & indica passagem por referência. Isso permite que a função modifique o vetor originalmente criado em main().
Já uma função que apenas consulta os dados pode ser declarada com o operador const no parâmetro:
void exibirPrecos(const vector<double>& precos)Nesse caso, o símbolo & evita realizar uma cópia potencialmente custosa do vetor, enquanto const impede modificações acidentais no conteúdo.
Para estruturas que podem conter grande quantidade de dados, evitar cópias desnecessárias constitui uma decisão relevante tanto para consumo de memória quanto para tempo de execução, aspectos fundamentais na análise de algoritmos (Ziviani, 2006).
É possível construir matrizes dinâmicas?
Sim. Embora a biblioteca padrão não ofereça um tipo denominado matrix, uma matriz dinâmica pode ser representada por um vector contendo outros vectors. Por exemplo, no código a seguir:
vector<vector<double>> matriz;O vetor externo representa as linhas e cada vetor interno representa as colunas.
No exemplo abaixo:
vector<vector<double>> historicoPrecos = {
{3499.90, 3399.90, 3299.90},
{4599.00, 4499.00, 4399.00},
{799.90, 749.90, 699.90}
};podemos interpretar as linhas como produtos e as colunas como semanas, igual a tabela:
| Produto | Semana 1 | Semana 2 | Semana 3 |
|---|---|---|---|
| Produto 1 | R$ 3.499,90 | R$ 3.399,90 | R$ 3.299,90 |
| Produto 2 | R$ 4.599,00 | R$ 4.499,00 | R$ 4.399,00 |
| Produto 3 | R$ 799,90 | R$ 749,90 | R$ 699,90 |
O acesso ao preço do segundo produto na terceira semana seria:
historicoPrecos[1][2]
resultando em:
4399.00
Uma particularidade importante aqui: o vector<vector<double>> não é necessariamente uma matriz armazenada como um único bloco bidimensional contínuo de memória. Isso significa que cada linha é um vector independente.
Por isso, também podemos criar estruturas irregulares, conforme vemos a seguir:
vector<vector<double>> dados = {
{10.0, 20.0},
{30.0, 40.0, 50.0},
{60.0}
};Tecnicamente, isso é um vetor de vetores, mas não uma matriz retangular no sentido matemático.
Quando é indispensável possuir uma região única e contígua de memória, uma alternativa consiste em armazenar a matriz em um único vector<double> e converter coordenadas de linha e coluna em um índice linear. Entretanto, para aplicações introdutórias e para muitos sistemas convencionais, vector<vector<double>> oferece uma representação mais direta e legível.
Conclusão
O contêiner std::vector fornece uma abstração segura e conveniente para trabalhar com arranjos cujo tamanho precisa variar durante a execução. A estrutura combina acesso aleatório em O(1) com inserção ao final em tempo amortizado O(1), deixando para a biblioteca padrão grande parte do gerenciamento de memória (CPP Reference, 2026; Cormen et al., 2012).
Podemos utilizar o std::vector quando a quantidade de elementos variar, ou quando for necessário acesso rápido por índice ou quando as inserções ocorrerem predominantemente no final da estrutura. Segundo o CPP Reference (2026), seu armazenamento contíguo também favorece localidade de memória e compatibilidade com algoritmos projetados para arranjos.
Entretanto, vector não é necessariamente a estrutura ideal para situações que exigem inserções e remoções frequentes no início ou no meio da coleção, pois essas operações podem provocar deslocamento de elementos e apresentar custo linear. A escolha de uma estrutura de dados deve considerar as operações predominantes que serão executadas sobre ela.
Obrigado pela leitura e bons estudos!
Referências
CORMEN, Thomas H.; LEISERSON, Charles E.; RIVEST, Ronald L.; STEIN, Clifford. Algoritmos: teoria e prática. 3. ed. Rio de Janeiro: Elsevier, 2012.
CPP REFERENCE. std::vector. 2026. Disponível em: <https://en.cppreference.com/cpp/container/vector>. Acesso em: 14 set. 2026.
ZIVIANI, Nivio. Projeto de algoritmos: com implementações em Java e C++. Material didático, 2006.


