C/C++ · 10 min read · Oct 10, 2025
Apprendere C/C++ Passo dopo Passo - Pagina 16
16. Passo dopo Passo C/C++ — Programmazione C++ - Polimorfismo
Polimorfismo
| | 1. Sovraccarico di Funzione
- Polimorfismo
- Tipi di polimorfismo
- Funzioni membro normali accessibili con puntatori
- Funzione Virtuale
- Funzione Pura
- Assegnazione e Inizializzazione per Copia
- Il Costruttore di COPIA
- Puntatore ‘this’ |
1. Sovraccarico di Funzione
Se una funzione con il suo nome differisce per comportamento degli argomenti, si parla di polimorfismo delle funzioni o sovraccarico di funzione.
| | // Un programma di esempio per dimostrare l’uso del sovraccarico di funzione
#include
using namespace std;
void printline()
{
for(int i=0;i<=80; i++) cout << “-“;
}
void printline(int n)
{
for(int i =0 ;i<=n;i++) cout << “-“;
}
void printline(int n,char ch)
{
for(int i=0;i<=n; i++) cout << ch;
}
int main()
{
printline();
printline(5);
printline(10, ‘*’);
return 0;
}
|
Polimorfismo
Il polimorfismo è una delle caratteristiche cruciali della OOP. Significa semplicemente un nome, molteplici forme. Abbiamo già visto come il concetto di polimorfismo venga implementato utilizzando funzioni e operatori sovraccaricati. Le funzioni membro sovraccaricate vengono selezionate per l’invocazione abbinando gli argomenti, sia per tipo che per numero. Il compilatore conosce queste informazioni al momento della compilazione e quindi è in grado di selezionare la funzione appropriata per una particolare chiamata già al momento della compilazione. Questo è chiamato binding anticipato o binding statico o linking statico. Conosciuto anche come polimorfismo al tempo di compilazione, il binding anticipato significa semplicemente che un oggetto è legato alle sue chiamate di funzione al momento della compilazione.
Ora consideriamo una situazione in cui il nome della funzione e il prototipo sono gli stessi sia nella classe base che in quella derivata. Ad esempio, considera le seguenti definizioni di classe.
| | #include
using namespace std;
class A
{
int x;
public : void show();
};
class B : public A
{
int y;
public : void show();
};
int main()
{
B b;
b.show();
return 0;
} |
Come possiamo usare la funzione membro show( ) per stampare i valori degli oggetti delle classi A e B? Poiché il prototipo di show( ) è lo stesso in entrambi i posti, la funzione non è sovraccaricata e quindi il binding statico non si applica. Infatti, il compilatore non sa cosa fare e rimanda la decisione.
Sarebbe bello se la funzione membro appropriata potesse essere selezionata mentre il programma è in esecuzione. Questo è noto come polimorfismo al momento dell’esecuzione. Come potrebbe accadere? C++ supporta un meccanismo noto come funzione virtuale per raggiungere il polimorfismo al momento dell’esecuzione. Durante l’esecuzione, quando è noto quali oggetti di classe sono in considerazione, viene chiamata la versione appropriata della funzione.
Poiché la funzione è legata a una particolare classe molto più tardi dopo la compilazione, questo processo è definito come binding tardivo. È anche noto come binding dinamico o linking dinamico perché la selezione della funzione appropriata avviene dinamicamente al momento dell’esecuzione.
3. Tipi di Polimorfismo
Il polimorfismo è di due tipi, ovvero.
| 1 | Polimorfismo al tempo di compilazione
O Binding anticipato
O Binding statico
O Polimorfismo di linking statico. Un oggetto è legato alla sua chiamata di funzione al momento della compilazione. |
| 2 | Polimorfismo al momento dell’esecuzione
O binding tardivo
O binding dinamico
O polimorfismo di linking dinamico. La selezione e la funzione appropriata vengono eseguite dinamicamente al momento dell’esecuzione. |
| |
|
Il binding dinamico è una delle caratteristiche potenti di C++. Questo richiede l’uso di puntatori agli oggetti. Discuteremo in dettaglio come i puntatori agli oggetti e le funzioni virtuali vengono utilizzati per implementare il binding dinamico.
4. Funzioni Membro Normali Accessibili con Puntatori
Il programma sottostante consiste in una classe base
| | / Funzioni normali accessibili da puntatore /
/ Polimorfismo con classi (senza utilizzare il polimorfismo VIRTUALE /
#include
using namespace std;
class BASE
{
public :
void disp() { cout << “\nSei nella classe BASE “; }
};
class DERIVED1 : public BASE
{
public :
void disp() { cout << “\nSei nella classe DERIVED1”; }
};
class DERIVED2 : public BASE
{
public :
void disp() { cout << “\nSei nella classe DERIVED2”; }
};
int main()
{
DERIVED1 d1; // Oggetto della classe derivata 1
DERIVED2 d2; // Oggetto della classe derivata 2
BASE *b; // puntatore alla classe base
b=&d1; // Assegna l’indirizzo di d1 al puntatore b
b->disp(); // chiamata a disp()
b=&d2; // Assegna l’indirizzo di d2 al puntatore b
b->disp(); // chiamata a disp()
return 0;
} |
Il programma sopra dimostra:
| | • Una classe BASE
• Classi DERIVED1, DERIVED2 derivate da BASE
• Oggetti delle classi derivate (d1,d2)
• Puntatore alla classe BASE b |
| | *Output Sei nella classe BASE
Sei nella classe BASE |
5. Funzione Virtuale
Virtuale significa esistente in effetti ma non in realtà.
Una funzione membro può essere dichiarata come funzione virtuale precedendo la funzione membro con la parola chiave virtual.
| | / Polimorfismo con Classi (Polimorfismo Virtuale) /
#include
using namespace std;
class B
{
public :
void show(){ cout << “\nmetodo Show() della classe B “; }
virtual void disp() { cout << “\nmetodo disp() della classe B”; }
};
class D : public B
{
public :
void show(){cout << “\nmetodo Show() della classe D “; }
void disp(){ cout << “\nmetodo disp() della classe D”; }
};
int main()
{
D d1;
d1.show();
d1.disp(); // Membro della classe base
B b;
D d;
B *Bptr;
Bptr = &b;
Bptr->show();
Bptr->disp(); // Membro della classe base
Bptr=&d;
Bptr->show(); // membri della classe derivata
Bptr->disp(); // Membro della classe base
return 0;
} | | | Output metodo Show() della classe D
metodo disp() della classe D |
6. Funzione Pura
Una funzione definita in una classe base e non ha definizione relativa alla classe derivata è chiamata funzione pura. In parole semplici, una funzione pura è una funzione virtuale senza corpo.
| | #include
using namespace std;
class B
{
public :
void show(){ cout << “\nmetodo Show() della classe B “; }
virtual void disp() = 0; // funzione virtuale pura
};
class D : public B
{
public :
void show(){cout << “\nmetodo Show() della classe D “; }
void disp(){ cout << “\nmetodo disp() della classe D”; }
};
int main()
{
D d1;
d1.show(); // O/P : Metodo della classe D show()
d1.disp(); // O/P : Metodo della classe D disp()
D d;
B *Bptr;
Bptr=&d;
Bptr->show(); // O/P : Metodo della classe B show()
Bptr->disp(); // O/P : Metodo della classe D disp()
return 0;
} |
Bptr -> show() * è la funzione eseguibile predefinita dalla Base Bptr -> disp() è la funzione eseguibile predefinita dalla Base ma è dichiarata come funzione virtuale pura, quindi al momento dell’esecuzione verrà chiamata la disp()* della classe derivata.
| | / Programma per dimostrare il vantaggio delle funzioni virtuali pure /
#include
using namespace std;
enum boolean { false, true };
class NAME
{
protected : char name[20];
public :
void getname()
{ cout << “Inserisci nome :”; cin >> name; }
void showname()
{ cout << “\nIl nome è “<< name; }
boolean virtual isGradeA() = 0; // funzione virtuale pura
};
class student : public NAME
{
private : float avg;
public :
void getavg()
{
cout << “\nInserisci media studente :”;
cin >> avg;
}
boolean isGradeA()
{ return (avg>=80) ? true : false ; }
};
class employee : public NAME
{
private : int sal;
public :
void getsal()
{ cout << “\nInserisci stipendio “; cin >> sal; }
boolean isGradeA()
{ return (sal>=10000) ? true : false ; }
};
int main()
{
NAME names[20]; // numero di puntatori al nome
student s; // puntatore allo studente
employee *e; // puntatore all’impiegato
int n = 0; // numero di nomi nella lista
char choice;
do{
cout << “Inserisci Studente o Impiegato (s/e) “;
cin >> choice;
if(choice==’s’)
{
s = new student; // crea un nuovo studente
s->getname();
s->getavg();
names[n++]=s;
}
else
{
e = new employee; // crea un nuovo impiegato
e->getname();
e->getsal();
names[n++]=e;
}
cout << “Inserisci un altro (y/n) ?”; // fai un altro
cin >> choice;
} while(choice==’y’);
for(int j=0; j
names[j]->showname( );
if(names[j]->isGradeA( )==true)
cout << “È una persona di Grado 1”;
}
return 0;
} |
7. Assegnazione e Inizializzazione per Copia
Il compilatore C++ è sempre occupato a tuo favore, facendo cose che non ti preoccupi di fare. Se prendi il controllo, si atterrà al tuo giudizio; altrimenti farà le cose a modo suo. Due esempi importanti di questo processo sono l’operatore di assegnazione e il costruttore di copia.
Hai usato l’operatore di assegnazione molte volte, probabilmente senza pensarci troppo. Supponiamo che a1 e a2 siano oggetti. A meno che tu non dica al compilatore diversamente, l’istruzione.
a2 = a1; // imposta a2 al valore di a1
Causerà al compilatore di copiare i dati da a1, membro per membro, in a2. Questa è l’azione predefinita dell’operatore di assegnazione, =.
Sei anche familiare con l’inizializzazione delle variabili, inizializzando un oggetto con un altro oggetto, come in
alpha a2(a1); // inizializza a2 al valore di a1
Causa un’azione simile. Il compilatore crea un nuovo oggetto, a2, e copia i dati da a1, membro per membro, in a2. Questa è l’azione predefinita del costruttore di copia.
Entrambe queste attività predefinite sono fornite, gratuitamente, dal compilatore. Se la copia membro per membro è ciò che desideri, non è necessario intraprendere ulteriori azioni. Tuttavia, se desideri che l’assegnazione di inizializzazione faccia qualcosa di più complesso, puoi sovrascrivere le funzioni predefinite. Discuteremo le tecniche per sovraccaricare l’operatore di assegnazione e il costruttore di copia separatamente.
Sovraccarico dell’Operatore di Assegnazione
| | // Sovraccarico dell’Operatore di Assegnazione ( = )
#include
using namespace std;
class alpha
{
private:
int data;
public:
alpha() { } // costruttore senza argomenti
alpha( int d )
{ data = d; } // costruttore con un argomento
void display()
{ cout << data; } // Visualizza i dati
alpha operator =(alpha & a) // operatore = sovraccaricato
{
data = a.data; // non fatto automaticamente
cout << “\n Operatore di assegnazione invocato “;
return alpha(data);
}
};
int main()
{
alpha a1(37);
alpha a2;
a2 = a1; // Invoca sovraccaricato =
cout << “\n a2 = “; a2.display(); // visualizza a2
alpha a3 = a2; // NON invoca =
cout << “\n a3 = “; a3.display(); // visualizza a3
return 0;
}
| | | Output: a2 = 37
a3 = 37 |
8. Il COSTRUTTORE di COPIA
Come abbiamo discusso, possiamo definire e allo stesso tempo inizializzare un oggetto al valore di un altro oggetto con due tipi di istruzioni:
alpha a3(a2); // Inizializzazione per copia
alpha a3 = a2; // inizializzazione per copia, sintassi alternativa
Entrambi gli stili di definizione invocano un costruttore di copia: cioè, un costruttore che copia il suo argomento in un nuovo oggetto. Il costruttore di copia predefinito, fornito automaticamente dal compilatore per ogni oggetto, esegue una copia membro per membro. Questo è simile a ciò che fa l’operatore di assegnazione; la differenza è che il costruttore di copia crea anche un nuovo oggetto.
Il seguente esempio dimostra il costruttore di copia.
| | #include
using namespace std;
class alpha
{
private :
int data;
public:
alpha( ) { } // costruttore senza argomenti
alpha(int d) { data = d; } // costruttore con un argomento
alpha(alpha& a) // costruttore di copia
{
data = a.data;
cout << “\nCostruttore di copia invocato”;
}
void display( )
{ cout << data; }
void operator = (alpha& a) // operatore = sovraccaricato
{
data = a.data;
cout << “\nOperatore di assegnazione invocato”;
}
};
int main()
{
alpha a1( 37 );
alpha a2;
a2 = a1; // invoca sovraccaricato =
cout << “ a2 = “; a2.display(); // visualizza a2
alpha a3( a1 ); // invoca costruttore di copia
// alpha a3 = a1; // definizione equivalente di a3
cout << “ a3 = “; a3.display(); // visualizza a3
return 0;
} |
Il programma sopra sovraccarica sia l’operatore di assegnazione che il costruttore di copia.
L’operatore di assegnazione sovraccaricato è simile a quello nell’esempio precedente.
9. Puntatore ‘this’
C++ utilizza una parola chiave unica chiamata this per rappresentare un oggetto che invoca una funzione membro. This è un puntatore che punta all’oggetto per il quale è stata chiamata la funzione this.
Questo puntatore semplicemente svolge il compito di restituire l’oggetto stesso.
Il seguente programma definisce gli oggetti i, j e i è assegnato con il valore di 5 e l’intero oggetto di i è assegnato dalla sua funzione membro a j
| | #include
using namespace std;
class A
{
int a;
public:
A() { }
A(int x)
{ a = x; }
void display()
{
cout << a;
}
A get()
{
return *this; // Restituisce se stesso
}
};
int main()
{
A i(5);
A j;
j = i.get();
j.display();
return 0;
} |
Rif: Programmazione orientata agli oggetti in Turbo C++: Robert Lafore
Ricevi i nuovi post nella tua casella di posta.
Nessuno spam. Disiscriviti in qualsiasi momento.