In questo capitolo iniziamo ad esplorare le strutture del C++: i tipi standard C e C++, i modificatori e gli
operatori.
I nomi delle variabili, delle costanti, delle funzioni, come pure dei tipi (classi) e degli oggetti, sono
anche chiamati identificatori: ne creiamo uno ogni volta che dichiariamo una variabile, di un tipo o
di una funzione e lo utilizziamo per richiamare il relativo elemento nelle istruzioni del programma.
Un identificatore è formato da una sequenza di una o più lettere, cifre o caratteri e deve iniziare con
una lettera o con un carattere di sottolineatura. Gli identificatori possono contenere un qualunque
numero di caratteri ma solo i primi 31 caratteri sono significativi per il compilatore.
i
MAX
max
first_name
_second_name
È sempre bene cercare di utilizzare identificatori che abbiano un nome evocativo, che ne ricordi
scopo. Se un identificatore dovrà contenere l‟indirizzo di una persona sarà certamente meglio
utilizzare il nome indirizzo piuttosto che il nome casuale XHJOOQQO.
Ci sono poi alcuni identificatori che sono utilizzati comunemente come indici (es i, j, k) nei
costrutti iterativi (eg. cicli for), un po‟ come in matematica per gli indici di sommatoria, ad
esempio.
Le variabili
Possiamo pensare ad una variabile come ad un contenitore di informazioni da utilizzare nei
programmmi. Per conservare una determinata informazione, tale contenitore fa uso della memoria
del computer.
Come suggerisce il nome, una variabile, dopo essere stata dichiarata, può vedere cambiare il suo
contenuto durante l‟esecuzione di un programma, grazie ad opportune istruzioni di assegnamento.
Le informazioni che in un certo istante sono contenute nelle variabili rappresentano lo stato del
programma. Cambiare i dati nelle variabili significa quindi far evolvere il programma da uno stato
ad un altro. In qualche senso perciò si può dire che le variabili rappresentino l‟essenza del
programma.
I tipi standard
Ma cosa intendiamo quando parliamo di “dichiarazione di una variabile”? È necessario fornire al
computer una informazione precisa sul tipo di variabile che vogliamo definire. Ad esempio, se
vogliamo rappresentare un numero dovremo fare in modo che il computer riservi una quantità di
memoria sufficiente a contenere tale informazione.
Il C++ fornisce una serie di tipi standard che ci permettono di definire alcune tipologie base di
variabili. In questa lezione esaminiamo le caratteristiche principali dei tipi standard del C++ che
servono a rappresentare caratteri, testi, numeri interi e in virgola mobile, composizioni e a
manipolare locazioni di memoria.
I i tipi base del C++ per sono quindi: testo o char (string), intero o int, valori in virgola mobile o float, valori
in virgola mobile doppi o double, enumerazioni o enum, non-valori o void e puntatori.
In questa lezione esaminiamo tutti questi tipi tranne i puntatori, che saranno oggetto di una
trattazione specifica.
char
Qualunque lingua parlata e scritta, per costruire le proprie frasi fa uso di una serie di caratteri. Per
esempio, questa guida è scritta utilizzando svariate combinazioni di lettere dell‟alfabeto, cifre e
segni di punteggiatura. Il tipo char, quindi, serve proprio ad identificare uno degli elementi del
linguaggio.
Il tipo char rappresenta infatti un singolo carattere, intendiamo per carattere una lettera o un
numero o un simbolo (come !, $,%,&). Per questo occupa lo spazio di 8bit (1Byte), utili
storicamente a rappresentare i caratteri nella codifica ASCII.
a b c d e f g h i j k l m n o p q r s t u v w y z
A B C D E F G H I J K L M N O P Q R S T U V X Y Z
0 1 2 3 4 5 6 7 8 9
Vediamo adesso un semplice programma C++ che illustra la dichiarazione e l‟uso del tipo char:
/*
*/
main()
char carattere;
char numero;
scanf("%c", &carattere);
scanf("%d", &numero);
return(0);
Nell‟esempio troviamo le funzioni printf e scanf che sono derivate dal linguaggio C. Utilizziamo
printf per visualizzare a schermo dei messaggi e scanf per leggere l‟input dell‟utente.
Entrambe queste funzioni (definite nel file d‟intestazione stdio.h) rilevano dei codici di
formattazione a seconda del tipo di dato che si vuole rappresentare, eccone alcuni:
Codice Serve a mostrare…
%c un carattere
scanf("%c", &carattere);
qui diciamo che intendiamo leggere l‟input dell‟utente come carattere (quindi verrà considerato
soltanto il primo elemento immesso) e memorizzare tale valore nella variabile carattere. La “e
commerciale” (&) prima del nome della variabile ne indica l‟indirizzo di memoria.
Invece, la riga:
scanf("%d", &numero);
serve a leggere un intero decimale e memorizzarlo nella variabile numero. In modo analogo, quando
scriviamo:
diciamo di voler stampare a schermo (in output) il messaggio "Il carattere inserito è: "
seguito dal carattere contenuto nella variabile carattere: se si è inserito il carattere „c‟ verrà,
quindi, stampato:
Il carattere inserito è c
serve a stampare, al posto del codice %d, il numero intero contenuto nella variabile numero.
Ricordiamo che sia la variabile carattere sia la variabile numero sono char, ciò che cambia è il
modo in cui abbiamo scelto di acquisire e stampare le informazioni memorizzate.
I testi
Per rappresentare del testo, nello standard C++ è stato introdotto anche il tipo string (da utilizzare
preferibilmente agli array di char)
int
Il tipo int serve a rappresentare l‟insieme dei numeri interi, per intenderci “senza virgola”, positivi
e negativi (1, 2, 43, -89, 4324). Storicamente i compilatori rappresentavano gli interi con 16bit
(valori compresi tra -32768 e 32767) questo perché gli indirizzamenti erano a 16bit, ma attualmente
quasi ovunque troviamo gli interi codificati da 32bit (valori compresi tra -2.147.483.648 e
2.147.483.647), in alcuni casi si sta passando anche agli interi da 64bit.
Ad arricchire la varietà di tipi numerici interi, troviamo i qualificatori unsigned, short e long,
(“senza segno”, “corto” e “lungo”) che modificano alcune caratteristiche dei tipi fondamentali, char
e int.
Per capire la differenza tra interi con segno e unsigned, prendiamo il caso di una variabile di tipo
char. char è rappresentato da un byte, ovvero da 8bit (il bit, che sta per cifra binaria – binary digit
– può contenere i valori 0 o 1). Vediamo alcuni esempi di byte:
Rappresentazioni di un byte
---------------------------
Binaria | Esadecimale
10100010 | A2
00001100 | 0C
00000000 | 00
11111111 | FF
Si definisce bit più significativo (MSB) quello all‟estrema sinistra mentre il bit meno significativo
(LSB) sarà quello all‟estrema destra. Nella notazione normale il bit più significativo viene utilizzato
per il segno: in particolare se il primo bit vale 1 rappresenterà il segno meno mentre se il primo bit
vale 0 rappresenta il segno +.
Invece di 8 bit per rappresentare un valore, ne verranno usati solo 7 e con 7 bit il valore più grande
che possiamo raggiungere è 128. Poiché anche lo zero deve essere considerato tra i valori possibili,
allora otterremo proprio l‟intervallo da 0 a 127 per i positivi e da -1 a -128 per i negativi.
Se utilizziamo unsigned, tutti i bit che compongono il byte vengono utilizzati per contenere e
comporre un valore, che in tal caso potrà essere soltanto un valore positivo. Il numero relativo
all‟ultimo byte rappresentato nell‟esempio precendente varrà, in tal caso:
Questo ragionamento è valido per tutti gli altri tipi unsigned del C++.
Il qualificatore short, forza l‟intero alla dimensione di 16bit. In passato dire int o dire short int
era equivalente. Il qualificatore long è invece usato per forzare il compilatore ad utilizzare 32bit per
un intero, oggi è la norma.
Infine è utile sapere che unsigned int, short int e long int possono essere dichiarati anche più
sinteticamente come unsigned, short, long, il compilatore assume che si tratti sempre di un int.
I valori double in virgola mobile sono valori molto estesi che normalmente occupano 64 bit (o 8
byte) e possono avere, quindi, un valore compreso fra ! 1,7 x 10-308 e 1,7 x 10308 (con 15 cifre
significative). I valori long double sono ancora più precisi e normalmente occupano 80 bit (o 10
byte). Il loro valore è compreso fra ! 1,18 x 10-4932 e 1,18 x 104932 (con ben 19 cifre
significative).
La base dell‟esponente può variare a seconda del compilatore e della macchina fisica (il
microprocessore) che eseguirà il codice.
/*
*/
#include <iostream.h>
main()
float raggio;
cout << "L'area del cerchio è: " << area << endl;
enum
Quando si definisce una variabile di tipo enumerativo, ad essa viene associato un insieme di costanti
intere chiamato insieme dell’enumerazione. La variabile può contenere una qualsiasi delle costanti
definite, le quali possono essere utilizzate anche tramite nome. Ad esempio, la definizione:
enum secchio {
VUOTO,
MEZZO_PIENO,
PIENO = 5
} mio_secchio;
crea il tipo enum secchio, le costanti VUOTO, MEZZO_PIENO e PIENO e la variabile enumerativa
mio_secchio.
Tutte le costanti e le variabili sono di tipo int e ad ogni costante è fornito in maniera automatica un
valore iniziale standard a meno che venga specificato in modo esplicito un diverso valore.
Nell‟esempio precedente, alla costante VUOTO viene assegnato automaticamente il valore intero 0 in
quanto si trova nella prima posizione e non viene fornito un valore specifico. Il valore di
MEZZO_PIENO è 1 in quanto si trova immediatamente dopo una costante il cui valore è zero. La
costante PIENO viene inizializzata, invece, esplicitamente al valore 5. Se, dopo la costante PIENO, si
fosse introdotta una nuova costante, ad essa sarebbe stato automaticamente assegnato il valore
intero 6.
Sarà possibile, dopo aver creato il tipo secchio, definire un‟altra variabile, tuo_secchio nel modo
seguente:
secchio tuo_secchio;
mio_secchio = PIENO;
tuo_secchio = VUOTO;
che assegnano alla variabile mio_secchio il valore 5 ed alla variabile tuo_secchio il valore 0.
Un errore che spesso si commette è quello di pensare che secchio sia una variabile. Non si tratta di
una variabile ma di un “tipo di dati” che si potrà utilizzare per creare ulteriori variabili enum, come
ad esempio la variabile tuo_secchio.
Poiché il nome mio_secchio è una variabile enumerativa di tipo secchio, essa potrà essere
utilizzata a sinistra di un operatore di assegnamento e potrà ricevere un valore. Ad esempio, si è
eseguito un assegnamento quando si è assegnato alla variabile il valore PIENO. I nomi VUOTO,
MEZZO_PIENO e PIENO sono nomi di costanti; non sono variabili e non è possibile in alcun modo
cambiarne il valore.
Dichiarazione di variabili
Come già detto, il C++ richiede tassativamente che ogni variabile prima di essere utilizzata dal
programma venga preventivamente dichiarata. La dichiarazione avviene semplicemente indicando il
tipo dell‟identificatore in oggetto seguito da uno o più identificatori di variabile, separati da una
virgola e seguiti dal punto e virgola al termine della dichiarazione stessa.
float a, b;
int c, d;
È importante sottolineare il fatto che una variabile può essere dichiarata soltanto una volta e di un
solo tipo all‟interno dell‟intervallo di azione della variabile stessa.
È possibile inizializzare una variabile, cioè assegnare alla stessa un valore contemporaneamente alla
sua dichiarazione; ad esempio è consentita la dichiarazione:
float a, b = 4.6;
char ch = 't';
float a, b;
char ch;
b = 4.6;
ch = 't';
cioè ad a non verrebbe per il momento assegnato alcun valore mentre b avrebbe inizialmente il
valore 4.6 e ch avrebbe il carattere 't'.
In alcuni casi, però, può essere utile forzarne l‟utilizzo secondo le esigenze del programmatore. Ad
esempio, un intero con segno considerarlo senza segno oppure considerarlo float aggiungendo
decimali fittizi. In C++ questa esigenza viene soddisfatta molto semplicemente premettendo alla
variabile il tipo con il quale si vuole riqualificarla, tipo che viene messo tra parentesi.
Per esempio, se la variabile a è di tipo int viene riqualificata come float nel seguente modo:
(float) a
Con il C++ la riqualificazione è molto spesso un‟operazione indispensabile nella divisione tra interi.
Per esempio:
int a = 9, b = 2;
float c;
Vale, infine, la pena di osservare che, riqualificando una variabile, non si modifica il contenuto
della variabile stessa ma soltanto il valore che interviene nell‟operazione in corso.
Le costanti
In molti casi è utile assegnare a degli identificatori dei valori che restino costanti durante tutto il
programma e che non possano essere cambiati nemmeno per errore. In C++ è possibile ottenere ciò
in due modi con due risultati leggermente diversi:
La direttiva #define
Sintassi di #define
Con la direttiva #define il compilatore in ogni punto dove incontra i simboli così definiti
sostituisce ai simboli stessi i valori corrispondenti. Ad esempio, indicando:
#include <stdio.h>
#define MAXNUM 10
#define MINNUM 2
int x,y;
x = MAXNUM;
y = MINNUM;
x = 10;
y = 2;
Volendo, in seguito modificare i valori in tutto il programma basterà modificare una volta per tutte i
soli valori indicati con #define.
Si noti che #define non richiede che venga messo il punto e virgola finale e neppure l‟operatore di
assegnamento (=).
L‟utilizzo della direttiva #define non soltanto diminuisce l‟occupazione di memoria (non vi è
infatti necessità di spazio per le costanti MAXNUM e MINNUM), ma anche rende più veloce il
programma che durante l‟esecuzione, quando le deve utilizzare, non deve ricercarne i valori.
C‟è da dire che #define ha un utilizzo più generale che va oltre la definizione di costanti. Essa
permette anche la definizione delle cosidette macro. Vediamo un esempio per capire bene in cosa
consistono le macro. Si supponga che in un programma si debbano invertire molte volte i contenuti
di alcuni identificatori. Piuttosto che ripetere ogni volta la stessa sequenza di operazioni, viene utile
costruirsi un‟istruzione apposita come dal seguente programma:
#include <stdio.h>
#define inverti (x,y,temp) (temp)=(x); (x)=(y); (y)=(temp);
main()
{
float a= 3.0, b = 5.2, z;
int i = 4, j = 2, k;
Il modificatore const
Sintassi di const
Si faccia attenzione al fatto che qui viene usato sia l‟operatore di assegnamento (=) che il punto e
virgola finale. Se il tipo non viene indicato il compilatore assume per default che sia intero (int).
Ecco un esempio di utilizzo del modificatore const:
Si noti che, contrariamente a #define, con un solo utilizzo di const è possible dichiarare più
identificatori separandoli con la virgola. Inoltre, il compilatore assegna un valore che non può
essere modificato agli identificatori utilizzati (MAXNUM e MINNUM nell‟esempio precedente), valore
che però è archiviato in una zona di memoria e non sostituito in fase di compilazione, come accade
per #define.
Operatori aritmetici
Il linguaggio C++ è dotato di tutti i comuni operatori aritmetici di somma (+), sottrazione (-),
moltiplicazione (*), divisione (/) e modulo (%). I primi quattro operatori non richiedono alcuna
spiegazione data la loro familiarità nell‟uso comune.
L‟operatore modulo, invece, è semplicemente un modo per restituire il resto di una divisione intera.
Ad esempio:
int a = 3, b = 8, c = 0, d;
d = b %a; // restituisce 2
d = a % b; // restituisce 3
Operatori booleani
Il C++ mette a disposizione un numero superiore di operatori rispetto ad altri linguaggi, ma alcuni
di essi risultano non facilmente interpretabili perché i loro simboli non hanno un immediato
riferimento mnemonico alla funzione svolta. Vediamo i più importanti operatori del C++.
Gli identificatori booleani e le operazioni su di essi sono molto usati. Spesso, in un programma, si
rende necessario sapere se una certa condizione è vera (true) oppure falsa (false). Nel primo caso,
il corso del programma prenderà una determinata direzione che, invece, non sarebbe intrapresa
altrimenti.
Un esempio grafico particolarmente attinente alle operazioni con boolean è rappresentato dalle
check box. Se una check box è selezionata si vorrà intraprendere una determinata azione. In caso
contrario non si vorrà fare nulla.
La maggior parte dei linguaggi di programmazione contemplano il tipo booleano. La maggior parte
dei compilatori C++ riconosce il tipo boolean con la parola chiave bool. Altri, invece, accettano la
scrittura boolean. Diamo per buono che il compilatore riconosca il tipo bool. In tal caso, una
dichiarazinone di una variabile booleana sarà la seguente:
bool flag;
I principali operatori di tipo booleano, ovvero gli operatori che consentono di eseguire operazioni su
elementi di tipo bool sono 3:
AND, prende in input due operandi e produce in output un booleano, attenendosi al seguente
comportamento: Se entrambi gli operatori sono true allora l‟output è true; in tutti gli altri casi
l‟output è uguale a false.
OR, prende in input due operandi e produce in output un booleano, attenendosi al seguente
comportamente: Se almeno undo degli operandi è uguale a true, l‟output è true; altrimenti, se
nessuno dei due operandi è uguale a true l‟output sarà false.
NOT, prende in input un solo operando e produce in output un booleano, attenendosi al seguente
comportamento: Se l‟operando di input è true allora l‟output sarà false. Se, invece l‟operando di
input è false, allora l‟output sarà uguale a true. In altri termini, l‟operatore di NOT prende un input e
ne restituisce l‟esatto contrario.
// Il risultato è false
Se, invece, Gianni decidesse di andare a letto se anche soltanto una delle due precondizioni fosse
vera allora l‟operatore da utilizzare sarebbe l‟operatore OR. Avremo in tal caso:
gianniStanco = true
Ovvero la variabile booleana gianniInForma sarà vera se non è vera quella che identifica Gianni
stanco. Questo è un banale esempio dell‟operatore NOT.
L‟utilizzo degli operatori booleani è perfettamente lecito anche su variabili che non siano bool. In
C++ il valore “0″ equivale a false e qualunque valore diverso da zero equivale a true. Ad
esempio:
int ore = 4;
int minuti = 21;
int secondi = 0;
Poiché il risultato deriva dall‟esame dei tre operandi e c‟è un valore (secondi) che è uguale a zero
(ovvero equivale a false) il risultato dell‟espressione è false.
Operatore di assegnamento
L‟operatore di assegnamento in C++, altro non fa che assegnare ad una variabile un determinato
valore. È importante dire che un‟espressione contenente un operatore di assegnamento può quindi
essere utilizzata anche all‟interno di altre espressioni, come ad esempio:
x = 5 * (y = 3);
In questo esempio, alla variabile y viene assegnato il valore 3. Tale valore verrà moltiplicato per 5 e
quindi, in definitiva, alla variabile x sarà assegnato il numero 15.
È, però, caldamente sconsigliato l‟utilizzo di tale pratica in quanto potrebbe rendere poco leggibili
le espressioni. Vi sono, tuttavia, un paio di casi in cui questa possibilità viene normalmente
sfruttata. Innanzitutto, si può assegnare a più variabili lo stesso valore, come in:
x = y = z = 4;
Il secondo tipo di utilizzo è molto frequente all‟interno dei cicli while e for:
oppure
Operatori di relazione
Un operatore di relazione è un operatore che verifica una condizione come: “è minore di” oppure
“è maggiore di” oppure ancora “è uguale a”.
Un utilizzo intuitivo di tali operatori è quello che viene fatto per comparare due numeri. Un
operatore di relazione può essere determinante per la scelta di una determinata strada piuttosto che
di un‟altra. Ad esempio, se una certa variabile supera un valore di soglia potrebbe essere necessario
chiamare una funzione per controllare altri parametri, e così via.
int risultato = 4 + 5 * 7 + 3;
Il risultato in questo caso dipende proprio dalla precedenza tra operatori. In C++, l‟operatore di
moltiplicazione (*) ha precedenza rispetto all‟operatore addizione (+). Quindi la moltiplicazione
5*7 avverrà prima di tutte le altre addizioni. Ecco la sequenza della risoluzione dell‟espressione
precendente:
int risultato = 4 + 5 * 7 + 3;
risultato = 4 + 35 + 3 = 42
Perché le operazioni siano effettuate con ordine diverso, sarà sufficiente introdurre delle parentesi
tonde. Ad esempio se vogliamo moltiplicare la somma 4+5 con la somma di 7+3, basterà scrivere:
Facciamo ora un elenco di tutti gli operatori ordinati per livello di precedenza. Ad ogni riga della
tabella è assegnato un livello di precendenza. L‟operatore a maggior priorità avrà il valore 1 e a
seguire quelli a priorità sempre più bassa
Livello di precedenza Operatore Nome
1 ! Not, negazione
2 * Moltiplicazione
2 / Divisione
2 % Modulo
3 + Addizione
3 - Sottrazione
4 < Minore
4 <= Minore uguale
4 > Maggiore
4 >= Maggiore uguale
5 == Uguale (confronto)
5 != Diverso
6 && AND
7 || OR
8 = Assegnamento
Le parentesi risolvono praticamente il problema di conoscere la precedenza tra gli operatori. Per
evitare errori ed aumentare la leggibilità del codice può essere consigliabile l‟uso delle parentesi in
ogni situazione in cui vi sia la presenza contemporanea di più operatori e si abbiano dubbi sulle
precedenze.