Dal C al C++/Utilizzo basilare di librerie/Dichiarazioni e definizioni: differenze tra le versioni

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Riga 3:
== Le variabili riferimento ==
 
Nel linguaggio C, si possono definire variabili che non sono puntatori, come in "int a;", oppure variabili che sono puntatori a oggetti che non sono puntatori, come in "int * a;", oppure variabili che sono puntatori a oggetti che sono puntatori a oggetti, i chequali non sono puntatori, come in "int * * a;", e così via.
 
In C++ rimangono tutte queste possibilità, ma si aggiunge una nuova tipologia di variabile, il "riferimento".
Consideriamo le seguenti righe, di cui le prime due sono valide anche in C:
int a = 34;
int * b = & a;
int & c = a;
La prima riga definisce una variabile di nome "a", di tipo "int", e inizializzata al valore intero "34".
La seconda riga definisce una variabile di nome "b", di tipo puntatore a "int", e inizializzata all'indirizzo dell'oggetto rappresentato dalla variabile "a".
La terza riga, non valida in linguaggio C, definisce una variabile di nome "c", di tipo ''riferimento'' a "int", e inizializzata alla variabile "a".
 
<source lang=cpp>
La variabile "a" rappresenta un oggetto, nel senso che dichiarando la variabile "a" si alloca un oggetto di tipo "int" e contenente il valore 34, e anche nel senso che quando in seguito si usa la variabile "a" in un punto in cui è richiesta un'espressione, tale variabile costituisce un'espressione avente come valore il valore dell'oggetto associato alla variabile.
int a = 34;
int * b = & a;
int & c = a;
</source>
 
La prima riga definisce una variabile di nome "a", di tipo "int", inizializzata al valore intero "34".
La seconda riga definisce una variabile di nome "b", di tipo puntatore a "int", inizializzata all'indirizzo dell'oggetto rappresentato dalla variabile "a".
La terza riga, non valida in linguaggio C, definisce una variabile di nome "c", di tipo ''riferimento'' a "int", inizializzata all'oggetto rappresentato dalla variabile "a".
 
La variabile "a" rappresenta un oggetto, nel senso che dichiarando la variabile "a" si alloca un oggetto di tipo "int" contenente il valore 34, e anche nel senso che quando in seguito si usa la variabile "a" in un punto in cui è richiesta un'espressione, tale variabile costituisce un'espressione avente come valore il valore dell'oggetto associato alla variabile.
 
Anche la variabile "b" rappresenta un oggetto, che questa volta è un puntatore. Tale puntatore viene allocato quando si dichiara la variabile "b", e ha un valore che viene utilizzato nelle espressioni che usano la variabile "b".
Line 25 ⟶ 29:
Tale fatto si esprime dicendo che "c" è un ''alias'' di "a".
Per esempio, eseguendo il seguente codice.
 
c = 7;
<source lang=cpp>
cout << a;
c = 7;
cout << a;
</source>
 
Viene stampato il numero "7", in quanto l'oggetto riferito da "c" è lo stesso oggetto riferito da "a".
 
Line 33 ⟶ 41:
 
Si considerino le seguenti righe, supposte all'interno di una funzione:
 
int a2;
<source lang=cpp>
int * b2;
int & c2a2; // Illegale
int * b2;
int & c2; // Illegale
</source>
 
Le prime due continuano a definire variabili a cui corrispondono oggetti, anche se tali oggetti non sono inizializzati e quindi hanno valori indefiniti.
La terza riga è invece illegale sia in C che in C++.
Infatti, tenterebbe di dichiarare una variabile che costituisca un riferimento alternativo a un oggetto, ma non specifica a quale oggetto.
Siccome il collegamento di una variabile riferimento al suo oggetto può avvenire solo all'atto della definizione della variabile, tale variabile rimarrebbe per sempre non legata a un oggetto, e quindi completamente inutile.
Pertanto, il linguaggio considera illegale dichiarare variabili riferimento senza inizializzarle.
 
L'uso principale delle variabili riferimento, è però per dichiarare parametri di funzione.
Si consideri il seguente programma:
 
