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

Contenuto cancellato Contenuto aggiunto
Nessun oggetto della modifica
Riga 1:
{{Dal C al C++}}
 
=== 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 che non sono puntatori, come in "int ** a;", e così via.
Riga 89:
Questi fatti si esprimono con la frase: '''I riferimenti hanno la sintassi dei valori, ma la semantica dei puntatori'''.
 
=== Il modificatore "const" ===
 
In C++ esiste la parola-chiave "const" (abbreviazione della parola inglese "constant", cioè "costante"), che serve a modificare la dichiarazione di variabili e funzioni.
Riga 159:
* 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.
 
== Il sovraccaricamento ==
 
Un concetto introdotto dal C++ è il sovraccaricamento (in inglese "overload"), che si applica alle funzioni e agli operatori.
 
=== Il sovraccaricamento delle funzioni ===
 
Il seguente programma è errato in C, ma valido in C++:
double f(int x) { return (double)x; }
double f(float x) { return (double)x * 3; }
int main() {
double a = f(7);
double b = f(7.f);
}
Alla variabile "a" viene assegnato il valore "7.0", mentre alla variabile "b" viene assegnato il valore "21.0".
 
Infatti, in linguaggio C, la ''firma'' (in inglese, "signature") di una funzione è costituita da semplice nome: se il compilatore incontra due funzioni con lo stesso nome ma parametri dello stesso tipo, le considera uguali.
Invece, in C++, la firma di una funzione è costituita dal nome insieme alla lista dei tipi dei parametri.
I ''nomi'' dei parametri e il tipo di ritorno non fanno parte della firma.
 
Se il compilatore incontra due funzioni aventi la stessa firma, le considera uguali, mentre se la firma differisce le considera funzioni distinte.
 
Quando il compilatore C++ analizza una chiamata di funzione, non si limita a cercare se è già stata definita una funzione con lo stesso nome, ma una con la stessa firma, e, in caso affermativo, la chiamata viene fatta puntare a tale definizione.
 
Nell'esempio, la prima istruzione della funzione "main" chiama una funzione avente come firma "f(int)", mentre la seconda una funzione avente come firma "f(float)".
Si tratta di due funzioni distinte, e quindi il comportamento in fase di esecuzione sarà distinto.
 
Il sovraccaricamento può dare origine ad ambiguità, come nel seguente programma errato:
void f(double x) { }
void g(float x) { }
void g(double x) { }
int main() {
f(7);
g(7); // Illegale
}
La prima istruzione chiama la funzione "f" passandole un valore di tipo "int".
Non esistono funzioni di nome "f" e con un parametro di tipo "int", ma ne esiste una con nome "f" e un parametro di tipo "double", ed esiste una conversione standard da "int" a "double".
Pertanto il compilatore associa tale definizione di funzione alla chiamata "f(7)".
 
La seconda istruzione chiama la funzione "g" passandole un valore di tipo "int".
Non esistono funzioni di nome "g" e con un parametro di tipo "int", ma ne esistono due con nome "g", una delle quali con un parametro di tipo "float" e l'altra con un parametro di tipo "double".
Esistono anche le conversioni standard da "int" a "float" e da "int" a "double".
Siccome non c'è una funzione che corrisponda esattamente e c'è più di una funzione che corrispodne in modo approssimato, il compilatore non sa decidere quale delle due funzioni chiamare, e genera un errore.
 
Per evitare tale errore, in presenza di più funzioni sovraccaricate, si deve passare una parametro avente uno dei tipi per cui esiste la funzione sovraccaricata.
Per esempio, quell'istruzione avrebbe dovuto essere una delle due seguenti:
g(7.f); // Chiama g(float)
g(7.); // Chiama g(double)
oppure una delle due seguenti:
g((float)7); // Chiama g(float)
g((double)7); // Chiama g(double)
 
=== Il sovraccaricamento degli operatori ===
 
In C gli operatori hanno un significato prestabilito e immutabile.
 
In C++, questo è vero per i tipi fondamentali, ma è possibile definire nuovi operatori per i tipi definiti dall'utente.
 
Si consideri il seguente programma:
#include <iostream>
#include <complex>
using namespace std;
int main() {
complex<double> c1, c2;
cout << (c1 + c2);
}
Nell'ultima riga si usano l'operatore di ''scorrimento a sinistra'' "<<" e l'operatore di ''somma'' "+".
 
La libreria standard ha ridefinito tali operatori per la classe "complex<double>", pur mantenendo il vecchio significato per i tipi predefiniti nel linguaggio.
 
Quando il compilatore incontra il segno "+" valuta il tipo dell'espressione alla sua sinistra e il tipo dell'espressione alla sua destra.
In questo caso si tratta di due oggetti di tipo "complex<double>".
Siccome nella libreria standard è definito un operatore di nome "+" e avente parametri di tale tipo, a fronte di questo segno viene generata una chiamata a tale operatore.
 
Come si possano definire operatori sovraccaricati è un argomento avanzato, descritto in un altro capitolo.
 
== L'inizializzazione degli oggetti ==