Differences

This shows you the differences between two versions of the page.

Link to this comparison view

appunti3s:introduzione_alle_classi [2018/04/25 07:55] (current)
Line 1: Line 1:
 +====== Le classi ======
 +===== Introduzione =====
 +Anche gli oggetti, come le funzioni, sono pezzi di programma che possono essere riutilizzati,​ ma a differenza delle tradizionali funzioni, questi trattano i dati in modo più "​responsabile"​.
 +Immaginando che il programma sia un insegnante che interroga gli studenti, e che come prima cosa debba conoscere il nome dello studente: ​
 +  * Un insegnante //orientato alle funzioni//, prenderebbe in mano il documento dello studente, per poter leggere il nome dal suo documento.
 +    * {{ :​appunti3s:​orientato_funzione.png?​156 |}}
 +  * Un insegnante //orientato agli oggetti//, chiederebbe gentilmente allo studente di fornirgli il proprio nome.
 +    * {{ :​appunti3s:​orientato_oggetti.png?​199 |}}
 +
 +Il primo insegnante, rispetto al secondo, agisce utilizzando un eccesso di forza e si comporta in modo sgarbato. Questo è il modo con cui si comporta una //​tradizionale funzione// con tutti i dati. Infatti il programmatore può creare una funzione così forte che può modificare i parametri che gli vengono passati, questo perché non esiste nessun controllo che limita la volontà del programmatore.
 + ​Questo permette di scrivere rapidamente le funzioni nel programma, ma rende difficile scoprire eventuali errori presenti nel codice di una funzione, perché non esiste una sola funzione responsabile delle modifiche fatte su un certo dato.
 +
 +Il secondo insegnante pensa che gli studenti abbiano diritto alla loro privacy e che inoltre sia più sicuro chiedere il nome invece che sperare di trovare, chissà in quale tasca, un documento. Anche il programmatore,​ usando gli oggetti, assegna a loro delle responsabilità,​ e se c'è un errore nel funzionamento del programma, sa immediatamente dove trovare il responsabile. Perché, di solito, per ogni dato esiste un solo responsabile.
 +
 +Ci sono anche altri vantaggi nell'​uso della programmazione orientata agli oggetti, come la facilità della manutenzione,​ dello sviluppo e del riutilizzo del codice, e altri che saranno compresi attraverso altri esempi.  ​
 +Per alcuni tipi di problemi la programmazione orientata agli oggetti invece non è la soluzione ideale....
 +Di solito l'​approccio migliore per descrivere la soluzione di un problema dipende anche dal problema...
 + Un po' come quando alcuni problemi si risolvono meglio usando la ricorsione e altri usando l'​iterazione.
 +====Gli oggetti sono strumenti di lavoro====
 +Gli oggetti sono strumenti che realizzano o forniscono qualcosa sui dati.
 +In questo modo si evita di occuparsi direttamente dei dati e si lascia che sia l'​oggetto ad occuparsene.
 +====Gli oggetti hanno una privacy====
 +Gli oggetti sono responsabili dei propri dati, ma pretendono riservatezza. (vedere [[appunti3s:​introduzione alle classi#data hiding]]) Non di deve pretendere di sapere come un oggetto esegue il proprio lavoro, ma chiedere solo che produca il risultato aspettato. Non c'è bisogno di capire qualcosa quando non siamo noi ad occuparcene.
 +
 +===== Esempio =====
 + 
 +Le classi sono pezzi di codice che dipendono un po' anche da altri classi, ma come primo esempio si vedrà una sola classe.
 +Classe {{:​appunti3s:​contocorrente1.zip|ContoCorrente}} (che in futuro si potrebbe legare ad una classe Correntista)
 +
 +In particolare,​ il codice sorgente dell'​header può essere rappresentato con la successiva immagine ​
 +
 +<file c contocorrente.h>​
 +
 +#include <​cmath>​
 +
 +class ContoCorrente
 +   {
 +     ​public:​
 +            ContoCorrente(const float& cifra); ​  //​costruttore
 +
 +            const float& saldo() const; ​         // il metodo ispettore non deve 
 +                                                 // poter modificare nemmeno tramite backdoor
 +            void versareDenaro(const float& cifra);
 +            void prelevareDenaro(const float& cifra);
 +
 +     ​private:​
 +            float mSaldo; ​                       // dato membro
 +                                                 // potrebbe essere utile aggiungere ​
 +                                                 // il numero del conto
 +   };
 +
 +</​file>​
 +
 +{{:​appunti3s:​contocorrente1.png}}
 +
 +==== Dati membro ====
 +...
 +==== Funzioni membro ====
 +
 +All'​interno di una classe, di solito, sono dichiarate le principali funzioni che operano sui dati di quella classe. Queste funzioni sono dette funzioni membro, vedere esempio nella classe ContoCorrente.
 +Il costruttore,​ il distruttore sono casi particolari di funzioni membro che vengono studiate a parte.
 +
 +Le funzioni membro della classe ContoCorrente possono essere chiamate solo tramite un oggetto di tipo ContoCorrente,​ col la seguente sintassi:
 +Vedere anche codice sorgente di {{:​appunti3s:​contocorrente1.zip|ContoCorrente.cpp}}
 +<​code>​
 +ContoCorrente mioConto(); ​ // creo l'​oggetto mioConto
 +mioConto.preleva(30); ​     // chiamo la funzione membro</​code> ​
 +
 +==== Funzioni esterne ====
 +
 +Altre volte le funzioni potrebbero essere dichiarate anche fuori della classe, ma poi, per operare su un determinato oggetto, diventa necessario passare l'​oggetto attraverso i reference. In tal caso si può scrivere.
 +
 +
 +<​code>​
 +void preleva(ContoCorrente&​ c, int soldi); // dichiarazione della 
 +                                           //​funzione,​ usando reference</​code>​
 +<​code>​preleva(mioConto,​30); ​               // chiamata della funzione</​code>​
 +
 +===== Incapsulamento delle funzioni=====
 +L'​incapsulamento (encapsulation) è un concetto che spiega come, riducendo l'​accoppiamento del codice nel programma, si riesce ad aumentare la facilità di manutenzione e di riutilizzo. Per esempio, facendo in modo che alcune parti del codice possano essere utilizzabili solo da altre parti del codice.
 +==== Data hiding ====
 +Con //data hiding//, un caso particolare di incapsulamento,​ si spiega che l'​isolamento del codice si può ottenere anche diminuendo la visibilità delle variabili usate da alcune parti di codice. Il data hiding essere realizzato usando le classi, infatti esse usano lo specificatore di accesso //private// per default (le //struct// invece no).
 +==== Specificatori di accesso ====
 +Esistono tre //​specificatori di accesso//: ​
 +  * //public//
 +  * //​protected//​
 +  * //private//
 +Lo specificatore di accesso è seguito da //due punti// (:) e modifica la visibilità di tutto quello che segue.
 +=== public ===
 +Quando un elemento viene dichiarato usando //public//, significa che vi si può accedere da qualsiasi parte.
 +=== private === 
 +Quando un elemento viene dichiarato usando //​private//,​ significa che vi si può accedere solo all'​interno della struttura/​classe dove è stato dichiarato.
 +=== protected ===
 +È usato nella composizione tra classi, come nell'​[[appunti3s:​ereditarietà]]
 +==== Esempio ====
 +
 +Come nell'​esempio {{appunti3s:​contocorrente1.zip}},​ la classe ContoCorrente possiede il membro //private// mSaldo: questo significa che è visibile solo dalle funzioni membro della classe e non da altri oggetti. Chiunque voglia conoscere il contenuto di mSaldo, o voglia modificarlo,​ deve farlo usando le funzioni membro di ContoCorrente. A loro volta, anche alcune di queste funzioni sono essere //​private//,​ ma almeno una di loro sarà //public//
 +
 +===== Costruttore =====
 +Poiché la classe è un nuovo tipo di dato, esso deve descrivere, oltre alla struttura dei dati e delle sue funzioni membro, anche come (1)//​riservare//,​ (2)//​inizializzare//​ e infine (3)//​liberare//​ la memoria da esso occupata. Il //​costruttore//​ serve per riservare ed inizializzare la memoria. La liberazione è fatta dal //​distruttore//​.
 +
 +Il //​costruttore//​ è una funzione che permette di realizzare (creare) gli oggetti in memoria, di un certo tipo di dato (la classe).
 +
 +====Dichiarazione====
 +La //​dichiarazione//​ costruttore assomiglia a quella di una normale funzione membro, ma a differenza di questa:
 +  - non restituisce nulla (neppure void);
 +  - possiede lo stesso nome della classe a cui appartiene.
 +
 +<​code>​
 +Class Esempio ​ // definizione della classe ​
 +{
 +  public: ​
 +    Esempio(); ​            // il compilatore permette di usare un costruttore di default (predefinito)
 +                           // ma di dichiarare anche diversi costruttori personalizzati
 +                           // che differiscono solo per i parametri usati
 +    Esempio(int a, int b); // dichiarazione del costruttore personalizzato
 +    Esempio(const int a);
 +    ~Esempio(); ​           // dichiarazione del distruttore
 +    int fun( int a);             // dichiarazione di funzione membro
 +    ​
 +  private:
 +    int x;
 +    int y;
 +    const int k;
 +    Cosa c1;
 +    Cosa& c2;
 +};
 +</​code>​
 +====Definizione====
 +Il costruttore costruisce l'​oggetto nella memoria costruendo prima i dati membri, nell'​ordine in cui sono dichiarati nella classe, e eseguendo per ultimo il codice del costruttore vero e proprio (il distruttore procede in ordine inverso). Per evitare confusione è utile usare lo stesso ordine usato per la dichiarazione dei membri, anche nella dichiarazione dei parametri del costruttore.
 +<​code>​
 +Esempio::​Esempio(int a, int b)  // definizione del costruttore
 +{
 +   x = a;  // inizializzazione dei membri dell'​oggetto tramite assegnazione
 +   y = b;
 +   // resto del codice...
 +}
 +</​code>​
 +===La lista di inizializzazione dei membri===
 +C'è un modo molto comodo di mettere in evidenza l'​ordine di inizializzazione dei membri appena descritto, ed è la cosiddetta //lista di inizializzazione//,​ in cui l'​inizializzazione dei membri non viene fatta con un'​assegnazione nel corpo del costruttore,​ ma prima del codice, utilizzando i //due punti// (:).
 +
 +<​code>​
 +Esempio::​Esempio(int a, int b)  //​definizione del costruttore
 +       : x(a), y(b)             // inizializzazione tramite lista
 +{
 +    // resto del codice...
 +}
 +
 +</​code>​
 +
 +Questa tecnica, alternativa alle assegnazioni,​ non è obbligatoria per i tipi primitivi (come //int//) ma è comunque consigliata. Doventa indispensabile usarla per i seguenti tipi:
 +  * per i membri costanti, ​
 +  * per tutti i membri che non usano il costruttore predefinito (oggetti)
 +  * per tutti i reference.
 +
 +Prima verranno eseguiti tutti gli inizializzatori e dopo il codice definito nel costruttore
 +
 +===== Funzioni membro predefinite =====
 +Sono funzioni che esistono sempre, anche se il programmatore non le definisce. In questo modo si ottiene la garanzia che alcune operazioni di uso frequente siano possibili. Si tratta di operazioni necessarie alla creazione/​distruzione di oggetti:
 +  * costruttore minimo <​code>​MiaClasse();</​code>​
 +  * distruttore ​ <​code>​~MiaClasse();</​code>​
 +  * costruttore di copia<​code>​MiaClasse (const MiaClasse&​);</​code>​
 +  * operazione di assegnazione con copia <​code>​MiaClasse&​ operator= (const MiaClasse&​ o)</​code>​
 +
 +Oltre ad essere operazioni frequenti, si tratta anche di operazioni delicate, e spesso il programmatore le deve ri-definire,​ per ottenere il risultato che desidera.
 +
 +==== Costruttore predefinito ====
 +
 +<​code>​MiaClasse();​ // è privo di argomenti</​code>​
 +È utile definirlo quando si crea una nuova classe in una libreria, perché chi utilizzerà la libreria potrebbe creare un oggetto senza fornire adeguati elementi di inizializzazione ed un eventuale costruttore predefinito fornirebbe una certa garanzia sul contenuto del nuovo oggetto. ​
 +Inoltre il costruttore predefinito può essere riutilizzato all'​interno degli altri costruttori,​ fornendo la garanzia della funzionalità e di non dover riscrivere due volte le stesse cose...
 +==== Distruttore predefinito ====
 +
 +<​code>​~MiaClasse();​ // deve essere privo di argomenti</​code>​
 +È molto simile al precedente, ma va ricordato che al distruttore non si passa (mai?) nessun argomento e quindi a maggior ragione è importante che esista sempre.
 +
 +==== Costruttore di copia predefinito ====
 +Quando si vuole passare un oggetto ad una funzione __per valore__, il programma deve poter realizzare una copia di tale oggetto, perciò deve sapere come farlo. Questo costruttore dovrebbe essere personalizzato dal programmatore,​ ad esempio, quando dentro l'​oggetto ci fossero dei puntatori. ​
 +È bene comunque ricordare che in alternativa al passaggio per valore si potrebbe passare l'​oggetto usando un reference costante. Questo eviterebbe anche l'​allocazione di nuova memoria...
 +
 +Nel caso in cui un oggetto (x1) contiene puntatori ad altri oggetti (y1), il costruttore di copia predefinito effettuerebbe solo una copia dei suoi dati membro e verrebbero solo copiati gli indirizzi contenuti nei puntatori. ​
 +Esisterebbero due nuovi oggetti (x1 e x2), ma modificando il contenuto di x2 si rischia di modificare il contenuto di x1.
 +
 +dichiarazione del costruttore con copia
 +<​code> ​   MiaClasse (const MiaClasse&​);</​code>​
 +
 +definizione
 +<​code> ​   MiaClasse::​MiaClasse (const MiaClasse&​ o)
 +      :lista di inizializzazione..
 +    {}</​code>​
 +
 +==== Operatore assegnazione di copia predefinito ====
 +Quando si inizializza un oggetto (//a//) assegnandogli un altro oggetto dello stesso tipo (//b//), si esegue una copia di //b// su //a//.
 +<​code>​a = b; </​code>​
 +Questa operazione di assegnazione opera in modo simile ad un costruttore di copia, dovendo inizializzare tutti i dati membro di a, in base a quelli di b.
 +
 +dichiarazione dell'​operatore assegnazione con copia
 +<​code> ​   MiaClasse&​ operator= (const MiaClasse&​ o)</​code>​
 +
 +definizione
 +<​code> ​   MiaClasse&​ MiaClasse::​operator= (const MiaClasse&​ o)
 +    {
 +     //...
 +     ​return *this;
 +    }</​code>​
  
  • appunti3s/introduzione_alle_classi.txt
  • Last modified: 2018/04/25 07:55
  • (external edit)