#include <iostream>
<source lang=cpp>
using namespace std;
#include <iostream>
double doppio1(double a) { return a * 2; }
using namespace std;
double doppio2(double * a) { return *a * 2; }
double doppio3doppio1(double & a) { return a * 2; }
double doppio2(double * a) { return *a * 2; }
int main() {
double doppio3(double & a) { return a * 2; }
int main() {
double x = 1;
cout << doppio1(x);
cout << doppio2(&x);
cout << doppio3(x);
}
</source>
 
Le funzioni "doppioNdoppio1", "doppio2", e "doppio3" sono sostanzialmente equivalenti, tanto che il programma stamperà "222".
Tali funzioni moltiplicano per due il valore della variabile "x", e rendono il risultato della moltiplicazione.
In realtà, mentre la funzione "doppio1" prende una ''copia'' del valore di "x", le funzioni "doppio2" e "doppio3" prendono esattamente l'oggetto rappresentato dalla variabile "x".
 
Per comprendere tale differenza, si consideri il seguente programma:
 
#include <iostream>
<source lang=cpp>
using namespace std;
#include <iostream>
void raddoppia1(double a) { a *= 2; }
using namespace std;
void raddoppia2(double * a) { *a *= 2; }
void raddoppia3raddoppia1(double & a) { a *= 2; }
void raddoppia2(double * a) { *a *= 2; }
int main() {
void raddoppia3(double & a) { a *= 2; }
int main() {
double x = 1;
raddoppia1(x);
Line 73 ⟶ 91:
raddoppia3(x);
cout << x;
}
</source>
 
Le funzioni "raddoppiaNraddoppia1", "raddoppia2", e "raddoppia3" ''non'' sono equivalenti, tanto che il programma stamperà "124".
 
Chiamando la funzione "raddoppia1", il valore dell'oggetto riferito dalla variabile "x" viene copiato sullo stack.
All'interno della funzione, tale valore viene riferito dal parametro "a".
La variabile "a" rappresenta un oggetto distinto da quello rappresentato dalla variabile "x", anche se inizialmente ha lo stesso valore.
Il corpo della funzione modifica tale oggetto allocato sullo stack, e, quando la funzione termina, l'oggetto modificato viene deallocato dallo stack, senza che ci siano effetti sul mondo esterno.
Quindi l'oggetto rappresentato dalla variabile "x" continua a valere "1".
 
Chiamando la funzione "raddoppia2", è l'''indirizzo'' dell'oggetto riferito dalla variabile "x" ad essere copiato sullo stack.
All'interno della funzione, tale valore viene riferito dal parametro "a".
Il corpo della funzione modifica l'oggetto puntato da tale valore allocato sullo stack, che è poi lo stesso oggetto associato a "x", e, quando la funzione termina, l'oggetto puntatore viene deallocato dallo stack.
Quindi l'oggetto rappresentato dalla variabile "x" è stato raddoppiato, passando dal valore "1" al valore "2".
 
Chiamando la funzione "raddoppia3", si ''comunica'' alla funzione che il parametro "a" dovrà rappresentare lo stesso oggetto rappresentato dalla variabile "x".
Questa ''comunicazione'' può avvenire copiando sullo stack l'indirizzo dell'oggetto rappresentato dalla variabile "x".
Il corpo della funzione modifica l'oggetto puntato da tale valore allocato sullo stack, e, quando la funzione termina, l'oggetto puntatore viene deallocato dallo stack.
Siccome la variabile "a" è solo un alias della variabile "x", le modifiche apportate ad "a" risultano applicate anche a "x".
Quindi l'oggetto rappresentato dalla variabile "x" è stato raddoppiato, passando dal valore "2" al valore "4".
 
Come si vede, il corpo della funzione "raddoppia3" è uguale a quello della funzione "raddoppia1", ma il comportamento è uguale a quello della funzione "raddoppia2".
Questi fatti si esprimono con la frase: '''I riferimenti hanno la sintassi dei valori, ma la stessa semantica dei puntatori'''. Questa frase significa: '''I riferimenti hanno lo stesso aspetto dei valori, ma lo stesso comportamento dei puntatori'''.
 
== Il modificatore "const" ==
Line 101 ⟶ 120:
In C++ esiste la parola-chiave "const" (abbreviazione della parola inglese "constant", cioè "costante"), che serve a modificare la dichiarazione di variabili e funzioni.
Ecco alcuni esempi di utilizzo:
const int x1 = 0;
const int *x2;
int * const x3 = 0;
const int * const x4 = 0;
La variabile "x1" è tipo "int", ed è non modificabile.
 
<source lang=cpp>
La variabile "x2" è di tipo puntatore a un "int" non modificabile, ma la variabile "x2" è modificabile.
const int x1 = 0;
const int * x2;
int * const x3 = 0;
const int * const x4 = 0;
</source>
 
La variabile "x3x1" èrappresenta un oggetto di tipo puntatore"int", e non modificabilepuò aessere unusata "int"per modificare tale modificabileoggetto.
 
La variabile "x4x2" èrappresenta un oggetto di tipo puntatore non modificabile a un "int" non modificabile.
Tale variabile può essere usata per modificare l'oggetto puntatore, rendendolo nullo o facendolo puntare a un altro oggetto, ma non può essere usata per modificare l'oggetto puntato, qualunque esso sia.
 
La variabile "x3" rappresenta un oggetto di tipo puntatore a "int".
L'inizializzazione di "x1", "x3", e di "x4" è obbligatoria, in quanto si tratta di variabili non modificabili, e quindi non gli si potrà assegnare un valore in seguito.
Tale variabile non può essere usata per modificare l'oggetto puntatore, che quindi dovrà sempre puntare allo stesso oggetto, ma può essere usata per modificare l'oggetto puntato.
 
Infine, anche la variabile "x4" rappresenta un oggetto di tipo puntatore a "int".
Tale variabile non può essere usata per modificare l'oggetto puntatore, che quindi dovrà sempre puntare allo stesso oggetto, e non può essere usata neanche per modificare l'oggetto puntato.
 
L'inizializzazione di "x1", di "x3", e di "x4" è obbligatoria, in quanto si tratta di variabili non modificabili, e quindi non gli si potrà assegnare un valore in seguito.
La variabile "x2" invece può non essere inizializzata, in quanto, essendo modificabile, le si potrà assegnare un valore in seguito.
Quello che non sarà modificabile è l'oggetto puntato da "x2", qualunque esso sia.
 
Ecco quali sono gli utilizzi ammessidelle precedenti variabili che il linguaggio ammette e quali quelli vietatiche dal linguaggiovieta:
 
x1 = 1; // Illegale
<source lang=cpp>
x2 = 0; // OK
x3x1 = 01; // Illegale
x4x2 = 0; // IllegaleOK
x3 = 0; // Illegale
Solo la variabile "x2" non è "const", e quindi gli altri assegnamenti sono illegali.
*x2x4 = 10; // Illegale
</source>
*x3 = 1; // OK
 
*x4 = 1; // Illegale
Solo la variabile "x2" è "non-const", e quindi solo lei è modificabile.
 
<source lang=cpp>
*x1 = 1; // Illegale
*x2 = 1; // Illegale
*x3 = 1; // OK
*x4 = 1; // Illegale
</source>
 
La variabile "x1" non è un puntatore, e quindi è ovviamente illegale dereferenziarla come se lo fosse.
Le variabili "x2", "x3" e "x4" sono dei puntatori, quindi si può tentare di assegnare un valore all'oggetto puntato.
Tuttavia "x2" e "x3x4" hanno il tipo di puntatori a "const", e quindi è illegale usare tali puntatori per modificare l'oggetto puntato.
 
Si noti che il modificatore "const" non si applica agli oggetti, ma alle variabili, come mostrato dal seguente esempio:
 
int main() {
<source lang=cpp>
int main() {
int x = 1;
int * p1 = &x;
Line 137 ⟶ 175:
*p1 = 3;
*p2 = 4; // Illegale
}
</source>
Sia la variabile "p1" che la variabile "p2" sono puntatori inizializzati a puntare allo stesso oggetto, associato anche dalla variable "x".
 
Tale oggetto in sé è modificabile, infatti lo si può modificare assegnandogli il valore "2" tramite la variabile "x", sia assegnandogli il valore "3" tramite il puntatore "p1".
Sia la variabile "p1" che la variabile "p2" sono puntatori inizializzati a puntare allo stesso oggetto, associato anche alla variable "x".
Tuttavia il tentativo di assegnargli il valore "4" tramite il puntatore "p2" è un errore sintattico, in quanto la variabile "p2" ha il tipo di puntatore a oggetto non modificabile.
Tale oggetto in sé è modificabile, infatti lo si può modificare sia assegnandogli il valore "2" tramite la variabile "x", sia assegnandogli il valore "3" tramite il puntatore "p1".
Tuttavia, il tentativo di assegnargli il valore "4" tramite il puntatore "p2" è un errore sintattico, in quanto la variabile "p2" ha il tipo di puntatore a oggetto non modificabile.
 
Il modificatore "const" si può applicare anche ai parametri di qualunque funzione, e alle funzioni membro stesse.
Per esempio:
 
void f1(const int &x5) {
<source lang=cpp>
void f1(const int& x5) {
x5 = 1; // Illegale
}
class C {
int x6;
void f2() const {
x6 = 2; // Illegale
}
};
</source>
 
Il parametro "x5" è di tipo riferimento a un "int" non modificabile, e quindi è illegale assegnare a tale variabile.
 
Un riferimento non può mai essere reindirizzato a un altro oggetto, pertanto la seguente riga è erronea:
 
void f1(int & const x5) { } // Illegale
<source lang=cpp>
void f1(int & const x5) { } // Illegale
</source>
 
Nella classe "C", la funzione membro "f2" non prende argomenti, ma presenta la parola "const" appena prima del corpo.
Line 164 ⟶ 210:
 
Gli scopi del modificatore "const" sono i seguenti:
* Rendere il codice autodocumentante, e quindi più comprensibile. Il programmatore potrebbe scrivere un commento come "questa routine si limita a leggere l'oggetto", ma oltretale atecnica essereè più verbosoverbosa e menonon standard, il compilatore non potrebbe assicurare la veridicità di tale commento.
* Evidenziare errori di programmazione. Se una variabile non deve essere modificata e invece lo è, si tratta di un errore logico che verrà segnalato dal compilatore.
* Rendere il codice più efficiente. Se il compilatore sa che una variabile ha un valore costante, può propagare il suo valore ovunque è usata la variabile. Per variabili locali, questo viene già fatto dai compilatori ottimizzanti, ma normalmente non viene fatto per le variabili globali. Questo fatto rende inutile e sconsigliato l'uso delle macro del preprocessore (la direttiva "#define") per definire costanti.
 
== L' Overloading o Sovraccaricamento ==
 
Un concetto introdotto dal C++ è il sovraccaricamento (in inglese "overload"), che si applica alle funzioni e agli operatori.
 
=== L' Overloading delle funzioni ===
 
Il seguente programma è errato in C, ma valido in C++: