Lezione 2 - Finalmente le classi!

Come si vede dallo schema dell'header file , la struttura di una classe C++ e' molto piu' complicata di quella di una classe Java. Questa maggiore complessita' e' creata dal fatto che variabili e metodi invece di avere dichiarazioni individuali di livello di accesso vanno raggruppati in zone private,public e protected. Inoltre il C++ permette di sovraccaricare i normali operatori algebrici e logici, per cui abbiamo definizioni anche di particolari metodi che implementano questi operatori. Infine abbiamo,oltre al costruttore, anche il distruttore che in Java si scrive solo raramente (e' il metodo finalize). Se una classe contiene un oggetto con puntatore(pointer data member) allora deve obbligatoriamente includere:
  1. Un distruttore
  2. Un costruttore di copia (cioe' un costruttore che ha come argomento un oggetto della stessa specie permettendone la copia dello stesso).Questo e' necessario per permettere il passaggio di un oggetto a un metodo per valore.
  3. Il sovraccarico dell'operatore di assegnazione (=)
Alcuni attributi dei metodi sono particolari del C++ e non esistono in Java:
Infine per quanto riguarda i livelli di accesso, abbiamo anche in C++ i livelli private,public,protected. Invece il livello di accesso di default di Java (accesso da parte delle classi dello stesso pacchetto) si realizza in C++ con l'attributo friend . Cioe' ogni classe deve dichiarare esplicitamente tutte le funzioni e le classi che possono accedere ai propri dati protetti come friend.


Programma 1: Realizza in C++ un semplice oggetto contatore .S

    file Counter.h

class Counter {
                    public:
                      Counter();      // Costruttore 
                      int getValue(); 
                      void increment(int n);                  
                    private:
                      int value;  
                }; // fine dichiarazione:nota il ;

    file Counter.cc

                #include "Counter.h"
                
                Counter::Counter() {
                   value = 0;  
                }

                void Counter::increment(int n) {
                   value += n; 
                }
                
                int Counter::getValue() {
                   return value;
                }

    file Calcola.cc

               #include "Counter.h"
               #include <iostream>    

               main() {  
               
                  Counter c; 
                                    
                  cout << c.getValue() << endl;
                  c.increment(5);
                  cout << c.getValue() << endl;
        
               }

L'istruzione per la compilazione diventa:
g++ -o Calcola Counter.cc Calcola.cc

Programma 2: Riscrivi il programma precedente in modo da usare funzioni inline e inizializzatori nel costruttore .S

    file Counter.h

              class Counter {
                    public:
                      Counter():value(0){}
                      Counter(int n):value(n){}
                      int getValue() {
                       return value; } 
                      void increment(int n){value += n;}                  
                    private:
                      int value;  
                }; 

    file Calcola.cc

               #include "Counter.h"
               #include <iostream>    

               main() {  
               
                  Counter c1(10); 
                  Counter *c2 = new Counter(20);
                                    
                  cout << c1.getValue() << endl;
                  c1.increment(5);
                  cout << c1.getValue() << endl;
                  cout << c2->getValue() << endl;
                  c2->increment(5);
                  cout << c2->getValue() << endl;
                  delete c2;
        
               }

Programma 3: Realizza una classe che permette di usare i numeri complessi .S

    file Complex.cc

 #include <iostream>    
 class Complex {
 public:
    Complex (  double a,  double b ): real(a),imag(b) { }
    Complex () :real(0),imag(0){ }
    Complex (const Complex& z):real(z.real),imag(z.imag) { }
    Complex add ( Complex y, Complex z ) {
        return  Complex (y.real+z.real, y.imag+z.imag);
    }
    Complex subtract ( Complex y, Complex z ) {
        return Complex (y.real - z.real, y.imag - z.imag);
    }
    Complex multiply ( Complex y, Complex z ) {
        return Complex (
            y.real * z.real - y.imag * z.imag,
            y.imag * z.real + y.real * z.imag );
    }

    Complex divide ( Complex y, Complex z ) {
        return  Complex (
            (y.real * z.real + y.imag * z.imag) /
                (z.real * z.real + z.imag * z.imag),
            (y.imag * z.real - y.real * z.imag) /
                (z.real * z.real + z.imag * z.imag) );
    }

    bool compare ( Complex y, Complex z ) {
        return (y.real == z.real && y.imag == z.imag);
    }
    void print() {cout << "(" << real << "," << imag << ")" << endl;}
    double imagpart ()  { return imag; }
    double realpart ()  { return real; }
    private:
      double real, imag;
};
int main()
{
     Complex a(1,4), b(3,8), c;
     a.print();b.print();
     c = a.add(a,b);
     c.print();
     Complex d(c);
     d = d.multiply(c,d);
     d.print();

}



Programma 4: Come il precedente ma ora aggiungi un distruttore e il sovraccarico dell'operatore di assegnazione = .S

    file Complex.cc

#include <iostream>    
 class Complex {
 public:
    Complex (  double a,  double b ): real(a),imag(b) { }
    Complex () :real(0),imag(0){ }
    Complex (const Complex& z):real(z.real),imag(z.imag) { }
    ~Complex () {cout << "L\'oggetto viene distrutto" << endl;}
    Complex& operator=(const Complex& z){real=z.real;imag=z.imag;return *this;}
    Complex add ( Complex y, Complex z ) {
        return  Complex (y.real+z.real, y.imag+z.imag);
    }
    Complex subtract ( Complex y, Complex z ) {
        return Complex (y.real - z.real, y.imag - z.imag);
    }
    Complex multiply ( Complex y, Complex z ) {
        return Complex (
            y.real * z.real - y.imag * z.imag,
            y.imag * z.real + y.real * z.imag );
    }

    Complex divide ( Complex y, Complex z ) {
        return  Complex (
            (y.real * z.real + y.imag * z.imag) /
                (z.real * z.real + z.imag * z.imag),
            (y.imag * z.real - y.real * z.imag) /
                (z.real * z.real + z.imag * z.imag) );
    }

    bool compare ( Complex y, Complex z ) {
        return (y.real == z.real && y.imag == z.imag);
    }
    void print() {cout << "(" << real << "," << imag << ")" << endl;}
    double imagpart ()  { return imag; }
    double realpart ()  { return real; }
    private:
      double real, imag;
};
int main()
{
     Complex a(1,4), b(3,8), c;
     a.print();b.print();
     c = a.add(a,b);
     c.print();
     Complex d(c);
     d = d.multiply(c,d);
     d.print();
     Complex e = d;
     cout <<"il risultato del confronto tra e,d ="<< e.compare(e,d) << endl;

}



Programma 5: Come il precedente ma ora definisci le varie operazioni come sovraccarico degli operatori numerici .S

    file Complex.cc

#include <iostream>    
 class Complex {
 public:
    Complex (  double a,  double b ): real(a),imag(b) { }
    Complex () :real(0),imag(0){ }
    Complex (const Complex& z):real(z.real),imag(z.imag) { }
    ~Complex () {}
    Complex& operator=(const Complex& z){real=z.real;imag=z.imag;return *this;}
    friend Complex operator+ ( const Complex& y, const Complex& z ) {
        return  Complex (y.real+z.real, y.imag+z.imag);
    }
    friend Complex operator- ( const Complex& y, const Complex& z ) {
        return Complex (y.real - z.real, y.imag - z.imag);
    }
    friend Complex operator* ( const Complex& y, const Complex& z ) {
        return Complex (
            y.real * z.real - y.imag * z.imag,
            y.imag * z.real + y.real * z.imag );
    }

    friend Complex operator/ ( const Complex& y, const Complex& z ) {
        return  Complex (
            (y.real * z.real + y.imag * z.imag) /
                (z.real * z.real + z.imag * z.imag),
            (y.imag * z.real - y.real * z.imag) /
                (z.real * z.real + z.imag * z.imag) );
    }

    bool compare ( Complex y, Complex z ) {
        return (y.real == z.real && y.imag == z.imag);
    }
    void print() {cout << "(" << real << "," << imag << ")" << endl;}
    double imagpart ()  { return imag; }
    double realpart ()  { return real; }
    private:
      double real, imag;
};
int main()
{
     Complex a(1,4), b(3,8), c;
     a.print();b.print();
     c = a+b;
     c.print();
     Complex d = c;
     d = (d*a+b)/c;
     d.print();

}



Programma 6: Scrivi un programma che permette di creare e stampare una lista di interi. S

    file Lista.cc

#include <iostream>    
class Nodo{
public:
 Nodo(int n, Nodo* p=0):dato(n),prossimo(p){}
 int dato;
 Nodo* prossimo;
};
int main()
{
 Nodo* p=new Nodo(0);
 for(int i=1;i<10;i++){
 p = new Nodo(i,p);
 }
 for(;p->prossimo;p=p->prossimo){
 cout<< p->dato << endl;
}
}


Programma 7: Come il precedente, ma questa volta la lista e' memorizzata in un vettore . S

    file Lista.cc

#include <iostream>    
class Lista{
public:
 Lista(): dati(new int[SIZE]), ndati(0), lunvec(SIZE){}
 ~Lista(){delete []dati;}
 Lista(const Lista &L):dati(new int[L.lunvec]),
   ndati(L.ndati), lunvec(L.lunvec) {
       for (int k=0; k < ndati; k++) {
         dati[k] = L.dati[k]; } }
 Lista & operator=(const Lista &L){
       if (this == &L) return *this;
       else {
         delete [] dati;
         dati = new int[L.lunvec];
         lunvec = L.lunvec;
         for (ndati=0; ndati < L.ndati; ndati++) {
           dati[ndati] = L.dati[ndati]; }
         return *this;  }}
 void aggiungi(int n){ndati=ndati+1;if(ndati<=lunvec){dati[ndati-1]=n;}
   else { cout << "superata la dimensione del vettore" << endl;}}
 void print (int i){cout << dati[i] << endl;}
private:
 static const int SIZE=10; 
 int *dati;
 int ndati;
 int lunvec;
};
int main()
{
 Lista l,l1;
 for(int i=0;i<11;i++){
 l.aggiungi(i);
 }
 l1 = l;
 for(int i=0;i<10;i++){
   l1.print(i);}
}


Programma 8: Come il precedente ma adesso la lista puo' contenere qualsiasi tipi di dati ed anche oggetti. S

    file Lista.cc

#include <iostream>    
template < class T >
class Lista{
public:
 Lista(): dati(new T[SIZE]), ndati(0), lunvec(SIZE){}
 ~Lista(){delete []dati;}
 Lista(const Lista < T >&L):dati(new T[L.lunvec]),
   ndati(L.ndati), lunvec(L.lunvec) {
       for (int k=0; k<ndati; k++) {
         dati[k] = L.dati[k]; } }
 Lista< T > & operator=(const Lista< T > &L){
       if (this == &L) return *this;
       else {
         delete [] dati;
         dati = new T[L.lunvec];
         lunvec = L.lunvec;
         for (ndati=0; ndati < L.ndati; ndati++) {
           dati[ndati] = L.dati[ndati]; }
         return *this;  }}
 void aggiungi(T n){ndati=ndati+1;if(ndati<=lunvec){dati[ndati-1]=n;}
   else { cout << "superata la dimensione del vettore" << endl;}}
 void print (int i){cout << dati[i] << endl;}
private:
 static const int SIZE=10; 
 T *dati;
 int ndati;
 int lunvec;
};
int main()
{
 Lista < double >l,l1;
 for(int i=0;i<11;i++){
 l.aggiungi(i);
 }
 l1 = l;
 for(int i=0;i<10;i++){
   l1.print(i);}
}

Questo e' un esempio di contenitore realizzato con template (modelli) .Un contenitore e' un oggetto formato da un insieme di altri oggetti come un array o un vector o una lista. Il costrutto template permette di realizzare contenitori non solo di oggetti ma anche di dati primitivi(in Java, se avete bisogno di introdurre un dato primitivo in un contenitore come Vector usate la corrispondente classe wrapper). Quella appena vista e' un esempio di classe generica .
C++ fornisce nella Standard Template Library(STL) vari tipi di contenitori realizzati con template. Per navigare nei contenitori vengono usati degli speciali oggetti detti iteratori che permettono di scrivere algoritmi indipendenti dal particolare contenitore usato.La STL stessa contiene parecchi di questi algoritmi generici che funzionano con vari tipi di dati. A livello di implementazione la STL e' stata divisa in diversi header files. Essi si trovano assieme agli altri header files del g++ in /usr/local/include/g++.

Generic Programming(Programmazione Generica) di cui abbiamo visto un esempio nel programma precedente, si riferisce alla possibilita' in un linguaggio di scrivere programmi che possono lavorare con diversi tipi di dati.In C++ si usa per questo l'approccio dei Template. Un template e' in effetti una specie di fabbrica per fare pezzi di codice:a seconda della dichiarazione, il programma generato dal template tratta interi, decimali, stringhe e in effetti qualsiasi tipo di oggetti.

La programmazione generica esiste anche in Java, ma qui l'approccio e' diverso:di solito si sfrutta il fatto che ogni oggetto in Java discende da Object per cui un contenitore di Object puo' contenere qualsiasi oggetto. (Ci sarebbe l'eccezione dei tipi primitivi, ma per questi si puo' usare la corrispondente classe wrapper.) Questo approccio,per quanto molto facile e intuitivo, e' invece inefficiente a livello di esecuzione se paragonato all'approccio C++ dei template.




Programma 9: Scrivete un programma che genera un vettore di interi a caso e li ordina usando i contenitori e gli iteratori della STL. S

    file Vector.cc

#include <vector>
#include <algorithm>
#include <iterator>
#include <iostream>
using namespace std;
int main() 
{
    vector< int> v(10,0);
    ostream_iterator< int> out(cout, " ");
    copy(v.begin(), v.end(), out);
    cout << endl;

    vector<int>::iterator i=v.begin() ;
    for(int k=0;k<10;k++){*i++ = rand()%1000;};
    copy(v.begin(), v.end(), out);
    cout << endl;

    sort(v.begin(), v.end());
    copy(v.begin(), v.end(), out);
    cout << endl;
    return 0;

}



Programma 10: Scrivete un programma che carica una lista di nomi di studenti in una mappa STL usando il numero di matricola(un intero) come chiave S

    file Mapex.cc


#include <iostream> 
#include <map> 
#include <string> 
using namespace std; 

int main() 
{ 
    typedef map<int,string *> Listastudenti; 

     Listastudenti listas; 
//  Riempi la mappa
    string s1 = "Pinco Pallino";
    listas[1021130] = &s1;
    string s2= "Rossi Mario";
    listas[1001050] = &s2;
    string s3= "Verdi Giuseppe";
    listas[821102] = &s3;
// Stampa gli elementi della mappa
    Listastudenti::iterator pos; 
    for (pos = listas.begin(); pos != listas.end(); ++pos) { 
        cout << "chiave: \"" << pos->first << "\" " 
             << "valore: " << (*pos->second) << endl; 
    } 
 // Vedi se c'e' lo studente con un certo numero di matricola e stampane il nome
    int matricola=1001050;
     pos = listas.find(matricola);
     if(pos!=listas.end()){
        cout << pos->first << " " << (*pos->second)<< " trovato" << endl;}
} 


La STL ha altri tre contenitori simili a map.
  1. multimap : come map ma due elementi di una multimap possono avere la stessa chiave.
  2. set : come map ma senza la colonna valore.
  3. multiset : set in cui due elementi possono avere la stessa chiave.



Programma 11: Scrivete una classe SingleCounter:un contatore che puo' avere un'unica istanza. S

    file SingleCounter.cc


#include <iostream> 

using namespace std; 
class SingleCounter{
    static SingleCounter c;
    int value;
    SingleCounter(int n):value(n){}
    void operator=(SingleCounter&);
    SingleCounter(const SingleCounter&);
   public:
    static SingleCounter& instance(){return c;}
    int getValue(){return value;}
    void increment(){value++;};
  };
 SingleCounter SingleCounter::c(0);

 int main() 
{ 

     SingleCounter& c1 = SingleCounter::instance();
     cout << c1.getValue() << endl;
     c1.increment();
      SingleCounter& c2 = SingleCounter::instance();
     c2.increment();
     cout << c1.getValue() <<" "<<  c2.getValue()<< endl;
  }

Per concludere ecco un esempio di grosso progetto software C++. Tutta la documentazione e il codice e' accessibile via Web. La gestione del codice stesso e' fatta via CVS che permette a piu' programmatori di collaborare allo stesso progetto software.


INDIETRO a Imparate C++ in 3 giorni
INDIETRO a Da Java al C++
Maintained by : info@zitogiuseppe.com
Ultimo aggiornamento: