Vettori in C/C++
In molti problemi di informaticasi ha la necessità di aggregare molti
dati di tipo semplice, per facilitarne la rappresentazione e rendere più
veloce il loro ritrovamento. Per ottenere questi scopi, i dati vengono organizzati
in un insieme a cui viene attribuito il nome di "struttura di dati".
Il vettore è un insieme di elementi omogenei
tra loro. Con una variabile possiamo indicare solo un dato, con il vettore
possiamo indicare tanti dati dello stesso tipo con un solo nome collettivo
di variabile:l'identificatore del vettore. Gli elementi si distinguono uno
dall'altro attraverso l'indice che viene assegnato nel vettore, e che viene
posto accanto all'identificatore del vettore.
Il vettore è quindi un insieme omogeneo di dati: è un esempio di struttura
di dati.
Il vettore si ottiene in C++ aggregando variabili dello stesso tipo.
Un vettore si definisce con la seguente sintassi:
tipo nomeVettore[dimensione];
Alla normale dichiarazione di variabile si aggiunge semplicemente tra parentesi quadre il numero di elementi (dimensione) che compongono il vettore. Per esempio la seguente dichiarazione crea un vettore di dieci coefficienti di tipo intero:
int T[10];
Le componenti di un vettore possono essere non solo numeriche, ma di uno
qualsiasi dei tipi standard del linguaggio C (int,char, float). Se
una variabile è definita di tipo vettore, deve essere sempre usata accompagnando
l'identificatore della variabile con un indice. L'indice è solitamente una
variabile di tipo intero.
Un importante considerazione sui vettori riguarda il passaggio dei parametri
alle funzioni: il passaggio, come parametro,
di un vettore ad una funzione avviene sempre per indirizzo (per riferimento
o referenza).
Quando un vettore viene passato come parametro ad una funzione, in realtà
viene passata la locazione (cioè l'indirizzo) della prima componente del
vettore. All'interno della funzione, il parametro diventa una variabile
come le altre il nome del vettore è a tutti gli effetti una variabile contenente
un indirizzo cioè un puntatore. Di conseguenza quando occorre effettuare
il passaggio per referenza di un vettore alla funzione, basta indicare il
nome del vettore. Si deve osservare che il passaggio per referenza è il
solo modo di passare un vettore come parametro.
Un vettore, in generale, è una sequenza finita di elementi omogenei fra loro (tutti dello stesso tipo)
e contigui (giacenti in memoria uno accanto all'altro). Di solito un vettore
viene rappresentato come una successione di celle:
Una matrice è un vettore a più dimensioni. Nel
caso di due dimensioni, la matrice viene rappresentata con una tabella.
Utilità del vettore
Talvolta, una sequenza di valori può essere gestita senza necessariamente
memorizzarli tutti, ma nella maggior parte dei casi è necessario conservarli
per operare su di loro anche più volte, o in tempi differenti.
Il vettore rende disponibile una serie di celle, nelle quali possono trovare
posto i valori da memorizzare, quindi il vettore è utile perché può conservare
tutti i valori di una sequenza.
È molto utile anche il fatto che per l'intera sequenza di valori ci sia
un'unica entità a cui far riferimento. Proviamo ad esempio a mettere in
ordine crescente 4 valori: servono 4 variabili e una rete di controlli non
banale.
Immaginiamo ora di dover mettere in ordine crescente 100 valori: avremmo
bisogno di 100 variabili, oltre a un'enorme rete di controlli. Con il vettore,
invece, è sufficiente definire una variabile vettore, con una variabile
indice (che indica di volta in volta quale cella intendiamo utilizzare)
e un controllo opportunamente ripetuto in modo ciclico.
Gestione di un vettore
Innanzitutto bisogna dichiarare quanti elementi al massimo potranno trovare
posto nel vettore, e di quale tipo.
Questa operazione viene definita dimensionamento del vettore.
Di solito il numero massimo di elementi emerge da una serie di considerazioni,
e dall'ipotesi di future modifiche al programma.
Quando il testo del problema non specifica la dimensione massima del vettore,
il programmatore, può definirla a piacere, purché secondo logica.
Il numero massimo di elementi dev'essere espresso attraverso una costante
intera (non è variabile a piacere). Di solito, si definisce in cima al programma
una costante dal nome autoesplicativo ("MaxNumeri", ad esempio) che costituirà
un punto di riferimento all'interno dell'intero programma, ma in modo particolare
laddove verrà dichiarato il vettore.
Spesso non è necessario gestire tutti gli elementi del vettore: di solito
le celle a partire dal fondo non vengono utilizzate, perciò bisogna definire
il numero di elementi utili, ossia quanti elementi saranno effettivamente
utilizzati.
Per esempio: dato un vettore di 100 elementi, quelli utili potranno essere
i primi N (ovviamente 0
Dato che un vettore di solito contiene più valori, si pone il problema di
indicare a quale di essi ci vogliamo riferire.
È sempre necessario accompagnare il vettore con una variabile numerica intera,
detta indice, che di volta in volta indica la posizione dell'elemento a
cui ci si riferisce. Ad esempio: Vettore[ Indice ], o Vett
[ i]. Attenzione: contando in modo abituale, si tende a partire da
1, per cui si può pensare che l'indice associato al primo elemento sia 1.
In realtà, in linguaggio C gli indici di vettori e matrici
partono da 0. Per evitare ambiguità, il primo elemento si definisce
0-esimo ("zeresimo"), il secondo 1-esimo ("unesimo"), ecc.
Dichiarazione di vettori e matrici
La sintassi generale è:
tipo "nome del vettore" [ "numero massimo di elementi"
] ;
ESEMPIO 1a - dichiarazione di un vettore con dimensionamento tramite costante
espressamente dichiarata
#define MaxValori 500 //costante
float Vett[MaxValori]; // Vettore di 500
valori float (si potrà ridimensionare, cambiando la costante)
int i;// i è l'indice del vettore
// è sempre consigliabile inizializzare il
vettore azzerando tutte le sue celle
i = 0; // l'indice i deve iniziare da 0
while (i < MaxValori){// la costante può
essere ancora utile
Vett[i]=0;// inserisce il valore 0 nella
cella i-esima
i=i+1;// Aggiorna l'indice i, che dovrà variare
fra 0 e (MaxValori - 1)
}// Il ciclo terminerà quando l'indice ivale
MaxValori, ossia 500
ESEMPIO 1b - dichiarazione del medesimo vettore, ma utilizzando una costante
immediata
float Vett[500]; /* L'indice dovrà variare fra 0
a 499 */
ESEMPIO 2 - dichiara due vettori con dimensioni differenti, più tre variabili
singole, tutto sulla stessa riga
int V1[10], V2[50], A, B, C = 0;
ESEMPIO 3 - dichiara un vettore di 8 interi e contestualmente lo inizializza
int Fib[8] = { 0, 1, 1, 2, 3, 5, 8, 13 };
ESEMPIO 4 - dichiara un vettore di caratteri che, data l'inizializzazione,
viene dimensionato automaticamente a 4
char Oper[ ] = {'+', ' -', '*' , '/'}; /* Dimensionamento
automatico */
ESEMPIO 5 - dichiara un vettore di 10 interi, attribuendo valori solo ai
primi 4 elementi (sovradimensionamento)
int Vett[10] = { 10, -1, 20, -3 }; /* Sovradimensionamento
*/
ESEMPIO 6 - dichiara una matrice di interi, con 10 righe (numerate da 0
a 9) e 8 colonne (numerate da 0 a 7)
int Mat[10][8];
/* La matrice è un vettore a due dimensioni, e serve per memorizzare tabelle
*/
ESEMPIO 7 - dichiara una matrice 2x3 di interi, e contestualmente la inizializza
(riga per riga)
int Mat[2][3] = { {1, 2, 15}, {22, 41, 17} };
ESEMPIO 8 - dichiara una matrice cubica 4x4x4
int Mat[4][4][4];
Scansione di un vettore
"Scansione" deriva dal verbo scandire, ossia visitare
ognuna delle celle del vettore, indicandole tramite un'opportuna variazione
dell'indice.
Il vettore può essere visitato in molti modi: in modo ascendente (a partire
dall'inizio), discendente (a partire dall'ultimo elemento utile), a celle
alternate, ecc., anche in due modi contemporaneamente, secondo il procedimento
che dobbiamo codificare.
Scansione ascendente. L'indice parte da 0 e viene
portato avanti di una cella per volta. Ammettiamo di scandire un vettore
"Vett" di n=6 elementi interi:
Aggiornando l'indice con l'istruzione i=i+1 o i++ , si punta alla cella
successiva:
Ripetendo il procedimento, potremo portare l'indice fino all'ultimo elemento
utile, o anche fino in fondo al vettore.
La scansione ascendente è la più usata e avviene di norma usando il ciclo
for.
Ad es. per inizializzare tutte le celle di un vettore T di n componenti
a 0 si usa:
for(i=0;i < n;i++) T[i]=0;
Scansione discendente. L'indice parte dall'ultimo
elemento utile e viene fatto arretrare di una cella per volta.
Dato che N = 6, l'indice parte da 5:
Aggiornando l'indice con l'istruzione i=i - 1 o i- -, si punta alla cella
precedente:
Ripetendo il procedimento, potremo portare l'indice fino al primo elemento.
Casi di errore
L'errore che più comunemente si compie consiste nel far variare l'indice
fino a oltre l'ultima cella del vettore: in questo caso si ha il cosiddetto
sconfinamento, che, accedendo al vettore, può provocare conseguenze imprevedibili.
In questo schema è illustrato un esempio di sconfinamento: l'indice i dovrebbe
andare da 0 a 9 ma, trovandosi a 10, va a indicare qualcosa che si trova
al di là del vettore (di solito un'altra variabile), per cui Vett[ i ] non
è un valore utile, anzi modificando Vett [ i ] (con i=10), si va a modificare
una variabile che non è Vett.
Un sottocaso di sconfinamento consiste nel far variare l'indice al di là
dell'ultimo elemento utile, anche se internamente al vettore.
Ammettiamo che gli elementi significativi siano solo 6: l'indice posizionato
oltre la 5-esima cella punta ad un valore indefinito. Anche in questo caso,
un accesso a questo elemento avrebbe conseguenze imprevedibili:
Conclusione: in tutti i casi, quando l'indice si trova fuori posizione (come
normalmente succede alla fine di un ciclo di scansione), dobbiamo evitare
di accedere al vettore prima di aver riposizionato l'indice.
Vi possono essere anche errori di dichiarazione del
vettore.
ERRORE 1 - dichiara un vettore senza che il compilatore possa determinare
in alcun modo la dimensione
int Vett[];/* Per il dimensionamento automatico bisogna
indicare un valore */
ERRORE 2 - dichiara un vettore di 1 solo elemento, che serve a poco, dato
che equivale ad una variabile
int Vett[0];
ERRORE 3 - dichiara un vettore presumibilmente troppo ampio
long int Vett[5000000000];
ERRORE 4 - tenta di dimensionare il vettore con un valore proveniente da
una variabile anziché da una costante
int Max = 100;
long int Vett[Max];
Esempi di utilizzo del vettore
Problema: inizializzare da programma un vettore di 10 componenti e stamparlo a video:
const int n=10;//costante n visibile
globalmente
// da tutti i punti del programma anche da altre
funzioni
// diverse dal main()
main(){
//dichiaro e inizializzo il vettore
int T[n]{3,7,11,15,7,2,4,6,12,8};
int i; //contatore
//stampa
for(i=0;i < n;i++)cout << " " << T[i];
}//fine main
Problema: caricare da tastiera un vettore di 5 componenti e stamparlo
a video:
const int n=5;
main(){
int T[n]; //dichiaro il vettore
int i; //contatore
//caricamento
for(i=0;i < n;i++){
cout << "ins:";
cin >> T[i];
}//fine for
//stampa
for(i=0;i < n;i++)cout << " " << T[i];
}//fine main
Problema: caricare in modo random vettore di 5 componenti con numeri variabili
da 0 a 9.
const int n=5;
main(){
int T[n]; //dichiaro il vettore
int i; //contatore
srand(time(0));//inizializzo il generatore
di numeri random
for(i=0;i < n;i++)T[i]=rand%10;
}//fine main