Minicorso di C++ (come secondo requisito)


Questo minicorso serve per imparare gli elementi del C++ che sono stati
mantenuti dal Java, anche se spesso con una sintassi diversa.
Soprattutto e' importante capire l'aggiornamento alla programmazione
orientata agli oggetti, per prepararsi alla radicale "OOPizzazione",
quando passeremo al Java.
In fondo il C++ e' un tentativo di rendere OO (Object Oriented) il C,
mantenendo pero' la compatibilita' col vecchio linguaggio.
Questa e' una soluzione che ha il vantaggio di rendere possibile ai
programmatori C di poter continuare ad usare il linguaggio che conoscono,
senza traumi troppo grandi, con in piu' la possibilita' di utilizzare la
nuova filosofia. Pero' e' un compromesso, e non una vera rivoluzione.
Per questo motivo, e per le molte differenze di sintassi col Java, in
questo minicorso la cosa fondamentale sara' di far entrare nella nuova
filosofia della OOP, quindi gli esempi pratici in C++ saranno ridotti.
Considerate che nella parte sul Java si danno per scontati i concetti di
classe, ereditarieta' e polimorfismo, quindi se per voi e' arabo, non
saltate questa parte.

Se volete provare i listati, vi occorre un compilatore C++, comunque dato
che il minicorso e' soprattutto teorico, non e' indispensabile.

Fin qua abbiamo analizzato l'ANSI C storico, come preparazione alla sua
versione modernizzata, ossia il C++, orientato agli oggetti.
Dando per scontato che si sappia programmare in C, introduciamo il concetto
di programmazione orientata agli oggetti (OOP).
Il C++ e' stato sviluppato nei primi anni 80 da Bjarne Stroustrup, ispirato
da altri linguaggi, come il Simula e l'Algol 68.
Le caratteristiche principali sono INCAPSULATION (incapsulamento di dati e
funzioni all'interno di un oggetto), INHERITANCE (ereditarieta'),
POLIMORPHISM (polimorfismo, ossia uso di funzioni con lo stesso nome per
compiti simili, ma che svolgono operazioni diverse avendo diversi parametri).
Agli albori dell'informatica i computer non erano molto complessi, e anche
i programmi erano piuttosto brevi, quindi erano scritti in linguaggio
assembly, ossia la diretta rappresentazione delle istruzioni del
microprocessore. Negli anni sessanta comparve la programmazione strutturata
e ad alto livello, ossia il C e il Pascal, che permettevano di scrivere
facilmente programmi abbastanza complessi. Ma i computer sono diventati piu'
complicati e potenti, per cui l'approccio strutturato non e' piu' sufficiente
per il programmatore a causa del'eccessiva complessita' dei listati.
La programmazione ad oggetti scompone un problema in sottoinsiemi collegati,
che e' possibile tradurre in "oggetti" autonomi.
In questo modo si produce codice veramente modulare e riutilizzabile, in
modo da poter costruire oggetti complessi assemblando componenti di base
semplici, facilmente sostitubili e verificabili, magari scritti da altri,
risparmiando tempo e fatica: per cambiare la funzionalita' di un programma
bastera' cambiare solo l'oggetto che si occupa di tale funzionalita',
senza modificare il resto del programma.
Ogni oggetto e' come un compomente di una televisione o di una lavatrice:
i pezzi sono costruiti e testati da differenti persone: chi assembla la
lavatrice non dovra' reinventare il transistor, perdendo tempo e rischiando
di includerci errori e bug.
Ma questo non si puo' fare gia' in C, curando le interfacce I/O delle
funzioni e evitando dipendenze "nascoste" fra parti diverse del programma?
Fino a un certo punto si, ma in C++ e' impossibile usare un oggetto in modo
diverso da quello voluto da chi l'ha progettato, e non si puo' corrompere un
dato al suo interno, ne' per errore, ne' volendo: l'oggetto e' come una
"scatola nera", analoga ad un transistor o un integrato, ossia un componente
modulare prefabbricato, utilizzabile per creare radio, videoregistratori o
altro.

I linguaggi OOP hanno 3 elementi in comune:

1) OGGETTI: sono entita' contenenti sia i dati che le istruzioni che
   li manipolano. All'interno di un oggetto, alcune funzioni e/o
   variabili possono essere private, ossia inacessibili dall'esterno:
   questo protegge tali dati da eventuali manipolazioni errate.
   Questo collegamento tra istruzioni e dati e' detto INCAPSULAMENTO:
   ossia la definizione di un oggetto (classe) composto da funzioni
   (metodi) e dati (variabili d'istanza).
   Quando si definisce una classe, si organizza astrattamente l'oggetto,
   come una struttura in C: si definisce che ci sono x variabili di
   tipo int, a cui accedono le funzioni x e y. Per poter usare tale
   classe, occorre creare oggetti di quel tipo, che sono ISTANZA della
   della classe. In altri termini creiamo (istanziamo) esemplari di
   quella particolare classe, da noi definita.
   Si evitano variabili globali che possono essere modificate al momento
   sbagliato da funzioni esterne. Se si "nascondono" in un oggetto, insieme
   a funzioni di interfaccia (ossia che ci permettano di comunicare con i
   dati nell'oggetto), si ha un accesso controllato.
   Un oggetto ha un repertorio di "comportamenti", o "METODI" che
   sa eseguire: per chiedergli di applicare uno dei suoi metodi,
   occorre mandargli un MESSAGGIO, ed esso risponde col metodo
   corrispondente.
   In questo modo non agiamo sull'oggetto direttamente: ad esempio
   per distruggerlo gli mandiamo il messaggio di autodistruzione, e lui
   esegue.

2) POLIMORFISMO: ossia la possibilita' di servirsi di uno stesso nome
   per intenti simili. Associando un nome a una tipologia logica di
   azioni, sara' poi il programma a eseguire il caso specifico adatto
   al tipo di dati che si trova di fronte. Per esempio si possono fare
   due diverse funzioni con lo stesso compito, una per valori interi e
   una per valori float, chiamate entrambe sommali(): sara' il compilatore
   a selezionare la routine corretta secondo il tipo di dati passati a
   sommali() nel momento della chiamata.
   Gli oggetti derivati dalla stessa classe possono compiere una azione
   comune a tutta la gerarchia, anche se ciascuno con proprie variazioni
   sulle modalita' di esecuzione.
   Se si mandano messaggi uguali ad istanze diverse di classi derivate
   dalla stessa classe base (superclasse), ognuna risponde con il proprio
   metodo, per cui la funzione che manda il messaggio non dovra' essere
   ne' modificata, ne' ricompilata.

3) EREDITARIETA': e' la possibilita' per un oggetto di ereditare le
   proprieta' di un altro oggetto, che si basa sul principio di
   classificazione gerarchica: ad esempio una mela delicious fa parte
   della classe mela, che a sua volta fa parte della classe frutta, ecc.
   In mancanza di classificazioni, ogni oggetto dovrebbe definire
   esplicitamente tutte le proprie caratteristiche, invece con le classi
   bastano le qualita' che lo rendono unico all'interno della propria
   classe, dalla quale puo' ereditare le proprieta' piu' comuni (essere
   un frutto, in particolare una mela). Bastera' poi specificare le
   caratteristiche specifiche di quella particolare mela (colore rosso).
   Per derivare un oggetto da un'altro non occorre avere i sorgenti
   dell'oggetto di partenza, la derivazione e' formale. Ogni oggetto
   derivato "sa fare" cio' che sa fare il capostipite, a modo suo: ad
   esempio avendo la classe di oggetti "modem", tutti i suoi derivati
   sapranno fare una connessione, chi con un protocollo, chi con un'altro,
   chi su linea normale, chi su linee speciali. A questo punto, se esce
   un nuovo tipo di modem, non previsto al momento della stesura del
   programma, avremo gia' pronta la classe da cui derivarlo, e il resto
   del programma funzionera' senza modifiche anche col nuovo modem.
   Data una classe base si possono definire classi da essa derivate,
   specificando solo cosa hanno in piu' o di diverso rispetto alla
   superclasse. Un metodo ereditato si puo' ridefinire, per cui avra' lo
   stesso nome di quello originale, ma una diversa implementazione; il
   corrispondente metodo della superclasse non viene alterato.

Recapitolando:

- C'e' una nuova terminologia, in cui si danno nomi diversi a cose gia'
  esistenti in C, ad esempio le funzioni sono chiamate metodi, mentre
  le chiamate alle funzioni sono chiamate messaggi.

1) Una classe e' la definizione astratta di un gruppo di dati e metodi.
2) Un oggetto (a cui si da un nome) e' un'istanza "fisica" di una classe.
3) Un metodo e' una funzione contenuta nella classe.
4) Un messaggio serve per attivare un metodo (come chiamare una funzione).
5) Metodi con lo stesso nome si possono sovrapporre (override-polymorphism)
6) Una classe si puo' derivare da un'altra grazie all'ereditarieta'.

Come conclusione, supponiamo di voler convertire un listato C in C++.
La prima cosa da fare e' identificare le classi da costituire, e di
conseguenza quali membri dovranno essere privati. Si dovra' poi fare
in modo che tali dati siano raggiungibili dall'esterno solo attraverso
metodi pubblici, o dagli eventuali metodi privati interni all'oggetto.
Inoltre, occorre identificare quali sono le funzioni non direttamente
chiamate dal main(), ossia non pubbliche, e renderle private nell'oggetto
di appartenenza, ossia nell'oggetto che le richiama.
A questo punto occorre creare i costruttori e i distruttori della classe,
che possono essere anche void (vuoti).
Si dovra' anche stare attenti alla determinazione delle gerarchie tra
le classi, e il gioco degli accessi tra di esse.
E' importante separare i compiti, ad esempio se una sola funzione calcola
i dati e li scrive sul disco, sara' meglio dividerla in 2 classi, in modo
che se si volessero far scrivere tali dati su un diverso dispositivo (ad
esempio su nastro), basti sostituire solo la classe che scrive.

Dato che questa e' la prima versione del corso, ci sono pochi esempi; nelle
prossime versioni la situazione migliorera'.
I listati contengono parte della lezione, nelle ampie zone di commento.