sabato 12 luglio 2014

Istanza della classe e oggetto puntato dal puntatore come cosa unica e non diverse istanze della classe.

Adesso vediamo se, creando un puntatore e attribuendovi l'indirizzo dell'istanza della classe, la variabile puntata dal puntatore è la stessa cosa dell'istanza della classe o una sua copia.
Il procedimento è molto semplice:
void main(){

 Triangolo mioTriangolo(3,4);
 Triangolo * vrbl;
 vrbl=&mioTriangolo;

 cout << mioTriangolo.area() << endl;
 cout << vrbl->area() << endl;
 vrbl->modificaBase();
 cout<< mioTriangolo.area() << endl;
 cout << vrbl->area() << endl;
 getchar();
}
6
6
246
246


Nel momento in cui è scattata la funzione modificaBase, si modifica sia l'area dell'istanza mioTriangolo sia quella dell'oggetto puntato dal puntatore. Questo significa che diversamente dalle variabili con i puntatori non si crea una nuova istanza della classe, ma viene puntata sempre la stessa.

Come mi aspettavo dalle mie reminiscenze di C++.

Istanza di una classe e variabile cui è stato attribuito il valore di quella istanza: cosa unica o due diverse istanze?

Variabile come nuova istanza della classe.

Aggiungo nella classe una funzione che modifica la base della figura:
class Rettangolo{
protected:
 int base,altezza;
public:
 Rettangolo(){}
 Rettangolo(int b, int a){
  base=b;
  altezza=a;
 }
 void modificaBase(){
  base=123;
 }
 int area(){
  return base*altezza;
 }
};
E adesso, per vedere se la variabile mioTriangolo e la variabile vrbl di tipo Triangolo che ho creato e cui ho attribuito il valore di mioTriangolo sono la stessa cosa o due istanze diverse della classe, faccio così:
void main(){

 Triangolo mioTriangolo(3,4);
 Triangolo vrbl;
 vrbl=mioTriangolo;

 cout << mioTriangolo.area() << endl;
 cout << vrbl.area() << endl;
 vrbl.modificaBase();
 cout<< mioTriangolo.area() << endl;
 cout << vrbl.area() << endl;
 getchar();
}

}
Creo una istanza mioTriangolo della classe Triangolo e poi una variabile vrbl di tipo Triangolo cui attribuisco il valore dell'istanza mioTriangolo.
Quindi metto a video l'area di mioTriangolo e di vrbl, faccio scattare nella variabile vrbl la funzione modificaBase e quindi rimetto a video l'area di mioTriangolo e di vrbl.
6
6
6
246

Ecco: sono due cose diverse.

Variabili oggetto di classi base e derivate.

Quindi, riepiloghiamo.

Posso creare una variabile di tipo classe base.
A questa variabile posso attribuire l'oggettoistanza di una classe derivata.

 Triangolo mioTriangolo(3,4);
 Rettangolo vrbl;
 vrbl=mioTriangolo;
Il codice funziona.
Adesso creo una classe Foo, che non fa parte della "famiglia":
class Foo{
protected:
 int ciccia;
public:
 Foo(int c){
  ciccia=c;
 }
};
E vediamo se una variabile di tipo Rettangolo (classe base) può accettare un valore di un'istanza della classe Foo.

 Triangolo mioTriangolo(3,4);
 Foo mioFoo(3);

 Rettangolo vrbl;
 vrbl=mioFoo;
1>------ Inizio compilazione: Progetto: test1, Configurazione: Debug Win32 ------
1>  foglio.cpp
1>c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(42): error C2679: '=' binario: non è stato trovato alcun operatore che accetti un operando destro di tipo 'Foo'. È anche possibile che non vi siano conversioni accettabili.
1>          c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(16): potrebbe essere 'Rettangolo &Rettangolo::operator =(const Rettangolo &)'
1>          durante la ricerca di corrispondenza con l'elenco di argomenti '(Rettangolo, Foo)'
========== Compilazione: 0 completate, 1 non riuscite, 0 aggiornate, 0 ignorate ==========
Ecco: non è possibile una conversione.
Posso quindi creare una variabile di tipo classe base e attribuire un oggetto istanza di una classe derivata, ma non posso attribuire un oggetto istanza di una classe diversa.
E vediamo se posso creare una variabile di tipo classe derivata e attribuirle un oggetto istanza di una classe base
 Rettangolo mioRettangolo(3,4);

 Triangolo vrbl;

 vrbl=mioRettangolo;
