E infatti nessuno si comporta in maniera polimorfa, il rettangolo si comporta come un rettangolo e il triangolo rettangolo come un triangolo rettangolo. Prova a togliere i virtual da quell' esempio e vedrai che non cambia niente. Probabilmente il libro vuole introdurti le cose poco alla volta.
---
Il motivo per cui si usano i puntatori è legato alle dimensioni delle classi.
Quando dichiari una variable con 'Gatto gatto' allochi nello stack una variabile che contiene un gatto, quando dichiari 'Gatto* gatto = new Gatto()' allochi nello stack un puntatore che punta ad una zona di memoria (lo Heap) che contiene il gatto.
Considera queste classi:
class Rettangolo
{
public:
int lunghezza;
int altezza;
};
class Parallelepipedo: public Rettangolo
{
public:
int profondita;
};
un Rettangolo ha i campi Lunghezza e Altezza, un parallelepipedo aggiunge la Profondità.
Quindi lo spazio necessario in memoria per contenere un rettangolo è di 8 bytes (4 byte per ogni intero) mentre per un parallelepipedo ne servono 12.
È evidente che dentro la zona di memoria che contiene un rettangolo non possiamo infilare un Parallelepipedo (che è più grande).
Diverso è il discorso con i puntatori, se prima un puntatore punta ad una zona di memoria contenente un rettangolo non c' è nessun problema a spostare il puntatore e farlo puntare ad una zona di memoria contenente un parallelepipedo.
In alcuni linguaggi (Java, C#) l' uso dei puntatori viene nascosto, per cui si scrive:
Gatto gatto = new Gatto();
gatto.Parla();
Ma il loro uso e solo nascosto in realtà sono puntatori.