edutecnica

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<=100). Il numero di elementi utili è variabile, e normalmente viene definito tramite acquisizione (più raramente con un'inizializzazione).

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