Differenze tra le versioni di "Dal C al C++/Classi isolate"

Jump to navigation Jump to search
wikificazioni varie
m (ortografia)
(wikificazioni varie)
vector<float> vf1;
vector<float> vf2(8);
Trattandosi di variabili di tipo valore, cioè '''né puntatori né riferimenti''', ad esse corrisponde un oggetto del tipo indicato alla loro sinistra.
Per ognuno dei quattro tipi presentati, vengono dichiarate '''due variabili''', delle quali una non è inizializzata esplicitamente, e l'altra sì.
 
La variabile "'''a1'''" essendo di un tipo fondamentale, '''non viene''' affatto''' inizializzata'''.
'''Tutte''' le altre variabili '''sono invece inizializzate''', in quanto in assenza di un inizializzatore esplicito ne viene eseguito uno implicito.
 
Tali tipi non fondamentali, per quanto facenti parte della libreria standard, non fanno parte del linguaggio e sono quindi implementati usando il linguaggio stesso.
 
Un primo abbozzo è il seguente:
<pre>
struct Complex {
double re_; // Parte reale
double im_; // Parte immaginaria
};
</pre>
Possiamo dichiarare un oggetto di tale classe nel seguente modo:
Complex c;
Tuttavia, il nostro oggetto '''non sarà inizializzato'''.
Per inizializzare automaticamente un oggetto, si deve definire una funzione membro che il compilatore chiami automaticamente alla creazione dell'oggetto.
Tale funzione membro viene detta essere un "'''costruttore'''".
 
Per far sapere al compilatore che tale funzione è un costruttore, basta assegnare a tale funzione lo stesso nome della classe, come nel seguente codice:
<pre>
struct Complex {
Complex() { re_ = 0; im_ = 0; }
double im_; // Parte immaginaria
};
</pre>
Quindi, non si può dare a una funzione membro lo stesso nome della classe, se non per indicare che si tratta di un costruttore.
A questo punto il nostro oggetto è '''automaticamente''' inizializzato con '''entrambe le variabili membro poste a zero'''.
 
Ora vorremmo che funzionasse anche la seguente inizializzazione esplicita:
Complex c(2.4, 5.9);
A tale scopo, è necessario dichiarare un costruttore che accetti due parametri, come nel seguente codice:
<pre>
struct Complex {
Complex() { re_ = 0; im_ = 0; }
double im_; // Parte immaginaria
};
</pre>
Questo codice è corretto, in quanto si possono definire più costruttori sovraccaricati, purché, come tutte le funzioni sovraccaricate, differiscano per il tipo dei parametri.
Tuttavia è anche possibile, e più compatto, usare i valori di default per i parametri, come nel seguente codice:
<pre>
struct Complex {
Complex(double re = 0, double im = 0) { re_ = re; im_ = im; }
double im_; // Parte immaginaria
};
</pre>
 
== I distruttori ==
 
Visto il successo della creazione di una classe per i numeri complessi, proviamo a creare una classe per le stringhe, che chiamiamo "'''String'''".
<pre>
struct String {
String(const char * s = "") {
int lunghezza_; // Spazio utilizzato
};
</pre>
Il costruttore riceve un puntatore a carattere che si assume punti all'inizio di una stringa del C, cioè a un array di caratteri che termina con uno zero binario.
Se si costruisce un oggetto di tipo "String" non inizializzato esplicitamente, il costruttore viene chiamato comunque, ricevendo il valore di default del parametro, che è una stringa vuota.
 
L'oggetto sembra funzionare correttamente, ma ha un grosso problema: la memoria dinamica allocata da "new" nel costruttore non viene mai rilasciata.
Si tratta di un cosiddetto "'''memory leak'''", cioè di una "'''perdita di memoria'''".
 
Il problema è dovuto al fatto che nel costruttore è stata allocata una risorsa, nella fattispecie un array di caratteri, e non c'è nessuna istruzione che la dealloca.
 
Ecco un programma completo che usa costruttore e distruttore:
<pre>
#include <iostream>
using namespace std;
<< "[" << s3.c_str() << "]";
}
</pre>
Il programma stampa "[][abcd][XYZ]".
 
La variabile "'''s1'''" '''non è inizializzata esplicitamente''', ma il suo costruttore viene chiamato comunque, senza parametri; quindi viene usato il valore di default dell'unico parametro.
 
