domenica 30 dicembre 2012

Definire funzioni membro virtuali

Un oggetto di una classe derivata è anche un membro di una classe di base. Per esempio, il programma Capo virtuale, l'oggetto Capo è anche un oggetto Nemico. Tutto ciò ha senso in quando si tratta solo di un tipo di nemico speciale. Inoltre ha senso in quanto l'oggetto Capo ha tutti i membri dell'oggetto Nemico. Bene, e allora? Poiché una classe derivata è anche un membro della classe di base, si può usare un puntatore alla classe di base per puntare un oggetto della classe derivata. Questo è ciò che faccio in main() con la seguente istruzione, la quale istanzia un oggetto Capo su heap e crea un puntatore a Nemico che punti all'oggetto Capo.
Nemico* pCattivo = new Capo();

Cosa vuol dire tutto questo? E' utile a permetterci di gestire l'oggetto senza doverne conoscere il tipo esatto. Per esempio di può avere una funzione che accetta un puntatore a Nemico e può funzionare sia con Nemico che con Capo oggetti. La funzione non deve conoscere l'esatta tipologia di oggetto passato, può funzionare con oggetti che producono risultati diversi a seconda di quale sia utilizzato. Ma ora analizziamo cosa accade nella successiva riga di codice.
pCattivo - > Fastidio();

Invece mostra una linea di testo Il nemico vuole combatterti. Quindi non è stata richiamata la funzione Fastidio() dell'oggetto Capo ma dell'oggetto Nemico, nonostante sia stata superata dalla funzione Fastidio() nella classe di Capo. Questo accade come risultato di un legame precedente nel quale l'esatta funzione è legata al tipo di puntatore, in questo caso Nemico. Ciò di cui si ha bisogno è una funzione membro chiamata sul tipo di oggetto puntato, e non fissato dal puntatore. In questo si ottiene la flessibilità del legame successivo attraverso funzioni virtuali, che ottiene con il comportamento polimorfico. Per creare una funzione membro virtuale, si aggiunge semplicemente la parola chiave virtual prima del nome della funzione nella sua dichiarazione. Questo è ciò che viene fatto in Nemico con la seguente istruzione.
void virtual VFastidio() const
{ cout << “Il nemico vuole combatterti.\n”; }

Questo significa che VFastidio() è una funzione virtuale. E' virtuale in Nemico, e viene ereditata in Capo. Questo impila che VFastidio() in Capo possa superare con la seguente istruzione la funzione ereditata. La corretta versione di VFastidio() viene chiamata (in base al tipo di oggetto) e non sarà fissata dal tipo di puntatore.
void virtual VFastidio() const
{ cout << “Il capo vuole mettere fine alla tua esistenza.\n”; }

La prova del comportamento polimorfico viene in main() con la seguente istruzione, nel quale la funzione VFastidio() definita in Capo viene chiamata ed il testo “Il capo vuole mettere fine alla tua esistenza”. Viene mostrato su schermo.

pCattivo - > VFastidio();

Definire Distruttori virtuali

Quando si usa un puntatore alla classe di base per puntare un oggetto di una classe derivata, si può avere un potenziale problema. Quando viene cancellato il puntatore, solo il distruttore della classe viene chiamato per l'oggetto. Questo può condurre a risultati disastrosi poiché il distruttore della classe derivata è necessario per liberare la memoria ( come fa il distruttore di Capo). La soluzione come si può indovinare, è di rendere il distruttore della classe virtuale. In questo modo viene chiamato il distruttore della classe, il qual conduce (sempre) alla chiamata del distruttore della classe di base, dando ad ogni classe la possibilità di ripulirsi. La teoria viene messa in pratica quando si crea il distruttore virtuale della classe Nemico.
virtual ~Nemico(){ cout << “m_pDanno cancellato”; delete m_pDanno;}
Quando viene cancellato il puntatore che punta alla classe Capo con la seguente istruzione, il distruttore della classe Capo viene chiamato, il quale libera la memoria su heap dei dati m_pMultidanno e mostra il messaggio.
delete pCattivo;

Quindi viene chiamato il distruttore di Nemico, il quale libera la memoria su heap da m_pDanno e mostra il messaggio m_pDanno cancellato. L'oggetto è distrutto, e tutte le memorie associate ad esso sono cancellate.

venerdì 28 dicembre 2012

Presentare il Polimorfismo

Uno dei pilastri della OOP è il polimorfismo, con il quale le funzioni membro producono risultati differenti a seconda del tipo di oggetto nel quale sono stati chiamate in causa. Per esempio, un gruppo di nemici che il giocatore sta affrontando, il gruppo è formato da oggetti di tipo diverso imparentati tra loro dall'ereditarietà, i nemici ed i loro capi. Attraverso la magia del polimorfismo, si possono richiamare le stesse funzioni membro per ogni cattivo del gruppo, definire come attaccare il giocatore, e per ogni oggetto valutarne gli effetti. Una chiamata all'oggetto nemico produce un attacco debole, mentre una chiamata al capo produce risultati diversi, ad esempio un potente attacco. Questo può assomigliare al superamento, ma il polimorfismo è diverso, in quanto le chiamate delle funzioni sono dinamiche e gli effetti valutati in tempo reale, a seconda del tipo di oggetto. Il modo migliore per comprendere questo meccanismo è mostrarne esempi concreti.

