Ogni identificatore che il programmatore intende utilizzare in un
programma C++, sia esso per una variabile, una costante simbolica, di tipo o di
funzione (fanno eccezione le etichette), va dichiarato prima di essere utilizzato.
Ci sono diversi motivi che giustificano la necessita` di una dichiarazione; nel caso
di variabili, costanti o tipi:
- consente di stabilire la quantita` di memoria necessaria alla memorizzazione
di un oggetto;
- determina l'interpretazione da attribuire ai vari bit che compongono la
regione di memoria utilizzata per memorizzare l'oggetto, l'insieme dei valori
che puo` assumere e le operazioni che possono essere fatte su di esso;
- permette l'esecuzione di opportuni controlli per determinare errori semantici;
- fornisce eventuali suggerimenti al compilatore;
nel caso di funzioni, invece una dichiarazione:
- determina numero e tipo dei parametri e il tipo del valore restituito;
- consente controlli per determinare errori semantici;
Le dichiarazioni hanno anche altri compiti che saranno chiariti in seguito.
Tipi primitivi
Un tipo e` una coppia <V, O>, dove V
e` un insieme di valori e O e` un insieme di operazione per la creazione e la
manipolazione di elementi di V.
In un linguaggio di programmazione i tipi rappresentano le categorie di informazioni
che il linguaggio consente di manipolare. Il C++ fornisce quattro tipi fondamentali:
Il tipo char e` utilizzato per rappresentare piccoli interi e caratteri;
int e` utilizzato per rapresentare interi in un intervallo piu` grande di
char; infine float e double rappresentano entrambi valori in
virgola mobile, float per valori in precisione semplice e double per
quelli in doppia precisione.
Ai tipi fondamentali e` possibile applicare i qualificatori signed,
unsigned, short e long per selezionare differenti intervalli
di valori; essi tuttavia non sono liberamente applicabili a tutti i tipi:
short si applica solo a int, signed e unsigned
solo a char e int e infine long solo a int e
double. In definitiva sono disponibili i tipi:
- char
- short int
- int
- long int
- signed char
- signed short int
- signed int
- signed long int
- unsigned char
- unsigned short int
- unsigned int
- unsigned long int
- float
- double
- long double
Il tipo int e` per default signed e quindi e` equivalente
al corrispondente tipo signed, invece i tipi char,
signed char e unsigned char sono considerate
categorie distinte.
I vari tipi sopra elencati, oltre a differire per l'intervallo dei valori
rappresentabili, differiscono anche per la quantita` di memoria richiesta per
rappresentare un valore di quel tipo. Il seguente programma permette di conoscere
la dimensione di ciascun tipo come multiplo di char (di solito rappresentato
su 8 bit):
#include < iostream.h >
void main() {
cout << "char = " << sizeof(char) << endl;
cout << "short int = " << sizeof(short int) << endl;
cout << "int = " << sizeof(int) << endl;
cout << "long int = " << sizeof(long int) << endl;
cout << "signed char = " << sizeof(signed char) << endl;
cout << "signed short int = " << sizeof(signed short int) << endl;
cout << "signed int = " << sizeof(signed int) << endl;
cout << "signed long int = " << sizeof(signed long int) << endl;
cout << "unsigned char = " << sizeof(unsigned char) << endl;
cout << "unsigned short int = " << sizeof(unsigned short int) << endl;
cout << "unsigned int = " << sizeof(unsigned int) << endl;
cout << "unsigned long int = " << sizeof(unsigned long int) << endl;
cout << "float = " << sizeof(float) << endl;
cout << "double = " << sizeof(double) << endl;
cout << "long double = " << sizeof(long double) << endl;
}
Una veloce spiegazione sul listato:
la prima riga (#include <iostream.h>) richiede l'uso di una libreria per
eseguire l'output su video; la libreria iostream.h dichiara l'oggetto
cout il cui compito e` quello di visualizzare l'output che gli viene inviato
tramite l'operatore di inserimento <<.
L'operatore sizeof(<Tipo>) restituisce la dimensione di
Tipo, mentre endl inserisce un ritorno a capo e forza la
visualizzazione dell'output. Infine main e` il nome che identifica la
funzione principale, ovvero il corpo del programma.
Tra i tipi fondamentali sono definiti gli operatori di conversione, il loro
compito e` quello di trasformare un valore di un tipo in un valore di un altro tipo.
Non esamineremo per adesso l'argomento, esso verra` ripreso in
una apposita appendice.
Variabili e costanti
Siamo ora in grado di dichiarare variabili e costanti.
La sintassi per la dichiarazione delle variabili e`
< Tipo > < Lista Di Identificatori > ;
Ad esempio: int a, b, B, c;
signed char Pippo;
unsigned short Pluto; // se ommesso si intende int
Innanzi tutto ricordo che il C++ e` case sensitive, cioe` distingue le lettere
maiuscole da quelle minuscole, infine si noti il punto e virgola che segue sempre
ogni dichiarazione.
La prima riga dichiara quattro variabili di tipo int, mentre la seconda una
di tipo signed char. La terza dichiarazione e` un po'
particolare in quanto apparentemente manca la keyword int, in realta` poiche`
il default e` proprio int essa puo` essere ommessa; in conclusione la terza
dichiarazione introduce una variabile di tipo unsigned short int.
Gli identificatori che seguono il tipo sono i nomi delle variabili, se piu` di un
nome viene specificato essi devono essere separati da una virgola.
E` possibile specificare un valore con cui inizializzare ciascuna variabile facendo
seguire il nome dall'operatore di assegnamento = e da un valore o una
espressione che produca un valore del corrispondente tipo:
int a = -5, b = 3+7, B = 2, c = 1;
signed char Pippo = 'a';
unsigned short Pluto = 3;
Se nessun valore iniziale viene specificato, il compilatore inizializza le variabili
con 0.
La dichiarazione delle costanti e` identica a quella delle variabili eccetto che
deve sempre essere specificato un valore e la dichiarazione inizia con la keyword
const:
const a = 5, c = -3; // int e` sottointeso
const unsigned char d = 'a', f = 1;
const float = 1.3;
Scope e lifetime
La dichiarazione di una variabile o di un qualsiasi
altro identificatore si estende dal punto immediatamente successivo la dichiarazione
(e prima dell'eventuale inizializzazione) fino alla fine del blocco di istruzioni in
cui e` inserita (un blocco di istruzioni e` racchiuso sempre tra una coppia di
parentesi graffe). Cio` vuol dire che quella dichiarazione non e` visibile
all'esterno di quel blocco, mentre e` visibile in eventuali blocchi annidati dentro
quello dove la variabile e` dichiarata. Il seguente schema chiarisce la situazione:
// Qui X non e` visibile
{
... // Qui X non e` visibile
int X = 5; // Da ora in poi esiste una variabile X
... // X e` visibile gia` prima di =
{ // X e` visibile anche in questo blocco
...
}
...
} // X ora non e` piu` visibile
All'interno di uno stesso blocco non e` possibile dichiarare piu` volte lo stesso
identificatore, ma e` possibile ridichiararlo in un blocco annidato; in tal caso la
nuova dichiarazione nasconde quella piu` esterna che ritorna visibile non appena si
esce dal blocco ove l'identificatore viene ridichiarato:
{
... // qui X non e` ancora visibile
int X = 5;
... // qui e` visibile int X
{
... // qui e` visibile int X
char X = 'a'; // ora e` visibile char X
... // qui e` visibile char X
} // qui e` visibile int X
...
} // X ora non piu` visibile
All'uscita dal blocco piu` interno l'identificatore ridichiarato assume il valore che aveva prima
di essere ridichiarato:
{
...
int X = 5;
cout << X << endl; // stampa 5
while (--X) { // riferisce a int X
cout << X << ' '; // stampa int X
char X = '-';
cout << X << ' '; // ora stampa char X
}
cout << X << endl; // stampa di nuovo int X
}
Una dichiarazione eseguita fuori da ogni blocco introduce un identificatore globale
a cui ci si puo` riferire anche con la notazione ::<ID>. Ad esempio:
int X = 4; // dichiarazione esterna ad ogni blocco
void main() {
int X = -5, y = 0;
/* ... */
y = ::X; // a y viene assegnato 4
y = X; // assegna il valore -5
}
Abbiamo appena visto che per assegnare un valore ad una variabile si usa lo stesso
metodo con cui la si inizializza quando viene dichiarata. L'operatore :: e`
detto risolutore di scope e, utilizzato nel modo appena visto, permette di
riferirsi alla dichiarazione globale di un identificatore.
Ogni variabile oltre a possedere uno scope, ha anche un propria durata
(lifetime), viene creata subito dopo la dichiarazione (e prima
dell'inizializzazione! ndr) e viene distrutta alla fine del blocco dove e` posta la
dichiarazione; fanno eccezione le variabili globali che vengono distrutte alla fine
dell'esecuzione del programma. Da cio` si deduce che le variabili locali (ovvero
quelle dichiarate all'interno di un blocco) vengono create ogni volta che si giunge
alla dichiarazione, e distrutte ogni volta che si esce dal blocco; e` tuttavia
possibile evitare che una variabile locale (dette anche automatiche) venga distrutta
all'uscita dal blocco facendo precedere la dichiarazione dalla keyword static:
void func() {
int x = 5; // x e` creata e distrutta ogni volta
static int c = 3; // c si comporta in modo diverso
/* ... */
}
La variabile x viene creata e inizializzata a 5 ogni volta che
func() viene eseguita, e viene distrutta alla fine dell'esecuzione
della funzione; la variabile c invece viene creata e inizializzata una
sola volta, quando la funzione viene chiamata la prima volta, e distrutta
solo alla fine del programma.
Le variabili statiche conservano sempre l'ultimo valore che viene assegnato ad esse
e servono per realizzare funzioni il cui comportamento e` legato a computazioni
precedenti (all'interno della stessa esecuzione del programma).
Infine la keyword static non modifica lo scope.
Pagina precedente - Pagina successiva