1>------ Inizio compilazione: Progetto: test1, Configurazione: Debug Win32 ------
1>  foglio.cpp
1>c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(42): error C2679: '=' binario: non è stato trovato alcun operatore che accetti un operando destro di tipo 'Rettangolo'. È anche possibile che non vi siano conversioni accettabili.
1>          c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(27): potrebbe essere 'Triangolo &Triangolo::operator =(const Triangolo &)'
1>          durante la ricerca di corrispondenza con l'elenco di argomenti '(Triangolo, Rettangolo)'
========== Compilazione: 0 completate, 1 non riuscite, 0 aggiornate, 0 ignorate ==========
No: l'inverso non si può fare.

giovedì 10 luglio 2014

Studio delle funzioni virtuali con le variabili oggetto.

Ho due classi, una classe base Rettangolo e una classe derivata Triangolo.
#include "iostream"
using namespace std;

class Rettangolo{
protected:
	int base,altezza;
public:
	Rettangolo(){}
	Rettangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza;
	}
};

class Triangolo:public Rettangolo{
public:
	Triangolo(){}
	Triangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza/2;
	}
};

void main(){
	Triangolo mioTriangolo(3,4);
	Triangolo vrbl;
	vrbl=mioTriangolo;
	cout << vrbl.area() << endl;
	

	getchar();
}
Ho creato una variabile Triangolo e vi attribuisco il valore dell'oggetto mioTriangolo che ho creato istanziando la classe.
Vediamo come si comporta.

6


Si comporta come un triangolo.

Adesso creo una variabile Rettangolo e vi attribuisco il valore dell'oggetto mioTriangolo.
void main(){
	Triangolo mioTriangolo(3,4);
	Rettangolo vrbl;
	vrbl=mioTriangolo;
	cout << vrbl.area() << endl;
	

	getchar();
}
12


Si comporta come un rettangolo.
Rendiamo virtuale la funzione nella classe base.
class Rettangolo{
protected:
	int base,altezza;
public:
	Rettangolo(){}
	Rettangolo(int b, int a){
		base=b;
		altezza=a;
	}
	virtual int area(){
		return base*altezza;
	}
};


Creiamo di nuovo la variabile, prima di tipo Triangolo e dopo di tipo Rettangolo.
void main(){
	Triangolo mioTriangolo(3,4);
	Triangolo vrbl;
	vrbl=mioTriangolo;
	cout << vrbl.area() << endl;
	

	getchar();
}
6


Si comporta come un triangolo.

Ora creiamo una variabile Rettangolo:
void main(){
	Triangolo mioTriangolo(3,4);
	Rettangolo vrbl;
	vrbl=mioTriangolo;
	cout << vrbl.area() << endl;
	

	getchar();
}
12


Si comporta come un rettangolo.

Dunque, se la funzione non è virtuale, se si attribuisce il valore di un oggetto della classe derivata a una variabile del tipo della classe derivata, la variabile si comporta come oggetto della classe derivata. Se la funzione è virtuale, se si attribuisce il valore di un oggetto della classe derivata a una variabile del tipo della classe base la variabile si comporta come un oggetto della classe base.

mercoledì 9 luglio 2014

Classe base che viene istanziata ed ereditata.

Adesso mi esercito a creare una classe base che viene istanziata ed ereditata.
Per essere istanziata, non deve essere una classe generica... Facciamo che sia un rettangolo, che derivi la classe Triangolo.

#include "iostream"
using namespace std;

class Rettangolo{
protected:
	int base,altezza;
public:
	Rettangolo(){}
	Rettangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza;
	}
};

class Triangolo:public Rettangolo{
public:
	Triangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza/2;
	}
};

void main(){
	Rettangolo mioRettangolo(3,4);
	Triangolo mioTriangolo(3,4);
	cout << mioRettangolo.area() << endl;
	cout << mioTriangolo.area() << endl;

	getchar();
}
12
6


E fin qui ci siamo.

Proviamo a usare un puntatore:
#include "iostream"
using namespace std;

class Rettangolo{
protected:
	int base,altezza;
public:
	Rettangolo(){}
	Rettangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza;
	}
};

class Triangolo:public Rettangolo{
public:
	Triangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza/2;
	}
};

void main(){
	Rettangolo mioRettangolo(3,4);
	Triangolo mioTriangolo(3,4);
	Rettangolo *pRect=&mioRettangolo;
	Triangolo *pTriang = &mioTriangolo;
	cout << pRect->area() << endl;
	cout << pTriang->area()<< endl;

	getchar();
}
12
6


Funziona ancora, perché ho creato un puntatore di tipo Rettangolo alla classe base e uno di tipo Triangolo alla classe derivata.
Ma proviamo a creare due puntatori di tipo Rettangolo per ambedue le classi:
#include "iostream"
using namespace std;

class Rettangolo{
protected:
	int base,altezza;
public:
	Rettangolo(){}
	Rettangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza;
	}
};

class Triangolo:public Rettangolo{
public:
	Triangolo(int b, int a){
		base=b;
		altezza=a;
	}
	int area(){
		return base*altezza/2;
	}
};

void main(){
	Rettangolo mioRettangolo(3,4);
	Triangolo mioTriangolo(3,4);
	Rettangolo *pRect=&mioRettangolo;
	Rettangolo *pTriang = &mioTriangolo;
	cout << pRect->area() << endl;
	cout << pTriang->area() << endl;

	getchar();
}
Ecco, adesso invece è:
12
12


Se il puntatore è del tipo della classe base, quando viene chiamato il metodo viene eseguito quello della classe base, come se la classe derivata fosse un rettangolo.
Provo con valori diversi di base e altezza per spiegarmelo meglio.
void main(){
	Rettangolo mioRettangolo(3,4);
	Triangolo mioTriangolo(2,3);
	Rettangolo *pRect=&mioRettangolo;
	Rettangolo *pTriang = &mioTriangolo;
	cout << pRect->area() << endl;
	cout << pTriang->area() << endl;

	getchar();
}
12
6


Ecco: base e altezza forniti al costruttore della classe derivata sono 2 e 3, e l'area calcolata è 6, come se si trattasse di un rettangolo, non di un triangolo.

Quindi il problema è solo quando si usi un puntatore alla classe base. Usando un puntatore alla classe base, si può far comportare l'oggetto della classe derivata come un oggetto della classe base.

Il costruttore predefinito nella creazione di classi derivate

Ecco, adesso per continuare a ripassare le funzioni virtuali provo a istanziare la classe base Poligono, e mi trovo di fronte a un nuovo problema:
#include "iostream"
using namespace std;

class Poligono{
protected:
 int base,altezza;
public:
 Poligono(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return 0;
 }

};

class Rettangolo:public Poligono{
public:
 Rettangolo(int b,int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza;
 }
};

class Triangolo:public Poligono{
public:
 Triangolo(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza/2;
 }
};


void main(){
 Rettangolo mioRettangolo(3,4);

 Triangolo mioTriangolo(3,4);

 Poligono mioPoligono(3,4);

 cout << mioRettangolo.area() << endl;
 cout << mioTriangolo.area() << endl;
 cout << mioPoligono.area() << endl;
 getchar();
}
1>------ Inizio compilazione: Progetto: test1, Configurazione: Debug Win32 ------
1>  foglio.cpp
1>c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(20): error C2512: 'Poligono': non è disponibile alcun costruttore predefinito appropriato
1>c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(31): error C2512: 'Poligono': non è disponibile alcun costruttore predefinito appropriato
1>c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(49): error C2065: 'mioPoligono': identificatore non dichiarato
1>c:\users\antonello\documents\visual studio 2010\projects\test1\test1\foglio.cpp(49): error C2228: l'elemento a sinistra di '.area' deve avere una classe, struttura o unione
1>          il tipo è ''unknown-type''
========== Compilazione: 0 completate, 1 non riuscite, 0 aggiornate, 0 ignorate ==========
Ecco: non è disponibile alcun costruttore predefinito appropriato. Manca il costruttore di default della classe base, che nel momento in cui io creo un altro costruttore non viene più sottinteso ma deve essere esplicitamente dichiarato.

Ovvio all'errore:
#include "iostream"
using namespace std;

class Poligono{
protected:
 int base,altezza;
public:
 Poligono(){}
 Poligono(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return 0;
 }

};

class Rettangolo:public Poligono{
public:
 Rettangolo(int b,int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza;
 }
};

class Triangolo:public Poligono{
public:
 Triangolo(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza/2;
 }
};


void main(){
 Rettangolo mioRettangolo(3,4);

 Triangolo mioTriangolo(3,4);

        Poligono mioPoligono(3,4);
 cout << mioRettangolo.area() << endl;
 cout << mioTriangolo.area() << endl;
 cout << mioPoligono.area() << endl;
 getchar();
}
E proviamo di nuovo:
12
6
0


Ripasso delle funzioni virtuali in C++

Ecco la funzione virtuale!

Mi sono chiesto "ma perché bisogna ripetere ogni volta il codice del metodo, se il nome del metodo è uguale, nella dichiarazione delle diverse classi derivate?
E ho trovato le funzioni virtuali: mettere nella dichiarazione della classe base il metodo come virtuale per poi ridefinire il metodo nella dichiarazione delle classi derivate.
Ma, ripensandoci bene, dov'è il vantaggio, se bisogna comunque riscrivere sempre il codice del metodo nella dichiarazione della classe derivata, da sostituire alla dichiarazione della funzione virtuale?

La differenza sta nel fatto che si può usare un puntatore del tipo della classe base per ogni classe derivata, e tramite il puntatore si può fare riferimento alla funzione virtuale, ma solo se questa è dichiarata nella classe base.
#include "iostream"
using namespace std;

class Poligono{
protected:
 int base,altezza;
public:
 virtual int area(){
  return 0;
 }

};

class Rettangolo:public Poligono{
public:
 Rettangolo(int b,int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza;
 }
};

class Triangolo:public Poligono{
public:
 Triangolo(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza/2;
 }
};


void main(){
 Rettangolo mioRettangolo(3,4);

 Triangolo mioTriangolo(3,4);

 Poligono * figura1 = &mioRettangolo;
 Poligono * figura2 = &mioTriangolo;

 cout << figura1->area() <<endl;
 cout << figura2->area() << endl;
 getchar();
}

Ma ora ho un dubbio: se la funzione, il metodo, non fosse dichiarato virtual non sarebbe la stessa cosa?

Proviamo subito a dichiarare il metodo non virtuale. Anche i metodi non virtuali possono essere ridefiniti nelle classi derivate, dunque che differenza c'è?

#include "iostream"
using namespace std;

class Poligono{
protected:
 int base,altezza;
public:
 int area(){
  return 0;
 }

};

class Rettangolo:public Poligono{
public:
 Rettangolo(int b,int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza;
 }
};

class Triangolo:public Poligono{
public:
 Triangolo(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza/2;
 }
};


void main(){
 Rettangolo mioRettangolo(3,4);

 Triangolo mioTriangolo(3,4);

 Poligono * figura1 = &mioRettangolo;
 Poligono * figura2 = &mioTriangolo;

 cout << figura1->area() <<endl;
 cout << figura2->area() << endl;
 getchar();
}
E vediamo l'output:
0
0


Ecco: invece, rendendo di nuovo la funzione virtuale:
#include "iostream"
using namespace std;

class Poligono{
protected:
 int base,altezza;
public:
 virtual int area(){
  return 0;
 }

};

class Rettangolo:public Poligono{
public:
 Rettangolo(int b,int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza;
 }
};

class Triangolo:public Poligono{
public:
 Triangolo(int b, int a){
  base=b;
  altezza=a;
 }
 int area(){
  return base*altezza/2;
 }
};


void main(){
 Rettangolo mioRettangolo(3,4);

 Triangolo mioTriangolo(3,4);

 Poligono * figura1 = &mioRettangolo;
 Poligono * figura2 = &mioTriangolo;

 cout << figura1->area() <<endl;
 cout << figura2->area() << endl;
 getchar();
}
12
6


Se la funzione non è virtuale, il puntatore fa riferimento alla funzione della classe base, che restituisce zero. Se la funzione della classe base è virtuale, invece, fa riferimento alla funzione della classe derivata.