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

  1. Polimorfismo
  2. Tipi di polimorfismo
  3. Funzioni membro normali accessibili con puntatori
  4. Funzione Virtuale
  5. Funzione Pura
  6. Assegnazione e Inizializzazione per Copia
  7. Il Costruttore di COPIA
  8. 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

Share: X/Twitter LinkedIn

Ricevi i nuovi post nella tua casella di posta.

Nessuno spam. Disiscriviti in qualsiasi momento.