Presentare il programma Capo virtuale

Il programma Capo virtuale dimostra come si usano le funzioni membro virtuali e come raggiungere il comportamento polimorfico. Mostra cosa accade quando si usa un puntatore alla classe di base per chiamare funzioni virtuali e non virtuali. Mostra anche l'uso dei distruttori verticali che assicurano la corretta distruzione degli oggetti richiamati tramite puntatori alla classe di base.
// Capo virtuale
//dimostra funzioni virtuali
#include<iostream>
using namespace std;
class Nemico
{public:
            Nemico(int danno=10){ m_pDanno = new int(danno); }
            virtual ~Nemico(){ cout << “m_pDanno cancellato”; delete m_pDanno;}
            void Fastidio() const
              { cout << “Il nemico vuole combatterti.\n”; }
           void virtual vFastidio() const
              { cout << “Il nemico vuole combatterti.\n”; }
protected: int* m_pDanno;
};
class Capo: public Nemico
{
public: Capo( int multi =2){ m_pMultiDanno = new int(multi); }
           virtual ~Capo(){ cout << “m_pMultiDanno cancellato”; delete m_pMultiDanno;}
            void Fastidio() const
             { cout << “Il capo vuole mettere fine alla tua esistenza.\n”; }
           void virtual vFastidio() const
            { cout << “Il capo vuole mettere fine alla tua esistenza.\n”; }
protected: int* m_pMultiDanno;
};
int main()
{ cout << “Puntatore a Nemico che punta a oggetto Capo:\n”;
   Nemico* pCattivo = new Capo();
   pCattivo - > Fastidio();
   pCattivo - > vFastidio();
 cout << “Cancelliamo puntatore a Nemico:\n”;
  delete pCattivo;
  pCattivo = 0;

return 0;
}

Uso dell'operatore di Assegnazione sovraccaricato e costruttore Copia nelle classi derivate

