Il concetto di classe fornisce al programmatore uno strumento per creare nuovi tipi, utilizzabili nello stesso modo dei tipi predefiniti. In teoria un tipo definito dall'utente, non dovrebbe differire dai tipi predefiniti per il modo di usarlo, ma solo per il diverso modo con cui viene creato.
Un tipo costituisce la rappresentazione concreta di un concetto. Ad esempio, il tipo predefinito float, insieme alle operazioni +, -, *, /, rappresenta una versione limitata, ma concreta, del concetto matematico di numero reale.
La realizzazione di un nuovo tipo, è necessaria per fornire una definizione concreta e specifica di un concetto che non presenta controparte semplice e diretta fra i tipi predefiniti. E' possibile, ad esempio, fornire un tipo trunk_module in un programma che riguarda i telefoni, oppure un tipo list_of_paragraphs in un programma per l'eleborazione di testi. Un programma dotato di tipi strettamente corrispondenti ai concetti dell'applicazione è generalmente più semplice da comprendere e modificare.
L'idea fondamentale per la definizione di un nuovo tipo, consiste nel separare i dettagli secondari dell'implementazione (ad esempio, la struttura dei dati impiegati per memorizzare un oggetto di quel tipo) dalle proprietà fondamentali per un uso corretto (come, ad esempio, l'elenco completo delle funzioni che possono avere accesso ai dati). Tale separazione può essere espressa incanalando ogni utilizzo della struttura dati e le routine interne di gestione attraverso un'interfaccia specifica.
Concetto di classe:
Supponiamo di voler realizzare un programma per la gestione degli studenti
di una universita' e domandiamoci quale e' la struttura dati piu' idonea per
la memorizzazione delle relative informazioni. Sulla base di quanto finora
e' stato discusso e' immediato pensare ad un array di strutture e la scelta
potrebbe risultare adeguata per un linguaggio di programmazione non ad oggetti.
Poiche' questo corso ha come obiettivo la programmazione ad oggetti, ci dobbiamo
chiedere quali sono i punti deboli del tipo struct e qual'è l'alternativa
offerta dal linguaggio C++. Sappiamo che per default i membri di una struttura
sono pubblici e questo certamente non favorisce la protezione dei dati.
Qualunque funzione puo' liberamente accedervi e modificarne il contenuto.
Ed inoltre se volessimo raggruppare gli studenti per facolta' di appartenenza,
dovremmo creare una nuova struttura dati senza la possibilita' del riuso del
codice esistente. Il linguaggio C++ supera questi limiti attraverso l'introduzione
del costrutto classe. La classe e' un'astrazione che descrive le proprieta'
di tutti gli oggetti caratterizzati da:
e che consente la creazione di un numero qualunque di istanze (oggetti). La parola chiave class consente di dichiarare una classe:
class contatore {
int val; // private per default
public:
void reset(); // Funzioni di interfaccia
void inc(); // oppure
void dec();
int visual(); // Protocollo
};
Abbiamo cosi' creato un classe di nome contatore costituita da un intero
di nome val e da un insieme di operazioni (protocollo o interfaccia) che rappresentano
che cosa puo' fare un oggetto di questa classe. Nel nostro esempio un oggetto
di tipo contatore puo' azzerarsi, incrementarsi, decrementarsi, visualizzare
il valore.
Per indicare come opera un oggetto della classe, si deve scrivere il codice
delle funzioni:
void contatore::reset() { val=0;}
void contatore::inc() { val++;}
void contatore::dec() { val--;}
void contatore::visual() { return val;}
contatore c1,c2;
c1.reset();
Questa istruzione ha il significato di invio del messaggio all'oggetto c1 di eseguire l'operazione reset() che nel nostro caso pone a zero il campo val dell'oggetto c1. Un esempio completo e' a questo punto utile:
// nel file contator.h
class contatore {
int val;
public:
void reset();
void inc();
void dec();
int visual();
};// nel file contator.cpp
#include"contator.h"
void contatore::reset() {
val=0;
}
void contatore::inc() {
val++;
}
void contatore::dec() {
val--;
}
int contatore::visual() {
return val;
}// nel file prova.cpp
#include<iostream.h>
#include"contator.h"
main()
{
contatore c1,c2;
c1.reset();
c2.inc();
cout<<c1.visual()<<endl;//val=0
cout<<c2.visual()<<endl;//val=1
}
Notare che il programma è stato suddiviso su tre file. In generale è consigliabile memorizzare in un file la dichiarazione della classe, in un altro file la definizione delle funzioni membro e in un altro file ancora il programma principale.
Costruttori e distruttori:
Gli oggetti si comportano come normali variabili rispetto alla visibilità
ed al ciclo di vita. Questo significa che in presenza della dichiarazione:
contatore c1;
class contatore {
int val;
public:
.......
contatore(); // costruttore senza parametri
contatore(int); // costruttore con un parametro
~contatore(); // distruttore
};
Essi hanno lo stesso nome della classe di appartenenza, non sono funzioni membro e perciò non sono operazioni eseguibili dagli oggetti, ma vengono invocati automaticamente negli istanti di creazione e distruzione dell'oggetto stesso. Quando è richiesta l'inizializzazione degli oggetti, si prevedono costruttori con uno o più parametri. In una classe possono essere presenti più costruttori ed il compilatore utilizza quello adeguato in modo automatico, mentre il distruttore è unico in ogni classe. Facciamo un esempio che metta in luce tutti gli aspetti evidenziati (il codice già scritto non viene ripetuto):
// aggiungere nel file contator.cpp
contatore::contatore() {
val=0;
}contatore::contatore(int a) {
val=a;
}
contatore::~contatore() {
cout<<"distruttore"<<endl;
}
// file prova.cpp
#include<iostream.h>
#include"contator.h"
main()
{
contatore c1; // invoca il costruttore:contatore().c1=0
contatore c2(12); // invoca il costruttore:contatore(int)
// si ottiene c2=12
c1.inc(); // si ottiene c1=1
c2.dec(); // si ottiene c2=11
cout<<"c1="<<c1.visual()<<endl;
cout<<"c2="<<c2.visual<<endl;
} // invoca il distruttore: prima viene distrutto c2 e poi c1
Powered by Vincenzo Capuano