C++
Tutti gli esempi riportati sono stati tratti dal testo [Conte 96]
Alcuni esempi richiedono conoscenze approfondite del linguaggio, si consiglia di consultare un buon manuale.
Come poter misurare i tempi di esecuzione di certi algoritmi, utilizzando degli oggetti simili ai cronometri della realtà?
In C++ possiamo definirci una classe
opportuna che ci permetta di avere (istanziare) oggetti
per la misurazione del tempo.
La classe Cronometro rappresenta il nuovo tipo e le istanze della classe
rappresenteranno i nuovi oggetti.
Al generico cronometro possiamo dare
dei comandi (invocare
metodi):
|
|
Ovviamente dobbiamo poter leggere il tempo misurato o che scorre, servirà un metodo per interrogare od osservare il cronometro:
Invocare un comando significa attivare una funzione che agisce sull'oggetto
o meglio sullo stato interno
dell'oggetto della classe; interrogare od osservare lo stato interno equivale
ad invocare una funzione che restituisce il risultato voluto, elaborando
i dati interni.
All'interno
il cronometro ha una struttura
nascosta che contiene una serie di meccanismi sia per farlo funzionare,
sia per memorizzare le informazioni (stato
dell'oggetto).
Definizione
della Classe Cronometro ![]() |
class Cronometro
{ public: //------------------------ parte visibile all'utente della Classe Cronometro( ); // Costruttore void Start( ); // Comando di partenza void Stop( ); // Comando di arresto void Reset( ); // Comando di azzeramento float Time( ) const; // funzione di osservazione: valore segnato dal cronometro private: //------------------------------- parte nascosta all'utente della Classe float Ti; // Tempo iniziale: misura assoluta float TempoAccumulato; // tempo conteggiato dal reset bool StatoConteggio; // fermo: false, in funzione: true }; //* Cronometro */ |
In questo esempio i metodi sono dichiarati in modo da esser visibili dall'utilizzatore (parte pubblica della classe), mentre invece i dati sono nascosti (parte privata).
I dati Ti, TempoAccumulato e StatoConteggio all'interno della classe serviranno per definire lo stato dell'oggetto di classe Cronometro, cioè per memorizzare delle informazioni relative al cronometro.
Vi è pure un metodo, con lo stesso nome della classe, che assomiglia ad una funzione ma che non restituisce alcunché neppure il tipo void, vedremo che servirà per inizializzare gli oggetti: per questo lo chiameremo costruttore (constructor).
Una porzione di codice che prova il Cronometro è la seguente:
Collaudo ![]() |
Cronometro CASIO; //
creazione dell'oggetto di tipo Cronometro (viene invocato il
costruttore)
CASIO.Reset( ); // non servirebbe, ma lo resettiamo lo stesso CASIO.Start( ); // per farlo partire int dato; // definizione di uno spazio di nome dato per memorizzare un intero cin >> dato; // lettura da tastiera del numero con inserimento in dato CASIO.Stop( ); // ferma il cronometro cout << CASIO.Time( );// visualizza il tempo impiegato dall'utente per inserire un dato intero CASIO.Start( );// riprende la misura del tempo cin >> dato; CASIO.Stop(); cout << CASIO.Time( );// visualizza il tempo totale impiegato dall'utente per inserire due interi |
Vediamo come definire la classe Cronometro definendo i vari metodi:
Costruttore |
|
Questo è il costruttore che viene invocato ogni volta che un oggetto viene creato.
Nel nostro caso le azioni fatte dal costruttore sono di inizializzazione
di alcune variabili nascoste nella parte privata della Classe.
metodo
Start comando |
|
Nel nostro cronometro facciamo l'ipotesi che il comando di start inibisca se stesso, nel senso che non si possono dare due comandi di start in successione.
clock è una funzione della libreria time.h che
restituisce il tempo trascorso dall'inizio dell'invocazione del programma
misurato in unità di impulsi-processore; per avere il tempo in secondi
bisogna dividere il valore restituito da clock per la macro CLK_TCK, il
risultato è un numero reale.
metodo
Reset comando |
|
metodo
Stop comando |
|
Anche questo comando inibisce se stesso. Calcola l'istante assoluto Tf e memorizza il tempo accumulato fino a quell'istante nella variabile TempoAccumulato; in questo modo si può far ripartire il cronometro dal punto in cui era rimasto.
metodo
Time osservazione |
|
La funzione di interrogazione legge il tempo accumulato fino a quell'istante dal cronometro, se il cronometro è fermo legge direttamente dalla variabile TempoAccumulato.
Il concetto di Insieme è un concetto primitivo: un Insieme è una collezione di oggetti distinguibili chiamati membri o elementi dell'insieme. Gli insiemi possono avere una dimensione, o cardinalità, finita o infinita.
In questa slide proponiamo la realizzazione di una classe per definire un nuovo tipo: SetCaratteri. L'insieme è un Insieme finito e contenente solo i caratteri ASCII.
Vediamo una porzione di programma per il collaudo della classe SetCaratteri
:
Collaudo |
//Prova delle operazioni sugli insiemi
cout <<"Differenza: SetA - SetB =" << (SetA - SetB) << endl;
cout <<"aggiunta nel SetD degli elementi JKLA =" <<
SetD << endl;
cout <<" Verifica della prima legge di DeMorgan con i complementi: \n" "NOT (SetA * SetB) == NOT SetA + NOT SetB "; bool Verificata = !(SetA * SetB) == !SetA + !SetB; cout << (Verificata ? "Verifica riuscita" : "ERRORE nella verifica") << endl; |
Stiamo usando gli insiemi come se fossero tipi predefiniti del linguaggio. Si sarebbero potuti addirittura definire i termini "UNIONE" ed "INTERSEZIONE" al posto degli operatori " + " e " - ".
SetA [ ABXY ]
SetB [ ABCXYZ ] SetC [ ADHWX ] SetD [ ] Intersezione: SetA * SetB = [ ABXY ] aggiunta nel SetD degli elementi JKLA = [ AJKL ] Verificata la presenza dell'elemento 'A' nel SetD Verifica della prima legge di DeMorgan con i complementi:
Verifica riuscita |
|
Uscita a video |
Questo ci permette un alto livello di astrazione.
![]() |
class SetCaratteri
SetCaratteri( ); // costruttore per
il Set vuoto friend ostream & operator << (ostream &, const SetCaratteri & ); bool Vett[128]; // 128 è la cardinalità
massima dell'insieme }; //*class SetCaratteri*/ |
La struttura interna che contiene le informazioni è
costituita da un semplice vettore a dimensione fissa.
L'idea di base, per la realizzazione della classe SetCaratteri, è
quella di utilizzare un vettore di booleani il quale con la presenza del
valore true/false nella posizione i-esima, indichi la presenza/assenza
del carattere i-esimo (di codice i).
Questa soluzione ci permette di semplificare le definizioni delle operazioni
tipiche degli Insiemi; ma non sarebbe adatta per Insiemi che possono avere
una elevata cardinalità.
Passiamo alla definizione dei vari metodi:
void SetCaratteri:: CreaInsiemeVuoto( ) // funzione
di comodo { for ( int i=0; i<128; i++ ) Vett[i]= false; } |
//-------------------------------------------------------------
Costruttore di default SetCaratteri::SetCaratteri( ) // inizializzazione insieme vuoto { CreaInsiemeVuoto( ); } |
//---------------------------------------------------------------
Costruttore SetCaratteri::SetCaratteri(char * S) // inizializzazione con un insieme di caratteri { CreaInsiemeVuoto( ); int L = strlen(S); for ( i=0; i<L; i++ ) Vett[ S[i] & 127 ]= true; } |
//---------------------------------------------------
Inserimento di un elemento void SetCaratteri:: operator += ( char elem ) { Vett[elem & 127] = true; } |
//--------------------------------------------------
Inserimento di una sequenza di caratteri void SetCaratteri:: operator += ( char * S ) { if (S) while (*S) Vett[*S++ & 127] = true; } |
Si suppone che i caratteri immessi abbiano codice compreso tra lo zero
e il 127. Un approccio difensivo imporrebbe il controllo di tutti i caratteri
inseriti, eliminando i caratteri non ASCII. Noi abbiamo risolto il problema
"filtrando" i dati d'ingresso:
Vett[ elem & 127 ] = true; Vett[
(*S++) & 127 ] = true;
La definizione degli operatori è del tutto simile alla definizione
delle funzioni, se pensiamo di sostituire "operator + "
con il termine "somma", più nulla distingue la
definizione di una funzione dalla definizione di un operatore.
UNIONE | SetCaratteri SetCaratteri:: operator
+ (const SetCaratteri & SetB ) const { SetCaratteri Ris; // contenitore del risultato for ( int i=0; i< 128; i++ ) { if ( Vett[i] == SetB.Vett[i] ) Ris.Vett[i] = Vett[i]; else Ris.Vett[i] = true; // almeno uno dei due insiemi possiede l'elemento } return Ris; }//* Unione */ |
INTERSEZIONE | SetCaratteri SetCaratteri:: operator * (const
SetCaratteri & SetB ) const { SetCaratteri Ris; // contenitore del risultato for ( int i=0; i< 128; i++ ) { if ( Vett[i] == SetB.Vett[i] ) Ris.Vett[i] = Vett[i]; else Ris.Vett[i] = false; // solo uno dei due insiemi possiede l'elemento } return Ris; }//* Intersezione */ |
DIFFERENZA | SetCaratteri SetCaratteri:: operator - (const
SetCaratteri & SetB ) const { SetCaratteri Ris; // contenitore del risultato for ( int i=0; i< 128; i++ ) { if ( Vett[i] != SetB.Vett[i] ) Ris.Vett[i] = Vett[i]; else Ris.Vett[i] = false; } return Ris; }//* Differenza */ |
Infine, sfruttando la relazione d'ordine tra il valore false e true,
possiamo risolvere i problemi di inclusione dei sotto insiemi.
//-------------- Appartenenza di
un elemento all'insieme (operatore ridefinito con IN )
bool operator <=( char elem , const SetCaratteri & Set ) { return Set.Vett[elem]; } |
ostream & operator << (ostream & Flusso, const
SetCaratteri & Set ) |
La definizione dell'operazione di complemento viene lasciata al lettore.
Nel nostro caso l'insieme universo possiede 128 caratteri e con questa
implementazione risulta semplice ricavare l'insieme complemento.
Un esempio di vettore di oggetti generici
In C++ un tipo "parametrizzato" viene chiamato una classe modello (class template).
Una classe modello permette di definire un "prototipo" per la definizione di classi; per questo viene anche detta classe generica o generatrice di classi. Con il meccanismo delle classi modello abbiamo la possibilità di generare istanze di una classe (e da questa oggetti) legate ad un particolare tipo di dato, scelto di volta in volta.
Un template definisce una famiglia di tipi
I contenitori di oggetti sono i tipi più adatti
a dimostrare l'utilità delle classi modello, infatti un modello
contenitore, come può essere una classe array, potrebbe essere utile
per generare contenitori indicizzati per ogni tipo di dato
![]() |
};//* classe Vettore*/ |
Implementazione della classe Vettore
|
|
|
}//*assegnamento*/ |
|
Un programma principale di collaudo che crea un vettore di oggetti di
un tipo definito dall'utente. In questo caso si tratta del tipo Tartaruga
(come le tartaruge del linguaggio LOGO) che viene istanziato su sette elementi
del Vettore:
|
} |
|
Uscita a video |
Un altro esempio di collaudo crea una matrice di interi, o meglio, un
vettore i cui elementi sono vettori di interi:
Collaudo |
return 0; |
===== TAVOLA PITAGORICA ======== 1 2 3 4 5 6 7 8 9 10 2 4 6 8 10 12 14 16 18 20 3 6 9 12 15 18 21 24 27 30 4 8 12 16 20 24 28 32 36 40 5 10 15 20 25 30 35 40 45 50 6 12 18 24 30 36 42 48 54 60 7 14 21 28 35 42 49 56 63 70 |
Uscita a video |