E' già stato spiegato come scrivere un operatore di assegnazione sovraccaricato e un costruttore copia per una classe. Tuttavia, scriverli per una classe derivata richiede un leggero lavoro ulteriore in quanto non sono ereditati dalla classe di base. Quando si sovraccarica l'operatore di assegnazione nelle classi derivate, di solito si richiama l'operatore di assegnazione delle classi base, utilizzando una chiamata esplicita con il nome della classe di base quale prefisso. Se Capo è derivato da Nemico, la funzione dell'operatore di assegnazione definita in Capo di solito recita:
Capo& operator=(const Capo& b)
{ Nemico::operator=(b); //gestisce i dati membro ereditati da Nemico
// di seguito bisogna gestire i dati membro di Capo }

La chiamata esplicita dell'operatore di assegnazione di Nemico gestisce i dati membro ereditati da esso. Ma il resto della funzione deve occuparsi dei dati definiti in Capo. Per il costruttore Copia, di solito si effettua una chiamata al costruttore Copia della classe di base. Se Capo è derivato da Nemico, il costruttore di copia definito in esso, si scrive:
Capo (const Capo& b): Nemico(b){
// si gestiscono i dati membro definiti in Capo }

Dalla chiamata del costruttore Copia di Nemico con Nemico(b), la copia dei dati membro di Nemico viene messa nel nuovo oggetto Capo. Nel resto della funzione di Capo del costruttore copia si bada a tutti i dati membro dichiarati solo in Capo del nuovo oggetto.

giovedì 27 dicembre 2012

Chiamata e superamento delle Funzioni membro classi Base

Non si viene bloccati su ogni membro funzione delle classi di base ereditati dalle classi derivate così come sono. Ma si possiede l'opzione di una personalizzazione di come vengano ereditate le funzioni membro e come esse lavorino nelle classi derivate. E' possibile superarle attraverso nuove definizioni all'interno delle classi derivate. Si può esplicitare una chiamata alla classe di base da ogni funzione membro della classe derivata.

Presentare il programma Capo superiore

Il programma Capo superiore dimostra la chiamata ed il superamento delle funzioni membro della classe di base in una classe derivata. Il programma crea un nemico che infastidisce il giocatore e lo attacca. In seguito il programma crea un Capo mediante una classe derivata. Anche il Capo infastidisce il giocatore e lo attacca, ma la cosa interessante è che il suo modo di approcciarsi ed attaccare viene modificato rispetto al Nemico (rendendolo più resistente). Questi cambiamenti sono ottenuti mediante il superamento della funzione derivata rispetto alla funzione della classe di base.
//Capo superiore
//dimostra la chiamata ed il superamento delle funzioni membro di base
#include <iostream>
using namespace std;
class Nemico
{
          public: Nemico(int danno = 5): m_Danno(danno){}
                     void Fastidio() const { cout << “Il nemico vuole combatterti.\n”; }
                     void Attacco const
                    { cout << “Attacco infligge” << m_Danno << “ punti danno.\n”;}
         private: int m_Danno;
};
class Capo: public Nemico
{
          public:
                   Capo(int danno= 20): Nemico(danno){} //chiamata al costruttore di base con argomento
                   void Fastidio() const { cout << “Il capo vuole mettere fine alla tua esistenza.\n”; }
                  void Attacco() const
                         { Nemico::Attacco();
                            cout << “Ride di te.\n”; }
};
int main()
{
   cout << “Crea un nemico.\n”;
   Nemico nemico1;
   nemico1.Fastidio();
   nemico1.Attacco();

   cout << “\n Crea un capo. \n”;
   Capo capo1;
   capo1.Fastidio();
   capo1.Attacco();

return 0;
}

domenica 23 dicembre 2012

Controllo accesso sotto Ereditarietà

Quando viene derivata una classe da un'altra è possibile controllare quanto accesso la classe derivata ha sui membri della classe di base. Per la stessa ragione, si vuole fornire sono la quantità di accesso necessario ai membri della classe base, nel resto del programma, che possa servire alla classe derivata. Non è una coincidenza se vengono utilizzati gli stessi modificatori di accesso visti in precedenza (public, private e protected).

Presentare il programma Semplice Capo 2.0

Il programma Semplice Capo 2.0 è un'altra versione del programma visto in precedenza. Ma in questa nuova versione, la quale fornisce gli stessi risultati all'utente, ma il codice è leggermente diverso in quanto sono state messe delle restrizioni sui membri della classe base.
//Semplice Capo 2.0
//Dimostrazione del controllo di accesso con ereditarierà
 #include <iostream>
 using namespace std;
 class Nemico
 {
     public:
               Nemico(): m_Danno(10){}
               void Attacco const
               { cout << “Attacco infligge” << m_Danno << “ punti danno.\n”;}
    protected: int m_Danno;
};
class Capo: public Nemico
{
    public:
              Capo():m_MultiDanno(3){}
              void AttaccoSpeciale() const
             { cout << “Attacco Speciale infligge” << (m_MultiDanno * m_Danno);
               cout << “ punti danno.\n”;}
   private: int m_MultiDanno;
};

int main()
{
   cout << “Crea un nemico.\n”;
   Nemico nemico1;
   nemico1.Attacco();
  
  cout << “\n Crea un capo. \n”;
  Capo capo1;
  capo1.Attacco();
  capo1.AttaccoSpeciale();
return 0;
}

Presentare il programma Semplice Capo

Il programma Semplice Capo dimostra il concetto di ereditarietà. In esso, viene definita una classe di nemici inferiori, Nemico. Da questa classe viene derivata una nuova classe per i Capi più resistenti che il giocatore deve affrontare. Quindi viene istanziato un oggetto Nemico e richiamata la sua funzione membro Attacco(). Di seguito viene istanziato un oggetto Capo, del quale è possibile richiamare la funzione Attacco(), in quanto eredita la funzione membro da Nemico. Infine viene richiamata la funzione membro AttaccoSpeciale() definita solo per la classe Capo. Mentre la classe Nemico non ha accesso a questa funzione in quanto esclusiva del capo.
//Semplice Capo

//Dimostrazione ereditarietà

   #include <iostream>

   using namespace std;

   class Nemico

   {

          public: int m_Danno;

          Nemico(): m_Danno(10){}

          void Attacco const

          { cout << “Attacco infligge” << m_Danno << “ punti danno.\n”;}

   };

 class Capo: public Nemico

 {

          public: int m_MultiDanno;

         Capo():m_MultiDanno(3){}

         void AttaccoSpeciale() const

         { cout << “Attacco Speciale infligge” << (m_MultiDanno * m_Danno);

            cout << “ punti danno.\n”;}

  };

int main()

{

   cout << “Crea un nemico.\n”;

   Nemico nemico1;

   nemico1.Attacco();



   cout << “\n Crea un capo. \n”;

   Capo capo1;

   capo1.Attacco();

   capo1.AttaccoSpeciale();

return 0;

}

sabato 22 dicembre 2012

Ereditarietà e polimorfismo

Le classi sono un esempio perfetto per rappresentare entità nel gioco che hanno attributi e comportamenti. Ma le entità nei giochi sono spesso correlate tra loro. In questi post saranno introdotti i concetti di ereditarietà e polimorfismo, i quali permettono di esprimere tali collegamenti e possono far costruire classi di gioco ancora più semplici ed intuitive. I concetti espressi saranno:
  • Derivare una classe da un'altra
  • Usare i dati membro e funzioni di una classe ereditata
  • maneggiare i membri della classe di base
  • Definire funzioni virtuali per abilitare il polimorfismo
  • Definire funzioni virtuali pure per le classi astratte
  • Suddividere il codice in file multipli

Introdurre l'Ereditarietà

Uno degli elementi chiave della OOP è l'ereditarietà, la quale permette di derivare una classe nuova da una esistente. Quando viene fatto, la nuova classe eredita (ottiene) i dati membro e le funzioni membro della classe esistente. E' come ottenere qualcosa che funziona con un lavoro gratuito della classe esistente. L'ereditarietà è molto utile quando si vuole creare una versione più specializzata di una classe esistente poiché si vogliono solo aggiungere nuovi dati membro e funzioni per estenderla. Un esempio è, immaginiamo di avere la classe Nemico che definisce un avversario nel gioco, la quale ha una funzione membro Attacco() e il dato membro m_Danno. Da questa si vuole ricavare la classe Capo, per un capo dei Nemici. Questo significa che la classe Capo possiede automaticamente una funzione Attacco() e un dato m_Danno senza dover scrivere del codice. Tuttavia per rendere il capo più resistente, viene aggiunta la funzione AttaccoSpeciale() e il membro dati Multi_Danno alla classe Capo. Uno dei maggiori vantaggi dell'ereditarietà è la possiiblità di riutilizzo delle classi già scritte. Il quale si riassume nei benefici:
  • Minore lavoro. Non è necessario definire funzionalità già possedute. Una volta che una classe definisce funzioni di base per le altre classi, il codice non deve essere riscritto di nuovo.
  • Meno errori. Un classe priva di bachi, può essere riutilizzata senza doverne cercare altri nel nuovo codice.
  • Un codice più leggero. Poiché le funzionalità di base delle classi esistono nel programma, non si deve ripercorrere lo stesso codice più volte, rendendo la scrittura del programma più leggera e facile da comprendere.
Molte entità nei giochi chiamano a gran voce l'ereditarietà delle classi. Che si tratti di uno squadrone di nemici, o una serie di veicoli comandati dall'utente, o un inventario di armi che il giocatore possiede. Tutti possono essere definiti come un gruppo di elementi connessi tra loro, risulta in una programmazione più veloce.

sabato 15 dicembre 2012

Il programma Sala da Gioco - conclusione

La funzione SalaGioco::AggiungiGiocatore()

La funzione membro SalaGioco::AggiungiGiocatore() aagiunge un giocatore alla fine della coda nella sala.
void SalaGioco::AggiungiGiocatore()
{ //crea un nuovo nodo giocatore
        cout << “Inserisci il nome del giocatore: ”;
        string nome; cin >> nome;
        Giocatore* pNuovoGiocatore = new Giocatore(nome);
   //Se la lista è vuota, questo nuovo giocatore diventa l'inizio della coda
         if(m_pTesta ===){ m_pTesta = pNuovoGiocatore; }
  //altrimenti si cerca la coda della lista e si aggiunge il giocatore
        else { Giocatore* pCiclo = m_pTesta;
                  while (pCiclo - > DaiPros() !=0)
                   { pCiclo = pCiclo - >DaiPros(); }
                pCiclo - > MettiPros(pNuovoGiocatore);
         }
}

La prima cosa che la funzione richiede è il nome del giocatore dall'utente in modo che possa essere usato per istanziare un nuovo oggetto Giocatore. Quindi setta il puntatore membro dell'oggetto a valore nullo. Nell'istruzione successiva la funzione controlla che la Sala da Gioco sia vuota, e non vi sia coda. Se così, il nuovo oggetto Giocatore diviene la testa della coda, m_pTesta è fissato sul blocco di memoria di questo giocatore su heap. Se la Sala da Gioco non è vuota, il giocatore è aggiunto al fondo della coda. La funzione realizza questa operazione spostandosi di giocatore in giocatore attraverso pCiclo e la funzione membro DaiPros(), sino a quando questa non restituisce un valore 0 (fine della coda). Quindi la funzione aggiunge un nuovo punto nodale all'oggetto Giocatore appena creato su heap, aggiungendo così un nuovo elemento alla lista.

La funzione SalaGioco::RimuoviGiocatore()

La funzione membro SalaGioco::RimuoviGiocatore() toglie il giocatore che si trova in testa alla fila.
void SalaGioco::RimuoviGiocatore()
{ if(m_pTesta ==0){ cout << “ La sala da Gioco e' vuota!”; }
       else { Giocatore* pTempo = m_pTesta;
                 m_pTesta = m_PTesta - > DaiPros();
                 delete pTempo; }
}

La funzione controlla m_PTesta, se il suo valore è 0, la Sala da Gioco è vuota pertanto viene mostrato un messaggio. Altrimenti il primo Giocatore della lista viene rimosso. Questa operazione viene ottenuta creando un puntatore temporaneo, pTempo , puntando ad esso il primo giocatore nella lista; quindi m_pTesta viene reindirizzato sul secondo giocatore della lista, oppure 0. Infine la funzione distrugge l'oggetto Giocatore fissato dal puntatore temporaneo, pTempo.

La funzione SalaGioco::Azzera()

La funzione membro SalaGioco::Azzera() rimuove tutti i giocatori dalla Sala.
void SalaGioco::Azzera()
{ while (m_pTesta !=0) RimuoviGiocatore(); }

Se la lista è vuota, il ciclo non viene eseguito e la funzione termina. Altrimenti, il ciclo viene eseguito e la funzione inizia a rimuovere il primo oggetto Giocatore della lista attraverso RimuoviGiocatore() sino a quando non vi sono più Giocatori.

La funzione operatore<<()

La funzione operator<<() sovraccarica l'operatore << in modo che possa visualizzare la SalaGioco attraverso l'istruzione cout.
ostream& operator<<(ostream& os, const SalaGioco& aSalaGioco)
{ Giocatore* pCiclo = aSalaGioco.m_pTesta;
    os << “\n Chi si trova nella Sala Gioco:\n”;
    if (pCiclo ==0){ os << “La sala e' vuota!\n”;}
        else { while(pCiclo !=0){ os << pCiclo - > DaiNome() << endl;
         pCiclo =pCiclo - > DaiPros();} }
     return os;
}

Se la Sala da Gioco è vuota, viene restituito un messaggio corretto. Altrimenti la funzione cicla attraverso tutti i giocatori presenti nella lista, inviando i loro nomi al flusso di uscita, attraverso il puntatore pCiclo che scorre la lista.

La funzione main()

La funzione main() mostra un menù utente con una serie di scelte che permette di effettuare le azioni richieste.
int main()
{
     SalaGioco miaSala;
     int scelta;
     do
        { cout << miaSala;
           cout << “\nSALA DA GIOCO\n”;
           cout << “0 – esci dal programma.\n”;
           cout << “1 – Aggiungi un giocatore alla sala.\n”;
           cout << “2 - Rimuovi un giocatore dalla sala.\n”;
           cout << “3 – Azzera la sala.\n”;
           cout << endl; << “Dai una scelta: ”; cin >> scelta;
   switch (scelta)
     {   case 0: cout << “Addio!\n”; break;
          case 1: miaSala.AggiungiGiocatore(); break;
          case 2: miaSala.RimuoviGiocatore();break;
          case 3: miaSala.Azzera(); break;
         default: cout << “Scelta non valida.\n”;
      }
  }while(scelta !=0);
    return 0;
}

La funzione per prima cosa istanzia un nuovo oggetto SalaGioco, quindi entra nel ciclo e presenta un menù di scelte, e attende la scelta dell'utente. Quindi richiama le funzioni corrispondenti di SalaGioco; se la scelta è non valida, il programma lo comunica. Il ciclo prosegue sino a quando l'utente non digita 0.

mercoledì 12 dicembre 2012

La classe SalaGioco

La classe SalaGioco rappresenta la sala o la fila nella quale i giocatori attendono.
Class SalaGioco
{ friend ostream& operator<<(ostram& os, const SalaGioco& aSalaGioco);
public:
          SalaGioco(): m_pTesta(0){}
          ~SalaGioco( ){ Azzera(); }
          void AggiungiGiocatore();
          void RimuoviGiocatore();
          void Azzera();
private:
         Giocatore* m_pTesta;
};

Il membro dati m_pTesta è un puntatore che indica l'oggetto Giocatore, il quale rappresenta la prima persona nella coda. m_pTesta è l'inizio della coda. Poiché ogni oggetto Giocatore ha un membro dati m_pPros, è possibile collegare un gruppo di oggetti Giocatore in una lista collegata. Elementi individuali della lista collegata sono chiamati nodi. Un modo di pensare ai nodi del giocatore è come un gruppo di vagoni connessi tra loro. Ogni vagone porta un nome e sono collegati attraverso un puntatore chiamato m_pPros. La classe SalaGioco alloca la memoria su heap per ciascuno dei giocatori sulla lista. La classe SalaGioco fornisce accesso al primo oggetto Giocatore in testa alla lista. Il costruttore è molto semplice e inizializza i dati membro di m_pTesta a 0, con un puntatore nullo. Il distruttore chiama semplicemente Azzera(), il quale rimuove tutti i giocatori dalla lista e libera la memoria allocata. AggiungiGiocatore() istanzia un nuovo oggetto Giocatore su heap, e lo aggiunge alla fine della lista. RimuoviGiocatore() toglie il primo oggetto Giocatore dalla lista e libera la memoria allocata. Viene dichiarata la funzione operator<<() amica di SalaGioco, in quanto l'oggetto SalaGioco possa essere visualizzato con cout usando l'operatore <<.

lunedì 10 dicembre 2012

Semplice programma OpenGL

Un semplice programma OpenGL che visualizza un rettangolo colorato in una finestra, mediante la libreria glut, necessaria per utilizzare le funzioni grafiche poligonali.

#include <GL/glut.h>

const int   A = 500;  /* lunghezza della finestra */
const float B = 500;  /* lunghezza dello spostamento del rettangolo */
const float C = 200;  /* lunghezza del quadrato */

void miainiz(void)
{
  glClearColor(0.3, 0.7, 0.7, 0.0); /* sfondo grigio */

  glMatrixMode(GL_PROJECTION);     
  glLoadIdentity();                
  gluOrtho2D( -B/2, B/2, -B/2, B/2);
  glMatrixMode(GL_MODELVIEW);     
}

void mostra( void )
{
                                   
  glClear(GL_COLOR_BUFFER_BIT);    

  glMatrixMode(GL_MODELVIEW);      
  glLoadIdentity();                

  glBegin(GL_POLYGON) ;            
      glColor3f ( 1.0, 0.1, 0.1);      
      glVertex2f( -C/2, -C/2 );      
      glVertex2f(  C/2, -C/2 );       
      glVertex2f(  C/2,  C/2 );        
      glVertex2f( -C/2,  C/2 );       
  glEnd();

  glFlush();                        /* invia tutti i comandi */
}

int main(int argc, char** argv)
{
  glutInit(&argc,argv);
  glutInitWindowSize( A, A );       /* A x A pixel finestra a schermi  */

  glutInitDisplayMode( GLUT_RGB | GLUT_SINGLE);
  glutCreateWindow("Mio rettangolo"); /* titolo finestra                   */
  glutDisplayFunc(mostra);         /* di a OpenGL cosa fare     */
  miainiz();                         /* imposta attributi                 */

  glutMainLoop();                   /* passa controllo a main loop  */
  return 0;
}

sabato 8 dicembre 2012

Il programma Sala da Gioco

Il programma sala da gioco, simula un'area di gioco in attesa di giocatori, solitamente per un gioco in rete. Il programma non coinvolge alcuna funzione in linea del computer. Ma crea una singola linea di testo per la quale il giocatore deve aspettare. L'utente del programma esegue una simulazione ed ha quattro scelte. Può aggiungere un giocatore alla sala da gioco, rimuovere una persona dalla sala (la prima ad entrare è al prima ad uscire), azzerare la sala, o uscire dalla sala da gioco.

La classe Giocatore

La prima cosa da fare quando si crea la classe Giocatore e rappresentare i giocatori che aspettano nella sala da gioco. Poiché non sappiamo quanti giocatori deve avere la sala ogni volta, ha senso usare una struttura dinamica. Solitamente, i contenitori utilizzati sono quelli delle librerie standard, ma in questo caso viene definito un contenitore dinamico che possa allocare la memoria ed essere gestito dal programma. Un' ottima via per vedere la memoria dinamica in azione.

 //Sala da Gioco
  //Simula una sala nella quale il giocatore può attendere
  #include<iostream>
  #include<string>
  using namespace std;
class Giocatore
{
public:
          Giocatore(const string& nome=” ”): m_Nome(nome), m_pPros(0){}
          string DaiNome() const { return m_Nome;}
          Giocatore* DaiPros() const { return m_pPros;}
          void MettiPros(Giocatore* prossimo){m_pPros = prossimo;}
private:
           string m_Nome;
           Giocatore* m_pPros; //puntatore al giocatore successivo
};
Il membro dati m_Nome tiene il nome del giocatore. Questo è molto comprensibile, ma ci si può chiedere quale tipo di dato contenga m_pPros. E' un puntatore all'oggetto Giocatore, il quale indica che ogni oggetto Giocatore può contenere il nome e puntare ad un altro oggetto Giocatore. Questo è molto comprensibile durante lo sviluppo dell'oggetto SalaDaGioco. La classe ha due metodi accessori per m_Nome come due funzioni accessorie dai e metti per m_pPros. Il costruttore della classe è molto semplice, inizializza una stringa m_Nome basandosi sull'argomento passato ed imposta m_pPros con un puntatore nullo.

mercoledì 5 dicembre 2012

Sovraccarico operatore Assegnazione

Quando entrambe le istruzioni di assegnazione sono oggetti della stessa classe, la funzione dell'operatore di assegnazione della classe viene chiamata. Come il costruttore copia, esiste un opzione di default fornita dal compilatore se non ne viene scritta una. Tuttavia come il costruttore copia anche questa si occupa solo di effettuare una duplicazione di ogni membro della classe. Per le classi più semplici, solitamente funziona bene. Comunque quando una classe punta ad alcuni dati membro memorizzati su heap, è consigliabile fornire una propria funzione di assegnazione. Altrimenti si creeranno copie vuote degli oggetti quando vengono assegnati l'un l'altro. Per evitare il problema dare un'occhiata alla funzione scritta per la classe Roditore.

   Roditore& operator= (const Roditore& c)
     { cout << “Overload operatore assegnazione”;
        if (this == &c) return *this;
        else {
                  m_pNome = new string;
                 *m_pNome = c.DaiNome();
                 }
    }
Da notare che la funzione membro restituisce un riferimento all'oggetto Roditore. Per un operatore di assegnazione robusto, deve essere restituito il riferimento all'assegnazione sovraccaricata della funzione membro. Di seguito in main(), sono richiamate le funzioni che verificano l'operatore di assegnazione.

void testAssegnaOp()
{ Roditore ctopo1(“topo1”);
   Roditore ctopo2(“topo2”);
   ctopo1 = ctopo2;
testAssegnaOp() crea due oggetti e ne assegna uno all'altro. La procedura di assegnazione, ctopo1=ctopo2; richiama l'operatore di assegnazione (=) per ctopo1. Nella funzione operator=(), c è una costante di riferimento a ctopo2. Dopo che operator=() viene visualizzato a schermo, viene usato il puntatore this. Di cosa si tratta? E' un puntatore a tutti gli oggetti funzione non statici , al quale punta l'oggetto che viene usato per chiamare la funzione. Nelle righe successive viene confrontato l'indirizzo di ctopo1 sia equivalente a ctopo2 nel caso l'oggetto sia assegnato a se stesso. Altrimenti viene eseguita l'istruzione *m_pNome = c.DaiNome(); che assegna la stringa topo2 al blocco di memoria di ctopo1. Infine si restituisce una copia di ctopo1 restituendo con *this. La verifica viene effettuata facendo DireCiao() a ctopo1 e ctopo2 ed ottenendo il medesimo risultato.

lunedì 3 dicembre 2012

Definire un Costruttore Copia

A volte gli oggetti vengono ricopiati automaticamente. Questo accade quando:
  1. Un oggetto passa un valore ad una funzione
  2. Un oggetto restituisce un valore da una funzione
  3. Inizializza un altro oggetto attraverso un inizializzatore
  4. Fornisce un singolo argomento al costruttore dell'oggetto
La copia viene effettuata attraverso una speciale funzione membro chiamata costruttore copia. Come i costruttori e i distruttori, una copia di default viene fornita se non scritta appositamente per l'oggetto. La copia di default semplicemente copia i valori di ogni dato membro a dati membro dello stesso nome nel nuovo oggetto. Per le classi più semplice il costruttore copia di default è più che sufficiente. Comunque quando si ha una classe con dati membro che puntano ad un valore su heap, solitamente viene scritto un proprio costruttore copia. La ragione è che il costruttore di default si limiterebbe a copiare i puntatori senza modificarne il contenuto, generando così una copia vuota, per la quale i blocchi di memoria non sono cambiati e puntano alle stesse locazioni dell'oggetto originale. Per fare un esempio specifico. Se non fosse stato scritto un proprio costruttore copia nel programma Dati membro su Heap, il programma avrebbe automaticamente creato una copia di ctopo chiamata copia che esiste in testCopiaCostruttore().
testCopiaCostruttore(ctopo);
Il dato membro m_pNome di copia avrebbe puntato esattamente allo stesso oggetto string su heap di ctopo m_pNome. Perché si crea un problema?
Al termine della funzione testCopiaCostruttore(), il distruttore di copia viene chiamato, liberando la memoria su heap alla quale puntava il dato membro m_pNome di copia. A causa di ciò ctopo m_pNome avrebbe puntato ad una memoria libera pertanto il dato membro si sarebbe trasformato in un puntatore appeso. Ciò di cui si ha veramente bisogno nel costruttore copia è di produrre un nuovo oggetto con il proprio segmento di memoria su heap alla quale puntare, una copia profonda. Questo è quello che viene fatto con la seguente definizione del costruttore copia, il quale sovraccarica il default fornito dal compilatore.

Roditore(const Roditore& c)
{ cout << “Copia costruttore”;
m_pNome = new string;
*m_pNome = c.DaiNome();
}
Così come il primo, anche in costruttore copia deve avere lo stesso nome della classe, e non restituisce alcun valore, ma accetta un riferimento ad un oggetto della classe, l'oggetto che deve essere copiato. Il riferimento è quasi sempre una costante che protegge l'oggetto originale dall'essere modificato durante il processo di copia. Quando viene chiamato testCopiaCostruttore() passando ctopo come valore della funzione, viene richiamato il costruttore copia. Appare la scritta Costruttore Copia sullo schermo; quindi viene creato un nuovo oggetto Roditore (la copia) che accetta i riferimenti originali in c. Con la linea m_pNome = new string; il costruttore alloca un nuovo blocco di memoria su heap al quale punta m_pNome dato membro per la copia. Nella riga successiva *m_pNome =c.DaiNome(); il costruttore copia acquisisce una copia dell'oggetto stringa che equivale a “Topolino” dall'originale scrivendolo sul nuovo blocco di memoria. Come risultato una copia profonda di ctopo viene creata, quella usata in testCopiaCostruttore(). Quando testCopiaCostruttore() termina, la copia del Roditore usata dalla funzione viene distrutta, lasciando l'oggetto originale al suo posto su heap.

domenica 2 dicembre 2012

Dichiarare un Dato che punta al suo Valore su Heap

Per dichiarare un membro dati che punta la suo valore su Heap, la prima cosa da fare è dichiarare il dato come un puntatore. Questo è ciò che viene fatto nella classe Roditore con la seguente linea di codice, la quale dichiara m_pNome come puntatore all'oggetto stringa.

String* m_pNome;
Nel costruttore della classe, viene allocata la memoria su heap, e assegnato un valore a tale memoria, quindi fissa il puntatore alla memoria. Questo è ciò che fa il costruttore con la seguente linea di istruzione, la quale assegna la memoria all'oggetto string, assegna un nome ad essa e punta m_pNome al blocco di memoria scelto.

m_pNome = new string(nome);
Quando main() chiama testDistruttore(), la funzione crea un oggetto Roditore. L'oggetto ha m_pNome come membro dati che punta all'oggetto stringa equivalente a “Topp”, memorizzato su heap.

Definire i Distruttori

Un distruttore è la funzione membro che viene chiamata subito prima che l'oggetto sia distrutto. I distruttori sono usati spesso dai programmatori per compiere una necessaria pulizia prima che l'oggetto scompaia per sempre. Il distruttore deve avere lo stesso nome della classe preceduto dal carattere tilde (~). Un distruttore non può avere parametri o restituire valori. Se non viene scritto dal programmatore, il compilatore ne aggiunge uno di base per supplire alla mancanza. Tuttavia il distruttore di default, quando si scrivono valori dei dati membro su heap è lacunoso; pertanto è necessario scrivere il proprio distruttore, in modo da liberare la memoria su heap associata all'oggetto prima che esso scompaia, evitando perdite di memoria. Questo è ciò che viene fatto per la classe Roditore.

~Roditore() //distruttore
{ cout << “Chiamata al distruttore\n”; delete m_pNome; }
Il distruttore mostra un messaggio e libera la memoria al quale puntava m_pNome. Da notare che non viene assegnato valore 0 a m_pNome, in quanto essa cessa di esistere dopo l'operazione di delete, viene evitato il pericolo di un puntatore appeso.

sabato 1 dicembre 2012

Lavorare con i dati membro e Heap

E' possibile dichiarare i dati come puntatori il cui valore è immagazzinato su heap. Comunque i problemi possono nascere quando lo si utilizza in quanto bisogna comprendere il comportamento degli oggetti di default.

Presentare il programma dati membro Heap

Il programma definisce un nuovo tipo di roditore i cui dati membro sono dei puntatori, i quali puntano a oggetti immagazzinati su heap. La classe definisce alcune nuove funzioni membro che possono gestire la situazione nel quale un oggetto viene distrutto, copiato o assegnato ad un altro oggetto. Il programma distrugge, copia o assegna gli oggetti mostrando come essi si comportano, anche quando i dati membro puntano a valori conservati su heap. Il codice per il programma è il seguente:
 
// Dati membro su Heap
// Dimostra come un oggetto alloca dinamicamente i dati membro
 #include<iostrream>
 #include <string>
 using namespace std;
class Roditore
 {
    public:
              Roditore (const string& nome = “ ”) //costruttore
             { cout << “Chiamata al costruttore\n”;  m_pNome= new string(nome); }
   
             ~Roditore() //distruttore
            { cout << “Chiamata al distruttore\n”; delete m_pNome; }
   
             Roditore(const Roditore& c)
            { cout << “Copia costruttore”;
               m_pNome = new string;
              *m_pNome = c.DaiNome();       
              }
           Roditore& operator= (const Roditore& c)
          { cout << “Overload  operatore assegnazione”;
             if (this == &c) return *this;
            else {
                     m_pNome = new string;
                    *m_pNome = c.DaiNome();
                   }       
           }        

   string DaiNome() const { return *m_pNome; }
   void MettiNome( const string& nome =” ”){ *m_pNome = nome; }
   void DireCiao() const { cout << “Ciao, il mio nome e' ” << DaiNome() << endl; }
 private:
            string* m_pNome;
     };
 void testDistruttore();
 void testCopiaCostruttore (Roditore copia);
 void testAssegnaOp();
 

int main()
{
    testDistruttore();
    cout << endl;
    Roditore ctopo(“Topolino”);
    ctopo.DireCiao();
     testCopiaCostruttore(ctopo);
    cout << endl;
    testAssegnaOp();
    return 0;
    }
void testDistruttore()
 { Roditore ctopo(“Topp”);
   ctopo.DireCiao();
 }

void testCopiaCostruttore (Roditore copia);
{ copia.DireCiao(); }

void testAssegnaOp()
{ Roditore ctopo1(“topo1”);
   Roditore ctopo2(“topo2”);
  ctopo1 = ctopo2;
  ctopo1.DireCiao();
  ctopo2.DireCiao();
  cout << “Rimettere a topo1 il suo nome\n”;
  ctopo1.MettiNome(“topo1”);
  ctopo1.DireCiao();
  ctopo2.DireCiao();
  }