PROGRAMMIAMO
C++ - Classi, attributi, metodi
Cosa sono le classi

Nella lezione precedente abbiamo introdotto il concetto di incapsulamento come metodo per proteggere i dati per mezzo di una restrizione sull'accesso. Bisogna però osservare che la soluzione adottata nel programma per il calcolo della differenza fra due orari (quella cioè di organizzare i dati in struct e le operazioni di accesso in funzioni) non garantisce un solido incapsulamento. Infatti nulla impedisce al main di accedere direttamente ai dati contenuti nelle struct senza usare le apposite funzioni. Per esempio il main potrebbe effettuare un'istruzione di acquisizione del tipo:

cin>>ora1.ora;

senza che questo possa essere in alcun modo impedito. E' chiaro che questa possibilità rende in qualche modo inutile l'incapsulamento, in quanto la protezione dei dati è esclusivamente affidata alla buona volontà del programmatore (cioè al fatto che egli abbia voglia o meno di usare le apposite funzioni per accedere ai dati).

Per risolvere il problema il C++ introduce un nuovo tipo di oggetto denominato class (in italiano classe). Detto in modo molto semplice, una classe è una struct "estesa", i cui membri possono essere sia dati che funzioni.

Vediamo subito un esempio di uso delle classi, sempre con riferimento al solito programma per il calcolo della differenza fra orari:

#include <cstdlib>
#include <iostream>

using namespace std;

class orario {
private:
  int ora;
  int min;
public:
  void acquisisci();
  void visualizza();
  int minuti();
};

 

int main(int argc, char *argv[])
{
int dif;
orario ora1, ora2;

cout<<"Fornisci l'orario iniziale (ore e minuti): ";
ora1.acquisisci();

cout<<"Fornisci l'orario finale (ore e minuti): ";
ora2.acquisisci();

dif = ora2.minuti() - ora1.minuti();

cout<<"La differenza fra le ";
ora1.visualizza();
cout<<" e le ";
ora2.visualizza();
cout<<" e' "<<dif<<" minuti\n";

system("PAUSE");
return EXIT_SUCCESS;
}

void orario::acquisisci()
{
cin>>ora;
cin>>min;
}

void orario::visualizza ()
{
cout<<ora<<":"<<min;
}

int orario::minuti ()
{
return ora*60+min;
}

Poiché il programma è abbastanza lungo e ci sono diverse cose da osservare, analizziamone le diverse parti singolarmente.

Definizione di classe, attributi e metodi

Cominciamo col mettere a confronto la vecchia struct orario con il nuovo oggetto denominato class orario:

Struct Class

 

struct orario {
int ora;
int min;
};

 

class orario {
private:
  int ora;
  int min;
public:
  void acquisisci();
  void visualizza();
  int minuti();
};

 

Osserviamo subito che:

Come avevamo accennato, la class può essere considerata come un'estensione del concetto di struct. A differenza della struct, che contiene solo dati (cioè variabili), la class contiene anche funzioni.

Nel contesto di una class, le variabili vengono dette attributi e le funzioni metodi (dette anche member functions) della classe. Nell'esempio precedente, la classe orario ha due attributi (ora e min) e tre metodi (acquisisci, visualizza e minuti).

 

Invio di messaggi a un oggetto

Consideriamo ora le prime righe del nostro programma:

int main(int argc, char *argv[])
{
int dif;
orario ora1, ora2;

cout<<"Fornisci l'orario iniziale (ore e minuti): ";
ora1.acquisisci();

cout<<"Fornisci l'orario finale (ore e minuti): ";
ora2.acquisisci();

dif = ora2.minuti() - ora1.minuti();

...

L'istruzione:

orario ora1, ora2;

serve per dichiarare due oggetti (object), di nome ora1 e ora2, del tipo class orario. In pratica ora1 e ora2 sono due oggetti con la struttura interna precedentemente definita nella class orario.

Osserviamo adesso la particolare sintassi usata per usare un metodo di una classe:

ora1.acquisisci();

Analizzandola nel dettaglio essa si compone di:

ora1
.
acquisisci()
object
operatore punto
metodo

E' interessante mettere a confronto l'uso del metodo acquisisce con la chiamata alla funzione acquisisci nel precedente programma realizzato con le struct:

Struct Class

 

ora1 = acquisisci();

 

ora1.acquisisci();

 

Usando le classi, la funzione acquisisci viene chiamata con la stessa sintassi usata per accedere ai membri di una struct (l'operatore punto): a tutti gli effetti acquisisci() appartiene alla classe, ne è un membro.

Nel linguaggio della programmazione ad oggetti, l'interazione con gli oggetti viene detto scambio (o invio) di messaggi. L'invio di un messaggio pu richiedere l'esecuzione di un metodo o la modifica del valore di un attributo. In ogni caso la sintassi generale dell'invio di un messaggio a un oggetto la seguente

oggetto.membro;  // esempio: ora1.acquisisci();

 

Private e public

La possibilità o meno da parte del main di accedere a un membro, sia esso una variabile (attributo) o una funzione (metodo), di una classe viene regolata dalla parola chiave private oppure public. Osserviamo:

class orario {
private:
  int ora;
  int min;
public:
  void acquisisci();
  void visualizza();
  int minuti();
};

In questo caso ora e min sono stati dichiarati private, cioè non è possibile usarli all'esterno della classe. In altre parole se il main avesse tentato per esempio di eseguire un'istruzione del tipo:

cin>>ora1.ora;

avremmo ottenuto il seguente messaggio di errore: "Error: `int orario::ora' is private ", che significa appunto che non è consentito usare un membro private di una class all'esterno della stessa class.

Invece i metodi acquisisci, visualizza e minuti sono stati dichiarati public e dunque possono essere usati all'esterno della class, come abbiamo precedentemente visto.

Normalmente gli attributi (cioè i dati) sono dichiarati private (in modo da proteggerli), mentre i metodi (cioè le funzioni) sono public, ma vi sono numerose eccezioni a questa regola generale. Se viene omessa la parola chiave private o public, i corrispondenti membri vengono dichiarati private per default.

 

Definizione delle funzioni (metodi) di una classe

Concentriamoci ora sulla parte del programma in cui sono definiti i metodi acquisisci, visualizza e minuti della class orario:

void orario::acquisisci()
{
cin>>ora;
cin>>min;
}

void orario::visualizza ()
{
cout<<ora<<":"<<min;
}

int orario::minuti ()
{
return ora*60+min;
}

La prima cosa che notiamo è che i metodi vengono definiti in fondo al programma e fuori dalla classe. Questo può produrre un po' di confusione. Andiamo per ordine. In realtà il C++ consente di definire un metodo dentro la dichiarazione di una classe in questo modo:

class orario {

private:
  int ora;
  int min;

public:
  void acquisisci()
    {
    cin>>ora;
    cin>>min;
    }

  void visualizza ()
    {
    cout<<ora<<":"<<min;
    }

  int minuti ()
    {
    return ora*60+min;
    }
};

Questa dichiarazione è perfettamente corretta, ma viene usata solo per funzioni brevi e composte da poche istruzioni (si parla in questi casi di funzioni inline). Nella maggior parte dei casi si preferisce seguire la strada che abbiamo adottato anche noi, cioè quella di mettere nella class solo i prototipi delle funzioni e di definire le funzioni stesse all'esterno. In realtà le funzioni inline sono trattate dal compilatore in modo un po' diverso dalle funzioni esterne, nel senso che non viene effettuata una vera e propria chiamata alla funzione, ma il codice corrispondente viene semplicemente espanso e copiato nel programma, come se si trattasse di una macro. Lasciamo però perdere questi dettagli.

Torniamo invece al modo più usuale di definire i metodi di una certa classe e concentriamoci sulla particolare sintassi usata:

int
orario
::
minuti()
tipo della funzione
classe di appartenenza
operatore due punti (scope resolution)
nome della funzione

L'operatore '::' (detto scope resolution) serve a indicare la classe a cui appartiene la funzione che si sta definendo. Infatti un programma potrebbe avere più classi e classi differenti potrebbero usare metodi con lo stesso nome.

Un'altra cosa molto importante da osservare è che i metodi possono accedere agli altri membri della classe, compresi quelli dichiarati private. Inoltre l'accesso ai membri avviene direttamente, senza la necessità di indicare la classe di appartenenza. In pratica sarebbe sbagliato scrivere:

int orario::minuti ()
{
return ora1.ora*60+ora1.min; //ERRORE!!! NON BISOGNA SPECIFICARE LA CLASSE ora1!
}

 

Assegnazione fra oggetti

In C++ possibile eseguire un'assegnazione fra due oggetti appartenenti alla stessa classe, come per esempio in:

int main(int argc, char *argv[])
{
int dif;
orario ora1, ora2;

...

ora1 = ora2;

L'assegnazione precedente copia il valore degli attributi di ora2 sui corrispondenti attributi di ora1 ed pertanto equivalente a:

ora1.min = ora2.min;
ora1.ora = ora2.ora;

 

 

link precedente - successiva

Sito realizzato in base al template offerto da

http://www.graphixmania.it