Le variabili "'''s2'''" e "'''s3'''" sono inizializzate esplicitamente, '''con due sintassi diverse''', ma con la '''stessa semantica''', e quindi il loro costruttore viene chiamato '''allo stesso modo'''.
 
È stata aggiunta anche la funzione "'''c_str'''" per rendere l'uso della classe identico a quello della classe standard "string".
Tale funzione è "'''const'''", in quanto la chiamata a tale funzione non modifica l'oggetto di tipo "String" a cui si applica, e rende un puntatore "'''const char'''", in quanto non si vuole consentire la modifica dell'array interno tramite questo puntatore.
 
== Separazione tra interfaccia e implementazione ==
 
Una programma di dimensioni non banali in linguaggio C è composto da più unità di compilazione, che sono i file che tipicamente hanno suffisso "'''.c'''", e da più file di intestazione applicativi, che tipicamente hanno suffisso "'''.h'''".
Nei file di intestazione si pongono le dichiarazioni delle variabili, costanti, funzioni, e tipi globali.
Nelle unità di compilazione si pongono le definizioni delle variabili e funzioni globali, nonché dichiarazioni e definizioni di variabili, costanti, funzioni, e tipi locali al modulo.
 
In C++ tutto questo viene conservato, ma solitamente si usa un'estensione diversa per le unità di compilazione.
L'estensione più usata è "'''.cpp'''" (per "C plus plus"), anche se alcuni usano "'''.cxx'''" ("C++" con i "più" ruotati), o ".cc", o ".C" con la lettera maiuscola.
Per i file di intestazione l'estensione è più usata è ".h", come in C, ma alcuni usano ".hpp" o ".hxx".
 
In una classe una funzione membro può essere definita o anche soltanto dichiarata, cioè si può inserire solo quello che in C si chiama prototipo di funzione.
 
Per separare la definizione dalla dichiarazione, si procede come nel seguente esempio, in cui si suppone di avere tre file: "main.cpp", "c.cpp" e "c.h".
<pre>
"main.cpp", "c.cpp" e "c.h".
// Contenuto del file "c.h"
#ifndef C_H
cout << c.f();
}
</pre>
Per generare un eseguibile, bisogna compilare i file "'''main.cpp'''" e "'''c.cpp'''", e poi linkarli insieme.
Il programma stampa "0 1".
 
Il file "'''c.h'''" contiene le cosiddette "'''guardie di compilazione'''" che sono le direttive per il preprocessore "'''#ifndef'''", "#define" e "#endif".
Servono a evitare che il tale '''file sia letto più volte'''.
 
Infatti, per ogni unità di compilazione, ogni definizione di classe deve essere incontrata una sola volta dal compilatore.
Provando a togliere tali direttive, in realtà il programma è ancora corretto, ma basta duplicare una direttiva di inclusione del file di intestazione, che il compilatore si lamenta che "'''struct C'''" è definito più volte.
Ovviamente, tutti i programmatori evitano di includere un file di intestazione più volte nella stessa unità di compilazione, ma siccome gli stessi file di intestazione spesso includono altri file di intestazione, è facile giungere a inclusioni multiple.
 
Nella definizione di "struct C", le funzioni "'''f'''" e "'''g'''" hanno '''solo la dichiarazione''', come nei prototipi di funzione.
Questo è sufficiente a compilare il file "main.cpp", ma, se non esistesse il file "c.cpp", il linker lamenterebbe l'assenza della definizione della funzione "'''f'''", vista come membro della classe "C", in quanto tale funzione '''viene chiamata dalla funzione "main"'''.
Per la funzione "'''g'''" non è necessaria '''nessuna definizione''', in quanto non è '''mai utilizzata'''.
 
Il file "c.cpp" include il file "c.h" e poi definisce il corpo della funzione membro "f".
 
Siccome qui siamo all'esterno della classe, definire semplicemente una funzione "f" non sarebbe una corretta definizione di funzione membro, in quanto rimarrebbe indeterminata la classe a cui appartiene tale funzione.
Pertanto, si usa l'espressione "'''C::f'''", in cui il nome della funzione è ''qualificato'' dal nome della classe.
I due caratteri di "due punti" costituiscono un unico operatore, detto "'''operatore di risoluzione di ambito'''" (in inglese, "''scope resolution operator''").
 
Sarebbe stato errato scrivere "C: :f", mentre "C :: f" sarebbe stato corretto.

Menu di navigazione