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.