Informatica 4
Informatica 4
Capitolo 1 Liste
La principale applicazione dei puntatori associati alle strutture di dati consiste nella creazione di liste.
Possiamo assimilare una lista ad una struttura (vettore di record) di lunghezza indefinita.
Il codice minimo per realizzare una lista a puntatori è il seguente dove ogni record è costituito da due
campi, uno intero ed un altro è un puntatore all’elemento successivo:
#include<iostream>
using namespace std;
struct T{
int x;
struct T *y;
};
main(){
T *p,*q=NULL,*j=NULL;
bool primo=true;
//caricamento
do{
p=new T;
cout<<"ins:";cin>>p->x;
p->y=NULL;
if(primo){
j=p;
q=p;
primo=false;
}else{
q->y=p;
q=p;
}//fine if
}while(p->x);
//scrittura a video
1
p=j;//p punta al primo elemento con j
while(p->x){
cout<x<<" ";
p=p->y;
} //fine while
} // fine main
2
sempre al secondo giro (e anche in
quelli successivi) è necessario un
ulteriore movimento:
q=p;
in questo modo j rimane da solo a
puntare al primo elemento.
nei giri successivi questo comportamento si ripete, va avanti finché l'utente non inserisce come valore 0.
La scansione della lista viene effettuata portando p a puntare al primo elemento con l'istruzione:
p=j;
la stampa è eseguita dal ciclo while
while(p->x){
cout<x<<" ";
p=p->y;
}
incrementando ogni volta la posizione di p nella lista con:
p=p->y;
3
Void inserimentointesta(Void)
Pnuovo =new T;
Cin>> pnuovo->x;
Pnuovo->y=NULL;
If (TDL != NULL)
Pnuovo->Y=TDL;
TDL=Pnuovo;
Void inserimentointesta(Void)
{
T *TDL; /*puntatore iniziale della lista */
T *P;/* puntatore ausiliario */
If (TDL!=Null) /* se la lista non è vuota */
{
P=TDL;
TDL=P->Y
Delete p;
}
4
1.3 ESEMPIO DI OPERAZIONE SU LISTE :RICERCA DI UN ELEMENTO
IN UNA LISTA
Struct Nodo {
Int info;
nodo * psucc}
nodo * p;
nodo * piniz;
void lista ()
{
IF (piniz = =NULL)
Cout<< “La lista è vuota”;
else
{
Conta=0;
p=piniz;
While (p!= NULL)
{
p=p>psucc;
conta=conta +1
}
}
Cout<< “La lista contiene il seguente numero di elementi
“<<conta
5
1.4 ESEMPIO DI OPERAZIONE SU LISTE : CONTA LE OCCORRENZE DI
UN ELEMENTO IN UNA LISTA
STRUCT Nodo {
Int info;
Nodo *psucc }
Nodo *TDL, *p, *pnuovo=NULL;
While (p!=NULL)
{
If (p->info==numero)
Conta=conta+1;
p=p->psucc
}
6
If (conta==0)
Cout<<”il numero non esiste nella lista”
Else cout <<”IL numero appare nella lista”<<conta<<”Volte”<<
Una pila è una struttura di dati simile alla lista dove le uniche operazioni consentite sono la inserzione di un
elemento in testa alla lista ( PUSH) e la cancellazione di un elemento sempre in testa alla lista (POP). La
gestione è di tipo LIFO (Last in first out) cioè l’ultimo entrato è anche il primo ad uscire.
Dove (testa) è il puntatore iniziale della lista, quello che punta al primo elemento.
La coda invece è una struttura dati di tipo FIFO ( First in first out) cioè gli inserimenti avvengono alla fine e le
eliminazioni in cima alla coda, è simile alla coda degli sportelli degli Uffici Pubblici. La gestione della coda
avrà bisogno di due puntatori, il primo punterà alla testa della coda ed il secondo all’ultimo elemento.
void Push()
{
7
int DatoInp; // dato da inserire
nuovo->X = DatoInp;
nuovo->Y = NULL;
fine=nuovo ;
8
CAPITOLO 3 INTRODUZIONE ALLA PROGRAMMAZIONE ORIENTA TA
AGLI OGGETTI
Vent’anni di programmazione strutturata hanno evidenziato che i benefici apportati da questa tecnica
di programmazione non sono sufficienti a garantire un’abbondante riutilizzo del software
implementato. A partire dagli anni 80 si è quindi sviluppato un nuovo stile di scrittura del codice che
ne consente un reimpiego sempre maggiore.
L’idea è di per sé molto semplice rivoluziona il modo di implementare il codice inserendo un nuovo
approccio, rappresentato dai dati del problema. Questo modo di programmare si chiama
programmazione orientata agli oggetti.
La programmazione orientata agli oggetti non presuppone l’eliminazione delle tecniche precedenti ,
ma piuttosto le completa aggiungendo loro una nuova dimensione.
La programmazione orientata agli oggetti in breve OOP prende il nome dall’elemento su cui si basa:
l’oggetto .
Il programma realizzato con orientamento ad oggetti, si sviluppa attraverso le interazioni tra gli
oggetti: durante l’esecuzione del programma, gli oggetti possono cambiare il loro stato e possono
richiedere l’esecuzione di operazioni associate ad altri oggetti.
Facilità di lettura e di comprensione del codice anche per persone diverse dall’autore
Manutenzione del programma nel tempo per correzioni o miglioramenti
Robustezza del programma in situazioni critiche o in operazioni che coinvolgono grandi
quantità di dati
Riusabilità di parti di codice o di moduli funzionali all’interno di altri programmi
9
La struttura di un oggetto è completamente descritta quando vengono elencate le caratteristiche ed i
comportamenti dell’oggetto.
3.1 LA CLASSE
La classe è la descrizione astratta degli oggetti attraverso gli attributi ed i metodi. Per utilizzare un
oggetto occorre crearlo come esemplare della classe, cioè come un ‘istanza della classe.
Dovendo per esempio realizzare un programma che gestisca la clientela di un’azienda, ossia memorizzi
i dati anagrafici, consentendo l’inserimento di nuovi clienti, eventuali modifiche o cancellazioni.
Ciascun cliente prevede delle informazioni o attributi come il nome, l’indirizzo e il numero di telefono
e delle funzionalità quali l’inserimento, la modifica e la cancellazione.
Una classe viene rappresentata con uno schema grafico detto diagramma di classe che ne evidenzia il
nome, gli attributi ed i metodi.
10
Per esempio la classe cliente è così strutturata:
Gli elementi che formano la classe, attributi e metodi si chiamano membri della classe. Il
raggruppamento dei membri conferisce alla classe il significato di un’unità di programmazione,
riutilizzabile in altri programmi: il controllo dell’esecuzione diventa più facile e la modifica degli
oggetti nel tempo diventa più semplice. Questi aspetti descrivono il concetto di incapsulamento, che è
uno dei concetti alla base della programmazione ad oggetti.
Il termine incapsulamento indica la qualità degli oggetti di poter incorporare al loro interno sia gli
attributi sia i metodi, cioè le caratteristiche e i comportamenti dell’oggetto.
Nel linguaggio C++ le classi inglobano sotto un unico nome i dati membro(attributi) e le funzioni
membro (metodi). Questa modalità di aggregazione rappresenta in pratica il concetto di
incapsulamento della programmazione ad oggetti.
11
3.2 C++ PROGRAMMAZIONE ORIENTATA AGLI OGGETTI
Nella programmazione strutturata, viene scritto del codice che serve per manipolare dei dati, in questo
contesto, codice e dati vengono trattati come due elementi separati. La programmazione orientata agli
oggetti, tratta invece, dati ed oggetti come una singola entità chiamata classe. Tutti i programmi OOP
iniziano con una classe. Una classe è una struttura di dati che contiene tutto il necessario per memorizzare e
manipolare i dati. Supponiamo di sviluppare un programma per lo studio sui consumi di veicoli: .
#include
using namespace std;
Uno scritto di questo tipo sarebbe sufficiente per descrivere un veicolo. Ogni classe è dotata di attributi
(membri dati) che sono privati per impostazione predefinita. Noi abbiamo fatto un'eccezione: li abbiamo
dichiarati esplicitamente pubblici, altrimenti non avremmo potuto accedervi.
12
In questo caso i due attributi, a ed l pur facendo parte
dell'oggetto v1, sono accessibili dal mondo esterno.
Raggiungibili con le invocazioni:
v1.a
v1.l
La filosofia di usare variabili locali e di minimizzare le variabili globali all'interno del codice è sempre stata
raccomandata fin da quando la programmazione strutturata, organizzata in funzioni e procedure, ha
soppiantato quella sequenziale, caratterizzata da salti e rinvii fra i vari punti del programma (istruzione
goto).
Nella programmazione ad oggetti (OOP) questa tendenza è stata inasprita, proprio per evitare l'utilizzo di
variabili globali, dato che esse, essendo visibili da tutti i punti del programma, possono influenzare, talvolta
in modo incontrollato, l'esecuzione dello stesso, nonché, rendono difficile la gestione del codice, la sua
manutenzione e il suo sviluppo .
3.2.1 INCAPSULAMENTO
Una classe è una struttura di dati che contiene tutto il necessario per memorizzare e manipolare dati.
Ogni variabile definita all'interno di una classe è denominata attributo, si usa questo termine per distinguerla
da una normale variabile.
All'interno della classe vengono implementate anche le funzioni che manipolano gli attributi, esse sono
denominate metodi.
Dovrebbe essere possibile manipolare gli attributi di una classe solo attraverso i metodi della classe stessa,
non come abbiamo fatto noi, sopra, dove il main può accedere liberamente ai metodi e farne quello che
vuole.
14
All'interno del programma
un'eventuale chiamata del tipo:
Sono, invece, stati implementati 4 metodi pubblici: seta e setl per impostare i valori degli attributi
privati a ed l e geta e getl che servono per restituire al programma chiamante i valori di a ed l.
La riga di codice
V v1;
serve per istanziare l'oggetto v1 di classe V.
Un'istanza è una nuova copia della struttura definita nella classe (schema degli attributi + metodi
incapsulati: i metodi sono condivisi tra oggetti diversi della stessa classe).
In questa ottica la classe viene interpretata come il 'calco' che serve a 'coniare' un effettivo oggetto che verrà
poi dotato di dati concreti gestito dai corrispondenti metodi.
In altri termini, una classe non è un oggetto, ma una sua descrizione e come tale non occupa memoria.
16
autonomia/litri del veicolo.
Inoltre si nota la dichiarazione e l'implementazione del costruttore. I costruttori non possono restituire
valori.
Il costruttore di un oggetto, è invocato al momento della creazione dell'oggetto medesimo.
Ciò significa che è invocato quando viene eseguita la dichiarazione dell'oggetto. L'output del programma è
il seguente:
il veicolo ha un consumo:7.5km/l
oggetto distrutto
Il complemento costruttore è il distruttore; in numerose circostanze, un oggetto nel momento in cui viene
distrutto deve eseguire una o più azioni (oggetti locali sono creati all'entrata del relativo blocco di codice e
distrutti all'uscita).
Lo scopo principale del distruttore, rimane comunque la deallocazione della memoria precedentemente
richiesta all'atto dell'istanziazione da parte del costruttore.
Supponiamo ora, che di un determinato veicolo più che del consumo ci interessi il consumo per persona
trasportata; in pratica introdurremo un nuovo parametro, chiamato 'convenienza' che viene definito come:
convenienza = consumo/p
dove con p indichiamo il numero di persone trasportate, in questo modo il metodo consumo() diventa
accessorio al calcolo della convenienza; se di esso non interessa stampare i valori, può anche essere
dichiarato privato.:
3.2.5 EREDITARIETÀ
Tutti i veicoli hanno delle stesse caratteristiche di base, come si è visto autonomia, volume del serbatoio in
lt, persone trasportate; sappiamo anche che essi subiscono delle classificazioni di massima.
Ad esempio, ci sono moto che possono trasportare solo una persona, mentre altre ne possono trasportare 2.
Ci sono i motocarri e i sidecar; i primi possono trasportare al massimo due persone in cabina (+ il carico) i
secondi non hanno carico e possono trasportare fino a 3 persone.
Poi ci sono auto (che hanno 4 ruote) che possono trasportare 5 persone, ma alcune solo 4.
Il discorso potrebbe dilungarsi coi pullman che arrivano ad avere 6 ruote e un bel po' di passeggeri. Tutte
queste classi possono essere derivate dalla classe base dei veicoli.
18
Il sistema gerarchico dell'ereditarietà può essere illustrato come in figura; per convenzione una freccia punta
dalla classe derivata fino alla classe base.
L'ereditarietà si esplica permettendo ad una classe di incorporarne un'altra nella propria dichiarazione .
Il seguente programma, si propone di gestire soltanto dei veicoli che siano o motoveicoli (max 3 ruote, 3
passeggeri) o autoveicoli (min=max=4 ruote, 5 passeggeri) .
p=persone trasportate
r=numero di ruote
Come si nota, se istanziamo un auto di A l'attributo r=4 è assicurato dalla definizione di costante statica:
static const int ruote=4;
Per ciascuno dei mezzi usati deve essere possibile calcolare la convenienza().
Si deduce, come a questo punto, il metodo consumo(), può restare privato all'interno della classe
veicolo, assieme al metodo pubblico convenienza() senza che questi metodi debbano essere
riscritti ulteriormente per le due nuove classi.
#include < iostream >
using namespace std;
const float prz=1.5;
class V {//(veicolo)
19
int a;//autonomia (km)
int l;//volume del sebatoio(lt)
float consumo();//a/l
protected: int p;//passeggeri
//p accessibile solo dalle classi derivate
public: V(int x,int y);//costruttore
float convenienza();//consumo/p
};
V::V(int x,int y){
a=x;//autonomia
l=y;//litri di pieno
}
float V::convenienza(){
return prz*consumo()/p;
}
float V::consumo(){
return (float)a/l;
} //______________________________________fine classe V
class A:public V {
static const int ruote=4;//ruote
public: A(int aut,int ltvol,int pas);//costruttore
};
A::A(int aut,int ltvol,int pas):V(aut,ltvol){//costruttore
p=pas;//passeggeri
r=ruote;
} //______________________________________fine classe A
class M:public V {
int r;//ruote (variabili)
public: M (
int aut, int ltvol,int pas,int ruo);//costruttore
};
M::M(int aut,int ltvol,int pas,int
20
ruo):V(aut,ltvol){//costruttore
p=pas;//passeggeri
r=ruo;//ruote
} //______________________________________fine classe M
main(){
A a1(300,40,5);
cout << "il veicolo ha convenienza:" << a1.convenienza() <<
"E/persona\n"; M m1(200,20,2,3);
cout << "il veicolo ha convenienza:" << m1.convenienza() <<
"E/persona\n";
}//__________fine main
Notare come l'attributo p sia stato iscritto con la clausola protected nella classe V; in tal modo esso sarà
accessibile solo dai metodi delle due classi derivate M ed A, per altri classi diverse da quelle derivate esso
risulterà protetto.
Notare, inoltre, come la costante ruote (numero di ruote) nella classe A (auto) debba essere dichiarata static;
la parola static specifica che questo attributo viene allocato in memoria una sola volta per tutte le istanze
della classe A; diversamente dall'attributo r della classe M che richiede un'allocazione in memoria ad ogni
oggetto riferito alla classe M che viene creato; questo principio non vale solo per le costanti, ma anche per le
variabili. Un altro fatto rilevante è come vengono dichiarati i due costruttori:
osservando i due costruttori si nota come essi non riconoscano i primi due parametri che vengono passati
alla classe base V che li memorizza negli attributi aut (autonomia) ed l (litri del pieno) mentre il
parametro pas viene affidato all'attributo p ed il parametro ruo all'attributo r (però solo nel caso di
3.2.6 POLIMORFISMO
Per poter essere un linguaggio di programmazione orientato agli oggetti, il linguaggio deve supportare il
polimorfismo.
Il polimorfismo è un principio della OOP che, data una gerarchia di classi di oggetti, offre la possibilità di
usare lo stesso metodo applicato ad oggetti diversi della gerarchia, oppure lo stesso nome per identificare
21
metodi diversi, incapsulati in classi differenti.
Una variante al nostro programma potrebbe essere quella di delegare ad un unico metodo mostra() le
caratteristiche del veicolo; il risultato sarebbe il seguente:
22
float V::consumo(){
return (float)a/l;
} //______________________________________fine classe V
class A: public V {
static const int ruote=4;//ruote
public:
A(int aut,int ltvol,int pas);//costruttore
};
A::A(int aut,int ltvol,int pas):
V(aut,ltvol){//costruttore
p=pas;//passeggeri
r=ruote;
} //______________________________________fine classe A
class M:public V {
public: M(int aut,int ltvol,int pas,int ruo);//costruttore
};
M::M(int aut,int ltvol,int pas,int ruo):
V(aut,ltvol){//costruttore
p=pas;//passeggeri
r=ruo;//ruote - variabili
} //______________________________________fine classe M
main() {
A a1(300,40,5);
a1.mostra();
M m1(200,20,2,3);
m1.mostra();
}//__________fine main
La classe convenienza() può diventare privata, in ogni caso sarà accessibile dal metodo pubblico
mostra() che così potrò essere invocato dal main attraverso le chiamate:
a1.mostra();
23
m1.mostra();
supponiamo,ora, che solo nel caso di motocicli M a 2 o a 3 ruote il programma debba specificare se si tratta
di una moto, di un sidecar o di un motocarro:
La classe M vede ridefinito il metodo mostra() in tal caso, un oggetto di classe M istanziato, dovrò usare
quest'ultimo metodo per l'output a video e non quello riportato nella classe V.
Una importante modifica, riguarda la ridefinizione a 'protected' degli attributi della classe V, questa
commutazione è stata resa necessaria per permettere alla classe derivata M di accedere agli attributi (prima)
privati della classe V.
Abbiamo usato stesso nome per identificare metodi diversi, incapsulati in classi differenti della gerarchia,
dimostrando, così, il principio del polimorfismo.
Nella classe M il metodo mostra() è ulteriormente specializzato per identificare il tipo di veicolo.
SOMMARIO
3.2.6 Polimorfismo………………………………………………………………………………………………………………… 21
26
27