Semplice guida al linguaggio C++ scritta durante lo studio dell’esame di Fondamenti di Informatica 1; sebbene sintetica, la guida risulta molto funzionale per un ripasso veloce del linguaggio in oggeto.
Tipo variabili
Char, tipo carattere:
Il tipo âcharâ ha come valori i caratteri di stampa opportunamente codificati tramite 8 bit (1 byte). La codifica dipende dallâimplementazione, e la piĂš comune è quella ASCII. Le operazioni tipiche sui caratteri sono quelle di confronto, tuttavia per essi sono possibili tutte le operazioni definite sugli interi, che agiscono sulle loro codifiche. Il tipo âcharâ, come detto, è rappresentato su 8 bit anche se è dichiarato come âsigned charâ (da -128 a +127) o âunsigned charâ (da 0 a +255).
Enum, tipo enumerazione:
Un âtipo enumerazioneâ è un insieme di costanti intere, definite dal programmatore, ciascuna individuata da un identificatore detto enumeratore. I tipi enumerazioni vengono usati per un numero limitato di valori, ed in genere sono associati ad informazioni non numeriche (quali ad es. costanti matematiche). La dichiarazione di un tipo enumerazione ha questa forma:
enum identifier {enumerator-declaration-list}
Agli enumeratori sono associati valori interi consecutivi a partire da 0, perciò il primo elemento ha come valore 0, il secondo 1⌠e cosÏ via. Generalmente le enumerazioni vengono usate in calcoli che coinvolgono operazioni di confronto, ma sono possibili tutte le operazioni definite sugli interi, che agiscono sulle codifiche degli enumeratori.
Conversioni implicite:
intero e reale: lâintero viene convertito in reale e il risultato è un reale. assegnamento di intero o reale: se unâespressione aritmetica viene assegnata ad una variabile di tipo intero o reale, il suo valore viene convertito di conseguenza. Short e Long: con gli interi e i reali gli operandi sono sempre convertiti al tipo di quello con lunghezza maggiore. Un valore âcharâ o âenumâ agisce come un âintâ.
Conversioni esplicite:
int i = (int) 3.14; //cast
double f = double(4); //notazione funzionale
Oggetti costanti:
const int kilo = 1024;
const double pi = 3.14159;
const int mille = kilo â 24;
// Gli oggetti del tipo âenumâ possono essere considerati
// anche come int costanti.
Operatore SIZEOF
Restituisce il numero di byte (8 bit) con cui viene codificato un determinato tipo di oggetti.
La notazione è la seguente:
sizeof(char);
sizeof âcâ;
sizeof(int);
sizeof 3;
Definizione e dichiarazione
Definizione: implica allocazione di memoria. Dichiarazione: crea un nuovo elemento.
Espressioni di assegnamento
Lâassegnamento si effetua tramite lâoperatore â=â (assoc. âdâ, prioritĂ 2), da non confondere con lâ â= =â (operatore uguale, assoc. âsâ, prioritĂ 9). Gli assegnamenti piĂš comuni sono:
a = b = c = 3; // OK
a = b = 2 * (c = 5); // OK
a = b + 1 = 2 * (c = 5); // ERRORE
Incremento e decremento
Dato un identificatore si possono usare gli operatori di incremento (o decremento) ++ (o –). Lâincremento può essere prefisso, in questo caso prima si aumenta di 1 e poi si restituisce il valore:
i = 0;
j = ++i;
cout << i << j << â\nâ; // 1 1
Nel caso di incremento postfisso prima si restituisce il valore e poi si incrementa di 1:
i = 0; j = i++; cout << i << j << â\nâ; // 1 0
Nota bene: Le stesse proprietĂ valgono per lâoperatore di decremento (–i oppure i–).
Istruzioni Condizionali
Istruzione if
Lâistruzione if ha la seguente forma sintattica:
if (expression) statement; else if (expression) // Le righe seguenti potrebbero anche essere omesse statement; else statement;
Lâespressione che compare dopo la parola chiave âifâ è di tipo logico, per questo bisogna stare attenti quando si fanno operazioni di confronto âa = = bâ poichĂŠ se per errore si scrive âa = bâ questo è un assegnamento ed è sempre vero.
Da notare che se câè un âelseâ presente dopo una serie di âifâ questo si riferisce a quello piĂš vicino (a meno che lo âstatementâ non sia racchiuso tra graffe), quindi nella lettura di un programma non è utile tenere in considerazione soltanto lâindentatura che potrebbe essere sbagliata.
Operatore condizionale (e1Â ? e2Â : e3)
Eâ lâunico operatore ternario del C++, la sua associatività è a sinistra e la sua priorità è 3. Si usa nel caso in cui il valore di e1 sia di tipo logico, se è vera si esegue e2, se è falsa si esegue e3. Lâespressione condizionale assume il valore dellâespressione (e2 o e3) che viene eseguita.
Istruzione switch e break
Lâistruzione âswitchâ ha la seguente sintassi:
Switch (expression) { case constant-expression: statement; break; // opzionale } // in genere âdefaultâ è lâultimo valore quindi non ha break
Lâistruzione âswitchâ è molto piĂš chiara dellâ âifâ, poichĂŠ esamina tutti i casi e funziona solo per confronti di uguaglianza tra discreti. Dopo aver usato un etichetta (case expression:) è consigliabile mettere un âbreakâ a meno che non si voglia produrre un risultato âin cascataâ, se cosĂŹ fosse è opportuno commentare adeguatamente il codice; riguardo alle etichette è da ricordare che ci può essere una sola alternativa con lâetichetta âdefault:â.
Istruzioni di ciclo
Istruzione while
La forma sintattica dellâistruzione âwhileâ è:
while (expression) { statement; }
Lâesecuzione avviene nel seguente modo, viene valutata lâespressione, se il risultato è vero viene eseguita lâistruzione e lâistruzione âwhileâ viene ripetuta, se il risultato è falso lâistruzione âwhileâ termina; il corpo viene eseguito 0 o piĂš volte.
Ci sono tre modi per usare un ciclo âwhileâ che ha passi ânâ:
// n conserva il proprio valore, ma utilizzo 2 variabili while (i < n) { statement; i++; }
// al termine n vale 0 e si rovina il dato di ingresso while (n > 0) { statement; n--; }
// al termine n vale â1, impostazione consigliata while (n-- > 0) { statement; }
Istruzione do
Essa ha la seguente forma sintattica:
do { statement; } while(expression); // non è il REPEAT⌠UNTIL del Pascal
Inizialmente viene eseguita lâistruzione tra âdoâ e âwhileâ (corpo del âdoâ), poi viene valutata lâespressione, se è vera si riesegue lâistruzione âdoâ, altrimenti (lâespressione è quindi falsa) lâistruzione âdoâ termina. Il ciclo âdo â whileâ esegue il corpo 1 o piĂš volte.
Istruzione for
La sintassi dellâistruzione âforâ è la seguente:
for (init-statement; expressionopt; expressionopt) { statement; }
Lâistruzione di inizializzazione di solito è un assegnamento, che inizializza una âvarabile di controlloâ; lâespressione che segue lâinizializzazione in genere è di tipo logico, mentre lâultima espressione agisce sulla âvariabile di controlloâ modificandone il valore.
Quindi possiamo equiparare un ciclio âforâ e un ciclo âwhileâ:
// con il âforâ for (inizializzazione; condizione; incremento) { istruzione; }
// con il âwhileâ inizializzazione; while (condizione) { istruzione; incremento; }
Pertanto lâinizializzazione viene eseguita una sola volta, mentre la condizione viene valutata prima di ciascuna ripetizione del ciclo: se la condizione è vera, vengono eseguiti nellâordine, lâistruzione e lâincremento, altrimenti il ciclo âforâ termina. Il âforâ si può sempre usare, ma è preferibile usarlo quando si sa quante volte bisogna ciclare, altrimenti è migliore lâutlizzazione di âwhileâ e âdoâ. Lâistruzione âforâ è utilizzabile per creare i âfor innestatiâ, istruzioni for una dentro lâaltra che creano dei cicli annidati, ne è un esempio la possibile rappresentazione di una matrice.
Istruzioni di salto
break
esce da un âwhileâ o da uno âswitchâ.
continue
riprende lâesecuzione quando si valuta la condizione di controllo (non per lo switch).
goto
chiama la funzione definita con unâetichetta.
return
ritorna alla funzione che lâha chiamato.
Uso di una funzione
Una funzione viene usata quando in un programma avviene spesso di usare la stessa sequenza di operandi, magari applicata a oggetti dello stesso tipo e numero. Una funzione deve essere definita (la definizione è anche una dichiarazione) e dicichiarata. Nella definizione, il programmatore specifica quali sono i dati su cui operare e quali azioni devono essere eseguite. Per dichiarare una funzione esistono sonstazialmente due modi:
int mcd(int alfa, int beta) int mcd(int, int)
Una funzione può chiamare unâaltra funzione (anche se stessa) e per uscire da essa si usa il comando RETURN.
Esistono anche le âFunzioni a Voidâ (o procedure) che non restituiscono risultati, usate ad esempio per gli stream; in genere queste funzioni posso essere definite senza argomenti.
Ricorsione
Si definisce âProcedimento Ricorsivoâ un procedimento definito in termine di se stesso. In C++ per definire un procedimento ricorsivo si usano le funzioni che, allâinterno di se stesse, si richiamano creando una nuova istanza, cosĂŹ finchè non si arriva a uno o due casi in cui la funzione ritorna un valore.
Puntatori
Un puntatore equivale in senso stretto ad unâistruzione di indirizzamento a livello macchina, cioè ad un indirizzo di memoria (che può riferire una variabile o un sottoprogramma). L’uso dei puntatori è frequente e viene utilizzato in maniera pesante da tutti quei linguaggi che si basano sull’utilizzo diretto della memoria principale, in primis il C ( e quindi il C++). Eâ bene allora prendere alcuni appunti su cosa non fare con i puntatori, in riferimento proprio al C:
- specificare sempre il tipo di oggetto puntato: essendo un puntatore soltanto una zona di memoria, il compilatore non può effettuare il controllo in fase di compilazione, ma un eventuale errore verraâ fuori solo a tempo di esecuzione;
- evitare ogni puntatore uttuante; ad esempio, creando un oggetto nelloheap e poi rimuovendolo con la funzione free() (o con l’equivalente delete), è vene mettere sempre il puntatore a 0 per due motivi: utilizzando nuovamente il puntatore come riferimento all’oggetto puntato, in fase di compilazione non ci si accorge che l’oggetto non câe piĂš; una successiva chiamata a free() potrebbe creare guai seri per il funzionamento del programma (mentre non ha effetti, come la delete, su puntatori nulli);
- un puntatore se possibile va inizializzato;
- riferendosi all’oggetto puntato all’interno di una union, câe il rischio di alterare il dato in essa conservato;
- il rischio di produrre garbage è elevato con i puntatori se non si ha l’accortezza di usare la funzione free() (o l’operatore delete) per liberare la memoria puntata, a meno che non si abbiano dei processi raffinati di garbage collection 9 (e.g. in Java).
Uso dei puntatori
Un puntatore è un oggetto derivato e ha come valore lâindirizzo di un altro oggetto o funzione. La sintassi di un puntatore è:
âdichiarazione di puntatoreâ: âtipoâ âspecificatore di puntatoreâ
âspecificatore di puntatoreâ: * âvariabileâ * âspecificatore di puntatoreâ
In genere le operazioni piĂš comuni sui puntatori sono:
int* p = &i; // gli operatori â&â e â*â hanno assoc. a destra e prioritĂ 15; *p = 10; // equivale a i = 10; *&i = 4; // equivale a *p = 4; const int* p = &i; // non si può aggiornare i con delle istruzioni che usano p; int* const p = &i; // non si può modificare il valore di p (es *p = &j //NO);
Eâ consigliabile inizializzare sempre esplicitamente i puntatori. Esistono anche i puntatori a void, ad esempio quando bisogna usare il solito puntatore per farlo puntare prima ad un intero ed in seguito ad un carattere; prima di essere deferenziato, un puntatore a void deve essere convertito specificatamente al tipo opportuno (mediante un CAST).
Aliasing
Due variabili sono alias se denotano (condividono) lo stesso oggetto durante l’attivazione di una unitĂ di programma; una modifica del dato sotto il nome di una variabile è automaticamente visibile tramie tutte le variabili che condividono il dato. L’aliasing può sorgere durante l’esecuzione di una procedura quando i parametri sono passati per indirizzo:
void scambio(int &a, int&b) { int t = a; a = b; b = t; }
oppure può anche occorrere quando un parametro formale (passato per indirizzo) e una variabile globale individuno lo stesso oggetto o due oggetti sovrapposti:
void scambio(int &a) { int t = a; a = globale; // globale è un intero con visibilità globale globale = t; }
Ovviamente l’aliasing non sorge se i parametri sono passati per copia; tali parametri giscono da variabili locali all’interno della procedura, e i corrispondenti parametri attuali vengono modificati solo all’uscita della procedura. Una conseguenza dell’aliasing molto spiacevole è che la chiamata a un sottoprogramma potrebbe produrre risultati scorretti e inaspettati, per la lettura. recupero della spazzatura; è una procedura che consiste nell’individuare lo spazio di memoria nello heap davvero necessario al funzionamento del programma e rimuovere il resto; ad esempio è possibile scandire la memoria alla ricerca di celle non puntate da variabili automatiche.
Qual cosa il programmatore è costretto a esaminare non solo la unitĂ incriminata ma tutte le unitĂ che potrebbero chiamare il sottoprogramma. Inoltre l’aliasing riduce le possibilitĂ di generare codice ottimizato; ad esempio:
a = ( x - y * z) + w; b = ( x - y * z) + u;
Se a è un alias di x, y o z, la sottoespressione x – y * z non può essere calcolata una sola volta, e poi utilizzata nei due assegnamenti. Eâ molto probabile dunque che se si profila la possibilitĂ di aliasing, il compilatore non generi nessun tipo di ottimizzazione.
Uso dei riferimenti
I riferimenti permettono al programmatore di riferirsi ad uno stesso oggetto per mezzo di piĂš identificatori, creando dei sinonimi (o alias) di tale oggetto. Non si possono dichiarare riferimenti a riferimenti. Un riferimento ha questa forma:
int& ri = i;
Si può quindi modificare la variabile âiâ per assegnamento della variabile âriâ (es: ri = 5; // equivale a i = 5). Si può usare la parola chiave CONST per impedire che lâoggetto venga modificato tramite riferimento:
const int& ri = i; ri = 1234; // Errore
Lâuso dei riferimenti è utile quando si usano le funzioni, poichĂŠ permettono di modificare i dati di ingresso.
Gli array
Il seguente esempio mostra la dichiarazione di un array di 5 elementi di tipo int:
int a[5]; // int[5] è il tipo di a; int b[] = { 1, 2, 3, 4, 5 }; //forma inizializzata esplicitamente;
Il nome di un array viene interpretato come lâindirizzo del primo elemento dellâarray stesso, indirizzo costante; pertanto *x = x[0] (// o meglio x = &x[0]) quindi si può scrivere per modificare un valore:
a[3] = 7 // assegna 7 al 4° elemento dellâarray; *(x + 3) = 2 // assegna 2 al 4° elemento dellâarray;
Questi sono array monodimensionali (o vettori), ma esistono anche array multidimensionali (o matrici) dove il primo array costituisce le righe, il secondo le colonne; lâunico tipo di assegnamento lecito è:
p = mat[0]; // equivale a âp = &mat[0][0];
Gli array nelle funzioni permettono di operare con array dello stesso tipo ma di dimensioni diverse, e poichĂŠ gli array vengono passati per indirizzo permette che questi vengano modificati, ma se si utilizza lâattributo const si impedisce alla funzione di modificare il corrispondente argomento attuale.
Strutture
Una struttura ha la seguente sintassi:
struct identificatore { lista di dichiarazioni; } variabileopt;
Per riferirsi ad elementi di una struttura si usano i âselettori di membroâ, â.â e â->â, esempio:
struct persona { char nome[20]; char cognome[20]; struct {int giorno, mese, anno} d_nascita; };
persona andrea; //inizializza la variabile âandreaâ di tipo âpersonaâ;
In questo caso per cambiare il campo del mese della data di nascita si scrive âandrea.d_nascita.mese = 3;â
Unioni
Le âunioniâ sono dichiarate e usate con la stessa sintassi delle strutture, eccetto la parola chiave union al posto di struct, ma rappresentano aree di memoria che in tempi diversi possono contenere dati di tipo differente. Esempio:
struct { int i; double d; } x;
union { int i; double d; } y;
Nellâipotesi che i tipi int e double siano rappresentati rispettivamente da 32 e 64 bit, la struttura x occupa 96 bit, mentre lâunione y solo 64, che possono essere dedicati ad un valore intero o ad uno reale. Non sono definite le operazioni di confronto.
Caratteristiche delle funzioni
Puntatori a funzioni
Un puntatore a funzione ha valore un indirizzo di una funzione, e può essere usato per chiamare la funzione stessa: il suo uso permette di scrivere funzioni che accettano altre funzioni come argomento. Esempio:
double sin(double x);
Main( ) { double (*fp)(double); fp = sin; double y = (*fp)(0.5); } // equivale a âsin(0.5)â;
Si possono inoltre definire vettori di puntatori a funzione, e strutture o unioni contenenti puntatori a funzione.
Overloading
In C++ si può usare uno stesso identificatore per funzioni diverse, purchè queste abbiano argomenti diversi, in numero e/o tipo. La situazione in cui funzioni diverse hanno lo stesso identificatore si chiama âoverloadingâ. Esempio:
Double radq(int n) { /* versione int */ }
double radq(double x) { /* versione double */ }
main ( ) { cout << radq(36) << â\nâ // versione int cout << radq(0.789) << â\nâ //versione double }
Funzioni inline
Il comando âInlineâ si usa quando si definisce una funzione ed in pratica dice al compilatore di sostituire, durante la compilazione, il codice della funzione ogni qualvolta viene chiamata la funzione. Il suo contro piĂš grande è quello di allungare il codice. Non si usa MAI con le funzioni ricorsive.
Dichiarazioni typedef
Lâoperatore typedef permette di creare dei sinonimi di tipo, ad esempio un puntatore a interi si scrive cosĂŹ:
int* p;
PoichÊ può essere scomodo scrivere tutte le volte il codice si può usare il typedef:
typedef int* intP;
Operatori New e Delete
Lâoperatore NEW permette di allocare memoria per una variabile. La sintassi è la seguente:
int* h = new int; // operatore + tipo
Questo operatore va nellâ HEAP (memoria libera) e cerca lo spazio per la variabile e lo riserva. Invece le variabili automatiche vengono allocate nello STACK. Inoltre questo operatore riporta lâindirizzo dellâint.
Invece lâoperatore DELETE libera la memoria allocata con NEW e si applica con la seguente sintassi:
delete [ ] p; // nel caso di array;
delete q; // distrugge la variabile q;
Effetti collaterali
Esistono alcuni modi per scambiare informazioni tra funzioni, quello classico si ha quando la funzione chiamata è pura, cioè passa i propri risultati alla funzione chiamante soltanto per mezzo dellâistruzione return; un secondo metodo di interazione fra le funzioni si ha quando una delle funzioni può modificare il valore di variabili visibili alle altre. In questo caso, si dice che la funzione ha effetti collaterali. Una funzione può produrre effetti collaterali quando le vengono passati esplicitamente dei puntatori o dei riferimenti, e quando modifica variabili condivise o variabili globali. Nel caso di effetti collaterali possono anche non valere alcune proprietò comuni, ad esempio la proprietò commutativa della somma. Lâuso esplicito di puntatori può mettere in evidenza le chiamate di funzione che possono avere effetti collaterali, mentre i riferimenti possono nascondere la presenza di tali effetti; i programmi in cui si usano variabili condivise o variabili globali sono i meno leggibili e quindi piĂš soggetti ad errori.
Overloading di operatori
La ridefinizione di un operatore (tramite âoverloadingâ) ha la forma di una definizione di funzione, il cui identificatore è costituito dalla parola chiave âoperatorâ seguita dallâoperatore che si vuole ridefinire, ed ogni occorrenza della funzione equivale ad una chiamata alla funzione.
Funzioni friend
Una funzione è âfriendâ di una classe se una sua dichiarazione, preceduta dala parola chiave âfriendâ, appare nella dichiarazione di tale classe. La funzione può accedere ai membri pubblici e privati della classe, usando i selettori di membro. Le funzioni âfriendâ sono utili anche quando si debbano realizzare delle funzioni che operano su oggetti appartenenti a classi diverse.
Funzioni const
Possiamo dichiarare una funzione membro un modo che non possa modificare lâoggetto a cui è applicata, scrivendo la parola chiave âconstâ dopo la parentesi che chiude la lista degli argomenti; se un oggetto è stato dichiarato âconstâ, ad esso si possono applicare solo funzioni âconstâ. Il meccanismo di overloading distingue due versioni che differiscono solo per la proprietĂ di essere âconstâ.
Liste
Una lista è una struttura dati, formata da elementi dello stesso tipo collegati in catena, la cui lunghezza varia dinamicamente. La struttura di una lista è:
Struct elem { int inf; elem* pun; }
Un tipo di lista particolare è âlâ alberoâ che ha questa struttura:
Struct elem { int inf; elem* sin; elem* des; }
Classi
Un tipo âclasseâ ha una parte privata ed una pubblica. Tipicamente la parte privata contiene le strutture dati che realizzano il tipo, e la parte pubblica contiene le dichiarazioni delle funzioni che definiscono le operazioni fondamentali del tipo e che costituiscono lâinterfaccia. Le funzioni dichiarate in una classe sono dette funzioni membro, e sono le uniche che possono accedere alla parte privata della classe. Esempio:
class nome { parte privata;
public:
parte pubblica; };
Per definire od usare una funzione dichiarata nella classe si usa â::â il risolutore di visibilitĂ (assoc. sinistra, prioritĂ 17).
Puntatore this
Una funzione membro può riferirsi esplicitamente allâoggetto a cui viene applicata: per questo scopo utilizza il âpuntatoreâ costante predefinito this, contenente lâindirizzo dellâoggetto. Lâuso del puntatore this è necessario quando una funzione membro deve usare esplicitamente lâindirizzo dellâoggetto a cui è applicata, e quando deve restituire lâoggetto stesso.
Costruttori e Distruttori
Un costruttore è una funzione membro il cui nome è il nome della classe: il costruttore viene applicato automaticamente quando si crea un oggetto appartenente alla classe stessa; se definiamo un costruttore non è piĂš legale scrivere una definizione di oggetti senza inizializzatore, a questo scopo si crea un costruttore default con il metodo dellâoverloading (funzione();) o degli argomenti default(funzione(int i = 0);).
Un distruttore è una funzione membro che viene invocata automaticamente quando una variabile termina il suo tempo di vita; un distruttore per una classe ha come identificatore il nome della classe preceduto da â ~ â.
Oggetti costanti nelle classi
In una classe non si possono dichiarare costanti, tuttavia si possono dichiarare oggetti con lâattributo const, purchè questi vengano inizializzati nel momento in cui viene dichiarato un oggetto appartenente alla classe stessa.
Classi contenenti oggetti classe
Un membro di un oggetto classe può a sua volta essere un oggetto classe.
Array di oggetti classe
Si possono definire o allocare in memoria libera degli array di oggetti classe.
Membri statici
A volte conviene che una classe contenga delle informazioni globali, cioè appartenenti alla classe nel suo complesso e non alle singole istanze; ad esempio per una variabile contatore viene dichiarata come membro della classe e la sua dichiarazione inizia con la parola chiave static.
Classi Stream
Nelle librerie di ingresso/uscita sono definite le classi istream per lâingresso ed ostream per lâuscita: per usarle bisogna includere il file di intestazione . Lo stream cin è un istanza predefinita della classe istream e rappresenta lo stream standard di ingresso, mentre gli stream cout e cerr sono istanze predefinite della classe ostream e rappresentano gli stream standard di uscita e di errore. Gli operatori di lettura e di scrittura sono definiti per i tipi fondamentali, per i puntatori e per le stringhe (altrimenti vanno ridefiniti con lâoverloading). Se occorre leggere anche i caratteri di spaziatura, si usa la funzione membro get(); nella classe ostream è definita la funzione put(), che scrive un carattere nello stream di uscita.
Uso dei file
Nelle librerie di ingresso/uscita del C++ sono definite le classi ifstream, ofstream ed fstream, dichiarate nel file . Il nome del file viene passato al costruttore come stringa, e la classe ios::in serve per lâapertura del in lettura, ios::out per lâapertura in scrittura, ios::in|ios::out per lâapertura in lettura e scrittura, ios::app per lâapertura in append. La modalitĂ ios::nocreate impedisce la creazione del file nel caso questo non esista. Le funzioni membro sono open(), bad(), close() ed i contatori seekg() e seekp().
Ingresso e uscita per i tipi utente
Il meccanismo di overloading permette di ridefinire gli operatori di lettura e scrittura per i tipi definiti dallâutente.
sebbene sintetica, la guida risulta molto funzionale per un ripasso veloce del linguaggio
in oggeto.