Calcolatori Elettronici III
Riepilogo delle lezioni teledidattiche
Massimo Oss Noser
Contatti
Professore: Maurizio Fermeglia
• Mail: [email protected]
Tutore: Massimo Oss Noser
• Mail: [email protected]
• Cell: +393479497135
Esame (I)
Ammissione al colloquio
• Realizzare una applicazione in linguaggio C++
dove siano applicati i principali concetti della
programmazione ad oggetti.
• Il codice deve essere ben commentato e preceduto
da una breve descrizione generale che illustri
anche le tecniche di programmazione usate.
Esame (II)
Ammissione al colloquio
• I lavori devono essere spediti al tutore per la
valutazione e l’ammissione al colloquio.
• La valutazione è proporzionale al contenuto
tecnico dell’applicazione che non può prescindere
dall’uso (seppur minimo) dei tipi, delle librerie
standard C++ e dei concetti fondamentali di
ereditarietà, data-hiding e polimorfismo.
Esame (III)
In ordine di importanza verrà valutato
l’utilizzo delle seguenti tecniche di
programmazione:
1. Classi, ereditarietà, data hiding e polimorfismo.
2. Overloading operatori, libraria standard.
Esame (IV)
3. Algoritmi generici.
4. Utilizzo di librerie esterne (interfaccie testuali,
grafiche, etc…).
5. Commenti nei sorgenti e documentazione.
6. Utilità dell’applicazione.
Esame (V)
Colloquio:
• La data di svolgimento del colloquio va
concordata con il professore ed il tutore.
• All’esame dalla presentazione del programma
realizzato si trarrà spunto per una discussione sulle
tecniche di programmazione ad oggetti.
Approfondimento: file Intro2OOP.ps
Programmazione destrutturata, strutturata,
procedurale, modulare, ad oggetti.
Stili di programmazione (I)
Programmazione procedurale
Dato un problema lo si decompone in parti via via
più semplici, finchè tutte le parti sono codificabili
nel linguaggio di programmazione scelto.
Questo è lo stile di programmazione con cui
normalmente si codificano i problemi nel
linguaggio C.
Stili di programmazione (II)
Programmazione ad oggetti
Dato un problema si ragiona in termini di entità, che
dotate di sufficienti proprietà e opportunamente
relazionate tra di loro descrivono operativamente
il problema.
I linguaggi di programmazione ad oggetti sono dotati
di costrutti linguistici che favoriscono la codifica
dei problemi in questi termini.
Stili di programmazione (III)
Programmazione ad oggetti
Il costrutto linguistico fondamentale nei linguaggi di
programmazione ad oggetti è la classe.
Con le classi si modellano le entità del problema che
dobbiamo risolvere.
Le classi sono costituite da dati e funzioni in grado
di trattare coerentemente tali dati.
Linguaggi di programmazione (I)
Tradizionali
Non è necessario il concetto di classe.
Normalmente si adotta uno stile di programmazione
procedurale.
Pascal, C sono esempi di linguaggi di
programmazione tradizionali.
Obbiettivo del corso: programmare ad oggetti!
Linguaggi di programmazione
(II)
Orientati agli oggetti
Non è necessario il concetto di classe se non si
intende utilizzare gli oggetti.
E’ possibile adottare uno stile di programmazione
procedurale o ad oggetti.
Object Pascal, Objective C e C++ sono esempi di
linguaggi orientati agli oggetti.
Linguaggi di programmazione
(III)
Linguaggi ad oggetti “puri”
Non è possibile prescindere dal concetto di classe.
Lo stile di programmazione utilizzabile è quello ad
oggetti.
SmallTalk e Java sono esempi di linguaggi ad oggetti
puri.
Approfondimento: C++ Annotation, cap. 2-3
C++ come C “migliore”
Caratteristiche
Linguaggio orientato agli oggetti.
Utilizzabile anche per programmare in modo
tradizionale.
Grande compatibilità con il C. Introduce degli
importanti miglioramenti che limitano gli errori
più comuni con il C.
C++ come C “migliore”
Caratteristiche
Controllo rigoroso dei tipi di dato, non è possibile
scambiare valori tra variabili di tipo diverso se non
è stata prevista una opportuna conversione da un
tipo all’altro.
Controllo sui prototipi delle funzioni, le funzioni
devono sempre essere avere un prototipo.
C++ come C “migliore”
Utilizzare i tipi di dato const al posto del #define per
dichiarare le costanti:
• C
#define PI 3.14159
#define MAX_INT 0x7FFFFFFF
#define MAX_UNSIGNED 0xFFFFFFFF
• C++
const double PI = 3.14159;
const int MAX_INT = 0x7FFFFFFF;
const unsigned MAX_UNSIGNED = 0xFFFFFFFF;
C++ come C “migliore”
• I nomi dichiarati con #define non hanno tipo e la
loro visibilità non subisce restrizioni all’interno
del sorgente.
• I nomi dichiarati con l’utilizzo di const hanno un
tipo ben preciso e la loro visibilità è controllabile.
C++ come C “migliore”
Usare al posto delle macro le inline functions.
• C
#define MAX(A,B) (((A) >= (B)) ? (A) : (B))
… MAX(a++,b++); ???
• C++
inline int MAX(int a, int b) { return a>=b ? a : b; }
… MAX(a++,b++); OK!
C++ come C “migliore”
Definizione delle variabili
Le variabili possono essere definite ed inizializzate
ad un valore di default in qualsiasi punto del
codice.
E’ preferibile definire le variabili dove servono in
modo da facilitare la comprensione del codice.
C++ come C “migliore”
Esempi di definizione delle variabili
• Inizializzazione a valori di default:
int a=10, b=5, c(7);
char d=‘A’, e(‘B’);
• Definizione nel contesto d’uso:
for(int i=0; i<10; i++) ...
C++ come C “migliore”
Nuovi tipi di dato
• bool tipo di dato booleano con valori true e false
(keyword riservate).
L’utilizzo dei valori booleani è consigliato per
eliminare ambiguità sul significato del valore 0
che in certi contesti sta per vero in altri per falso.
C++ come C “migliore”
Nuovi tipi di dato
• string stringa di caratteri. Utilizzabile al posto del
array di char ma più comodo e vantaggioso da
usare.
In realtà questo è un primo esempio di classe. Per
utilizzare le classi non è detto sia necessario tale
concetto.
C++ come C “migliore”
Esempi d’uso dei nuovi tipi di dato
• Bool
bool vero=true;
bool falso=false;
…
if(vero && !falso) …
C++ come C “migliore”
Esempi d’uso dei nuovi tipi di dato
• String
string s;
// Stringa vuota
string s0 = “”;
// Stringa vuota
string s1 = “Hello World”;
string s2 = s1;
//Assegnazione
string s3 = s1 + s2;
// Concatanazione
Approfondimento: C++ Annotation cap. 5.4
C++ come C “migliore”
Esempi d’uso dei nuovi tipi di dato
string a = “ciao”, b( “CIAO” );
if (a == b) …
// Stringhe identiche
if (a != b) …
// Stringhe diverse
if (a > b) …
// Ordine alfabetico
char c1 = a[0];
// c1=‘c’
char c2 = b[1];
// c2=‘I’
C++ come C “migliore”
Usare new e delete invece di malloc e free:
int *i = new int[100];
delete [] i;
string *s = new string[100];
delete [] s;
C++ come C “migliore”
I/O standard
Il C++ ha una nuova libreria per I/O che prevede
nuove modalità di utilizzo dei dispositivi per
l’input/output.
Come per il nuovo tipo di dato string per utilizzare le
classi per la gestione dell’I/O non è necessario
sapere cos’è una classe, è sufficiente sapere come
si opera su di essa.
C++ come C “migliore”
I/O standard
La nuova libreria per I/O standard si chiama
iostream. Essa introduce due nuovi oggetti che
verranno utilizzati rispettivamente per l’output e
l’input standard cout e cin.
Per operare con tali oggetti si usano gli operatori di
redirezione << e >>.
C++ come C “migliore”
Esempi d’uso di cout
#inlcude iostream
void main()
{
cout << “Hello World!!” << endl;
}
C++ come C “migliore”
Esempi d’uso di cout
int a = 5; float b = 12.44; bool vero = true;
cout << “Intero: ” << a << “ Float: “ << b
<< “ Booleano: “ << vero;
C++ come C “migliore”
Esempi d’uso di cout
cout << dec << 16 << “, “ << hex << 16 << “, “
<< oct << 16;
Esistono dei manipolatori che permettono di
modificare il formato dei valori d’uscita.
C++ come C “migliore”
Esempi d’uso di cin
...
int a;
string s;
cin >> a >> s;
cout << “Intero: “ << a << “ Stringa: “ << s;
...
C++ come C “migliore”
Esempi d’uso di cin
...
string s;
// Lettura di un testo terminato con invio
getline( cin , s );
cout “Riga inserita: “ << s << endl;
...
C++ come C “migliore”
Overloading delle funzioni
In C++ più funzioni possono avere lo stesso nome, a
patto che siano differenti il tipo e/o il numero dei
parametri di ingresso.
Nel momento in cui si utilizza una funzione i
parametri di ingresso permettono al compilatore di
selezionare la funzione corretta tra tutte quelle che
hanno lo stesso nome.
C++ come C “migliore”
Esempi di overloading
#include <iostream>
#include <math>
int modulo( int );
double modulo( double );
double modulo( double, double );
C++ come C “migliore”
Esempi di overloading
int modulo(int i)
{ return( i < 0 ? -i : i ); }
double modulo( double f)
{ return( f < 0.0L ? -f : f ); }
C++ come C “migliore”
Esempi di overloading
double modulo( double re, double im)
{ return( sqrt( re*re + im*im ) );}
…
cout << modulo( -5 );
// Stampa 5
cout << modulo( -2.4 ); // Stampa 2.4
cout << modulo( 1.2, -2.2 ); // ...
C++ come C “migliore”
Argomenti di default
E’ possibile assegnare nelle funzioni valori di default
per i parametri di ingresso.
Se i parametri di una funzione sono dotati di valori
di default è possibile, se non crea ambiguità con
l’overloading delle funzioni, omettere i parametri
nella chiamata nella funzione.
C++ come C “migliore”
Argomenti di default: esempi
void stampa( string );
// Prototipo
void stampa( string s = “Hello World!!” )
{
cout << s; }
…
stampa();
// Stampa Hello Wordd!!
Stampa( “ciao” ); // Stampa ciao
C++ come C “migliore”
Argomenti di default: esempi
void esempio( int, int, int );
void esempio( int i1 = 1, int i2 = 2, int i3 = 3 );
{
cout << i1 << i2 << i3;
}
C++ come C “migliore”
Argomenti di default: esempi
…
esempio();
esempio( 7 );
esempio( 7, 8 );
esempio( 7, 8, 9 );
...
// 123
// 723
// 783
// 789
C++ come C “migliore”
Reference type
Un reference si lega ad una variabile e, da quel
momento, usare la variabile o il suo “riferimento”
produce lo stesso effetto.
Costituiscono una nuova tipologia di variabili
concettualmente duali rispetto ai puntatori.
C++ come C “migliore”
Reference type: esempi
…
int intero = 5;
int &ref = intero; // Definizione
…
La variabile ref è un riferimento alla variabile intero.
C++ come C “migliore”
Reference type: esempi
L’effetto è identico usando ref o usando intero.
…
intero++;
cout << intero;
…
Risultato: 6
…
ref++;
cout << ref;
…
Rusultato: 6
C++ come C “migliore”
Reference type: esempi
In C come in C++ i parametri delle funzioni sono
passati per valore. Questo significa che la funzione
lavora su delle copie dei dati delle variabili
argomento.
A livello di chiamata i dati possono solo entrare dagli
argomenti della funzione.
C++ come C “migliore”
...
int a = 2, b = 5;
cout << a << b;
scambia( a, b );
cout << a << b;
...
// 25
// ???
// 52
Come in questo caso talvolta
può essere utile il
passaggio di parametri per
riferimento, in modo da
permettere alla funzione di
operare sui dati originali
che le vengono passati.
C++ come C “migliore”
...
void scambia( int, int );
void scambia( int x, int y )
{
int z = x;
x = y;
y = z;
}
...
Questa funzione non produce
l’effetto voluto. I valori
delle variabili x e y, copie
dei valori di a e b,
vengono scambiati ma
questo non ha effetto sui
valori contenuti in a e b.
C++ come C “migliore”
In C (e volendo in C++) si
utilizzano i puntatori
per ottenere l’effetto
voluto. Questo però
comporta qualche
piccola scomodità
sintattica.
void scambia(int *, int *);
void scambia(int *x, int *y);
{ int z = *x;
*x = *y;
*y = z;
}
…
// Chiamata della funzione
scambia( &a, &b );
C++ come C “migliore”
Usando i reference si
ottiene con eleganza il
passaggio dei parametri
per riferimento.
void scambia(int &, int &);
void scambia(int &x, int &y)
{ int z = x;
x = y;
y = z;
}
…
// Chiamata alla funzione
scambia( a, b );
C++ come C “migliore”
Possiamo quindi usare il tipo
reference per far uscire
dati dai parametri di
ingresso di una funzione.
Una curiosità è che possiamo
fare anche l’inverso, fare
entrare dati dall’uscita di
una funzone.
…
int &fun();
int &fun()
{ static int valore;
return(valore);
}
…
fun() = 20;
// “Entra” 20
cout << fun(); // Stampa 20
Una variabile di tipo reference occupa memoria?
C++ come C “migliore”
Proprietà dei reference
int i1 = 5, i2 = 7;
int &ref = i1;
&ref = i2;
// Non è possibile cambiare la variabile
// a cui fa riferimento ref. Resterà
// sempre riferita ad i1.
int *p = &ref;
// Il reference non restituisce il proprio
// indirizzo ma l’indirizzo della
// variabile cui è riferito, i1.
C++ come C “migliore”
Operatore di scope resolution
#include <iostream>
Permette di specificare
“l’ambito” di utilizzo di
un certo nome, che può
essere una variabile o una
funzione.
int conta = 20;
void main()
{
int conta = 10;
cout << ::conta;
cout << conta;
}
ambito::nome
// 20
// 10
C++ come C “migliore”
Operatore di scope resolution
In C++ è possibile operare
dei raggruppamenti logici
di variabili e funzioni.
namespace parole{
string testo;
bool trova(string, string);
int conta(string);
}
parole::testo=“Hello World”;
parole::conta(parole::testo);
Le classi
Concetti fondamentali
Data hiding
Ereditarietà
Polimorfismo
Le classi
Data hiding
Nel mondo reale interagiamo con gli oggetti
attraverso delle interfaccie. Un automobile ha
fondamentalmente un volante, tre pedali e una
leva del cambio attraverso le quali possiamo
utilizzarla senza sapere come funzionano una
infinità di meccanismi interni.
Le classi
Data hiding
Questo concetto di isolamento dei meccanismi
interni di funzionamento si rivela utile anche negli
oggetti software. Nascondere le funzioni e
soprattutto i dati di un oggetto permette di
costruire oggetti utilizzabili come “scatole nere”
prescindendo dal come sono fatti.
Le classi
Oggetti software
Un oggetto software è costituito da dati e funzioni
racchiusi in un’unica struttura.
Il C++ estende la sintassi delle struct permettendo
l’inserimento di funzioni all’interno delle strutture.
Le struct così arricchite permettono la definizione di
nuovi tipi di dato che prendono il nome di classi.
Le classi
Oggetti software
Per realizzare il data hiding è necessario poter
specificare quali parti della struct costituiscono
l’interfaccia e quali sono la parte interna che deve
restare isolata dall’esterno.
Le keyword public e private dell’esempio successivo
indicano questo.
Le classi
Oggetti software
struct Persona {
private:
string nome, cognome;
int eta;
public:
string Nome();
void SetNome(string n);
}
string Persona::Nome()
{
return (nome);
}
void SetNome(string n)
{
nome = n;
}
Le classi
Oggetti software
Persona Mario;
Mario.SetNome(“Mario”);
cout << Mario.Nome();
// Mario
Mario.nome = “Pippo”;
// Errore non posso!!
Le classi
Keyword class
La keyword struct suppone se non indicato che i
membri della struttura siano pubblici cioè facenti
parte dell’interfaccia.
La keyword class suppone che siano privati per
sottolineare che solo le funzioni dell’interfaccia
devono essere pubbliche.
Le classi
class Persona {
private:
string nome, cognome;
int eta;
public:
string Nome();
void SetNome(string n);
}
Omettendo la keyword
private nella struct si
ottiene un oggetto dove
sono resi pubblici anche i
dati.
Omettendo le keyword
private nella class si
ottiene un oggetto, come
deve essere, con i dati
privati.
Le classi
class Persona {
private:
string nome, cognome;
int età;
public:
string Nome();
void Nome(string n);
stirng Cognome();
void Cognome(string n);
…
}
• Data hiding
Persona p;
p->Nome(“Mario”);
cout << p->Nome();
Le classi
Costruttori
In molti casi è necessario, prima di poter utilizzare
un oggetto, svolgere delle operazioni di
inizializzazione. Queste operazioni possono venire
svolte in una particolare funzione “il costruttore”.
Tale funzione è senza parametro di uscita e deve
avere lo stesso nome della classe.
Le classi
struct Persona {
private:
string nome;
public:
Persona();
// *1
Persona(string n);// *2
string Nome() const;
void Nome(string n);
}
In questo esempio ci sono
due costruttori. Grazie
all’operator overloading a
seconda dei casi sarà
selezionato quello
corretto.
Persona Anonima;
//*1
Persona Mario(“Mario”);//*2
Le classi
class Persona {
string nome, cognome;
public:
Persona();
Persona(string n, string co) { nome = n; cognome = co; };
// L’assegnazione non è efficiente nome viene inizializzato ad una
stringa vuota poi alla stringa voluta. Questo implica 2 new e 1
delete.
Persona(string n, string co) : nome(n), cognome(co);
// L’inizializzazione oltre ad essere elegante è efficiente, nome
viene direttamente inizializzato al valore voluto.
Le classi
Oggetti costanti
const Persona p1(“Mario”);
Persona p2(“Marco”);
cout << p1->Nome(); // OK
p1->Nome(“Paolo”); // Errore oggetto costante;
cout << p2->Nome(); // OK
p2->Nome(“Paolo”); // OK
Le classi
Distruttori
Quando un oggetto viene rimosso dalla memoria
prima della sua eliminazione potrebbe essere
necessario svolgere delle operazioni. Questo
compito spetta alla funzione “distruttore”.
Tale funzione ha lo stesso nome della classe
preceduta dal simbolo “~”.
Le classi
class Persona {
string *nome;
public:
Persona();
Persona(string s) { nome = new string(s); };
~Persona() { delete nome; };
}
Le classi
class Base {
public:
…
private:
…
protected:
…
}
class Derivata: Base {
public:
…
private:
…
protected:
…
}
Le classi
Tutto ciò che è pubblico o protetto nella classe Base
viene ereditato dalla classe Derivata. Tutto quello
che è privato resta accessibile solo attraverso
l’interfaccia fornita dalla classe Base.
Nella classe Derivata è possibile ampliare le
funzionalità della classe Base o modificarle.
Le classi
class Base {
…
public:
void Funzione1( int a );
…
}
Le classi
class Derivata {
public:
// Estensione
int Funzione1() {
Base::Funzione1(3); return (…); };
// Modifica
int Funzione1() { return (…); }
}
Le classi
Base *lista[3];
lista[0] = new Base();
lista[1] = new Derivata();
lista[2] = new Derivata();
Se definisco una variabile di tipo puntatore a Base la posso
inizializzare con una qualsiasi classe derivata.
Anche se posso accedere direttamente solo a metodi di Base.
Le classi
Per avere un comportamento polimorfico
devo definire una interfaccia comune a tutte
le classi derivate.
Posso farlo attraverso le funzioni virtulali.
Le classi
class Base {
…
public:
…
virtual void interfaccia1( int a );
virtual int interfaccia2() const;
…
}
Le classi
class Derivata {
…
public:
…
void interfaccia1( int a );
int interfaccia2() const;
…
}
Le classi
Base *lista[3];
lista[0] = new Base();
lista[1] = new Derivata();
lista[2] = new Derivata();
for (int i=0; i<3; i++) lista[i]->interfaccia2();
Ciascun oggetto risponderà in base al proprio tipo.
Scarica

Calcolatori Elettronici III