Menu

Vector in C++: array dinamici con std::vector spiegati

std::vector è l'array ridimensionabile del C++, il contenitore da usare di default. Impara a creare, leggere, far crescere e scorrere un vector, più le insidie dell'invalidazione degli iteratori e degli accessi fuori dai limiti.

Questa pagina include editor eseguibili: modifica, esegui e vedi subito l'output.

Perché vector invece di un array grezzo

Un array grezzo ha una dimensione fissa stabilita in fase di compilazione e si dimentica quanto è lungo nel momento in cui lo passi a una funzione. std::vector risolve entrambi i problemi: è un array ridimensionabile che tiene traccia della propria lunghezza, cresce quando serve e libera da solo la propria memoria. Nel C++ moderno vector è il contenitore di default: usa un array grezzo solo quando hai un motivo preciso per non usarlo.

Includi <vector>, poi dichiaralo con il tipo degli elementi tra parentesi angolari:

scores.size() ti dice sempre la lunghezza attuale: nessun int n separato da tenere allineato e nessun trucco con sizeof. {90, 75, 100, 60} è un inizializzatore con graffe; il vector capisce da solo che gli servono quattro posti.

Creare e inizializzare un vector

Ci sono diversi modi per costruirne uno, a seconda di cosa sai in partenza:

Attenzione alla trappola tonde contro graffe: vector<int> tens(5, 10) crea cinque copie di 10, mentre vector<int> tens{5, 10} crea un vector di due elementi che contiene 5 e 10. Le parentesi tonde significano "dimensione e valore di riempimento"; le graffe significano "proprio questi elementi".

Aggiungere e rimuovere elementi

Il senso di un vector è proprio che cresce. push_back aggiunge in fondo e pop_back rimuove dal fondo:

back() restituisce l'ultimo elemento e front() il primo, in modo più pulito di v[v.size() - 1] e v[0]. Da C++11 puoi anche usare emplace_back(args...) per costruire un elemento sul posto, evitando una copia temporanea per i tipi più pesanti.

Un errore comune da principianti è chiamare front() o back() su un vector vuoto. È undefined behavior, non un errore segnalato: proteggiti sempre prima con if (!v.empty()).

Leggere gli elementi: [] e at()

Accedi a un vector con [] esattamente come a un array. Ma [] non fa alcun controllo sui limiti: un indice fuori intervallo è undefined behavior, che può leggere spazzatura in silenzio o crashare più tardi in un punto che confonde:

vector<int> v = {1, 2, 3};
cout << v[10];   // UNDEFINED BEHAVIOR: nessun controllo, nessun errore

Quando vuoi sicurezza, usa at(). Controlla l'indice e lancia std::out_of_range in caso di accesso errato, una delle eccezioni della libreria standard che puoi catturare, così ottieni un errore chiaro invece di una corruzione:

Regola pratica: usa [] nei cicli stretti in cui hai già dimostrato che l'indice è valido, e at() ai confini in cui potrebbe infilarsi un input sbagliato.

Scorrere un vector

Il modo più pulito per scorrere un vector è il ciclo for basato su intervallo. Prendi gli elementi come const auto& per leggerli senza copiarli, oppure come auto& per modificarli sul posto:

Se ti serve davvero l'indice (per confrontare elementi vicini, ad esempio), usa un classico ciclo con contatore, ma nota che size() restituisce un tipo senza segno (size_t). Confrontarlo con un int i con segno può generare avvisi del compilatore e ritorni a capo sorprendenti, quindi quando puoi preferisci size_t i o un ciclo basato su intervallo:

for (size_t i = 0; i < v.size(); i++) {   // size_t, non int
    cout << v[i];
}

size, capacity e reserve

Un vector tiene due numeri: size() (quanti elementi contiene) e capacity() (quanti ne può contenere prima di dover crescere). Quando un push_back supera la capacità, il vector alloca un blocco più grande, ci copia ogni elemento e libera il vecchio blocco. Ecco perché una serie di push_back costa poco in media, ma ogni singola riallocazione non è gratis:

Se sai più o meno quanti elementi aggiungerai, chiama prima reserve() per evitare le riallocazioni ripetute. Nota che reserve() cambia la capacità, non la dimensione: il vector ha ancora zero elementi finché non li aggiungi.

Questa riallocazione è anche la fonte del bug più subdolo dei vector. Dato che la crescita sposta lo spazio in memoria, qualsiasi puntatore, riferimento o iteratore che hai salvato nel vector diventa pendente dopo un push_back che rialloca:

vector<int> v = {1, 2, 3};
int& first = v[0];     // riferimento dentro il vector
v.push_back(4);        // può riallocare...
cout << first;         // PENDENTE: può puntare a memoria liberata

Lo stesso vale per gli iteratori: non fare push_back o erase mentre iteri con un iteratore salvato. Se devi rimuovere elementi durante un ciclo, usa il valore restituito da erase, oppure l'idioma erase-remove con std::remove.

Prossimo: map

Un vector è perfetto quando cerchi le cose per posizione: elemento 0, elemento 1 e così via. Ma spesso vuoi invece cercarle tramite una chiave: un nome utente, l'ID di un prodotto, una parola. A questo serve std::map. Adesso vedremo map, il contenitore chiave-valore del C++, compreso come inserire, cercare e scorrere gli elementi, e l'insidia di [] che crea un valore predefinito e che fa inciampare quasi tutti.

Domande frequenti

Cos'è un vector in C++?

Un std::vector è un array dinamico (ridimensionabile) della libreria standard del C++. A differenza di un array grezzo, conosce la propria dimensione, cresce in automatico quando aggiungi elementi con push_back e libera la memoria al posto tuo. Includi <vector> e scrivi vector<int> v; per crearne uno.

Qual è la differenza tra [] e at() su un vector C++?

v[i] non fa alcun controllo sui limiti: un indice fuori intervallo è undefined behavior (crash o corruzione silenziosa). v.at(i) controlla l'indice e lancia std::out_of_range se non è valido. Usa [] nei cicli critici in cui hai già verificato l'indice, e at() quando vuoi un errore sicuro e facile da diagnosticare.

push_back invalida puntatori e riferimenti a un vector C++?

Sì, può farlo. Quando un vector esaurisce la capacità, push_back rialloca lo spazio in un nuovo blocco, invalidando ogni puntatore, riferimento e iteratore ai vecchi elementi. Non tenere un riferimento a un elemento attraverso un push_back, e se puoi chiama reserve() in anticipo per evitare riallocazioni a sorpresa.

Illustrazione dei linguaggi di programmazione di Coddy

Impara a programmare con Coddy

INIZIA