Una sera mio figlio mi domandò:"papà mi insegni la programmazione?".
E fu così che m'inventai una sorta di corso ultra veloce, e lui imparò a programmare, e siccome sono convinto che le esperienze positive debbano essere condivise cercherò di di trasmetterle a chi ha voglia di iniziare a programmare.
Il perché mio figlio mi ha chiesto d'insegnargli la programmazione è presto detto: aveva bisogno di poter fare qualcosa di sensato e corretto per i siti web, quindi programmazione con PHP e javascript.
Io non faccio programmazione web ma scrivo programmi per microcontrollori (da qui in poi chiamati "micro") e software per PC, di solito programmi di utilità per alcuni progetti basati su micro. Mi è anche capitato di scrivere del software per la gestione della mia ditta. Per queste cose uso il Java, per i micro uso il C .
E queste due sono le macchine per le quali è pensato questo tutorial: PC e micro.
Indice |
Premessa
Questo mini corso non è un manuale di riferimento. L'idea è quella di scrivere un qualcosa di estremamente comprensibile e di far comprendere il concetto, il senso di quello che è la programmazione nel minor tempo possibile. La lettura dovrebbe essere facile ed i concetti sintetizzati al massimo. Durante la trattazione degli argomenti tralascerò parecchie cose, anche importanti se vogliamo, che però riprenderò più avanti se e quando se ne presenterà l'occasione. Quindi so già che le mie semplificazioni scateneranno le ire di molti informatici; me ne farò una ragione e sarà interessante comprendere le loro critiche. Comunque se scriverò boiate o strafalcioni, errori di qualsiasi tipo insomma, vi prego di farmelo sapere.
Nota sul C: esistono diverse versioni di C, io farò riferimento a ANSI-C, più limitato rispetto, ad esempio, al C99 ma riconosciuto da tutti i compilatori per PC e per microcontrollori.
Cos'è un programma
Quando si usa un PC si utilizzano i suoi programmi. Il browser per la navigazione in internet, un editor di testo, uno strumento per modificare immagini o per disegnare componenti meccanici o circuiti elettronici sono tutti programmi. Un programma è un insieme (o meglio una lista) di istruzioni che il calcolatore esegue su dei dati che gli sono forniti. I dati possono essere forniti al programma in diversi modi. In un programma di disegno i dati sono le azioni che si compiono con la tastiera ed il mouse per disegnare, un foglio di calcolo può utilizzare dati forniti al momento ed inseriti tramite la tastiera o già memorizzati in un archivio (detto anche file).
I risultati che i programmi producono possono essere di diversi tipi. Nel caso di un browser il risultato è la visualizzazione della pagina web che si vuole visualizzare, un editor di testo produrrà, ad esempio, un documento pdf, un programma che visualizza file multimediali produrrà immagini in movimento, musica, suoni.
In generale possiamo dire che un calcolatore per generare un risultato, un output, ha bisogno di un programma e dei dati, cioè di un input.
Se vogliamo essere più precisi un PC, una volta acceso, esegue già un programma chiamato sistema operativo, una sorta di supervisore che controlla tutta la macchina e permette all'utente di eseguire (o lanciare) altri programmi.
Il programma di un micro invece esegue solitamente un solo compito: far funzionare l'apparecchiatura da lui controllata. Se il micro deve far funzionare una lavatrice il suo programma riceverà come dati di input la pressione dei pulsanti del frontale, la temperatura e livello dell'acqua; il suo output sarà il comando del motore che fa girare il cestello, la pompa di scarico, il blocco del portello di carico ed il riscaldatore per l'acqua ecc.
Programmazione strutturata e ad oggetti
Secondo il mio modo di vedere queste sono le due fasi per l'apprendimento della programmazione. La programmazione strutturata è nata prima della programmazione ad oggetti ed il tipo di programmazione che si usa per scrivere, ad esempio, programmi in C. La programmazione ad oggetti è l'evoluzione della programmazione strutturata ed è stata pensata per programmi di grandi dimensioni e per poter riutilizzare con facilità e sicurezza, parti di programmi scritti in precedenza o da altri per altri scopi. Java è un linguaggio di programmazione ad oggetti.
Va da sé che il primo passo per imparare la programmazione sia quello di imparare la programmazione strutturata.
Il linguaggio utilizzato
Per imparare la programmazione strutturata non servirebbe neanche un linguaggio di programmazione, a patto di studiare tutto in modo teorico e scrivere tutti gli esempi in pseudocodice (un misto fra lingua italiana e qualche costrutto tipico dei programmi).
Però bisogna anche avere qualcosa su cui provare realmente gli esempi o fare gli esercizi. Scegliere un linguaggio è come scegliere un'automobile per imparare a guidare. Se è troppo spartana e difficile da guidare non si presta abbastanza attenzione alla tecnica di guida, se è troppo sofisticata si rischia di passare il tempo perdendosi nei meandri dei vari accessori. Purtroppo ho visto in questo forum degli autodidatti che, provando a programmare, si perdono in dettagli inutili, come se, invece di impegnarsi ad imparare la tecnica di guida dell'auto, passassero il tempo a provare a vedere a cosa servono pulsanti e manopole del cruscotto.
Quindi userò come linguaggio per gli esperimenti principalmente il Java e a volte anche il C installati su NetBeans. Non scriverò un tutorial per preparare NetBean, per queste cose c'è il nostro bellissimo forum e tante persone competenti e volenterose che saranno felici di aiutare chi vorrà cimentarsi nello studio della programmazione. I motivi di questa scelta sono molti, ne elencherò alcuni:
- C e Java hanno una sintassi che è molto simile, anzi in molti casi identica.
- Se è vero che Java è un linguaggio fortemente orientato agli oggetti è anche vero che, con un piccolo sforzo e con un minimo di fiducia, si può benissimo fare programmazione strutturata.
- Java ha delle caratteristiche molto utili per imparare la programmazione. Ad esempio è fortemente tipizzato, possiede variabili di tipo boolean, input ed output da tastiera sono semplici, gli array portano con sé l'informazione sulla dimensione e via discorrendo.
- L'esecuzione di un programma Java si può seguire passo per passo, mediante visualizzazioni grafiche, finestre ed animazioni utilizzando un software che si chiama Jelliot. E' uno strumento didattico eccezionale con il quale si può capire molto bene cosa succede all'interno di un calcolatore.
- Con NetBeans si possono avere sia C che Java installati, quindi è facile provare gli esempi nei due linguaggi o passare da un linguaggio all'altro.
- Il C è il re per quanto riguarda la programmazione dei micro, sopratutto di un certo livello.
- Se si vuole proseguire con la programmazione ad oggetti, Java è un ottimo linguaggio, semplice e robusto.
- Altri linguaggi di programmazione come il C++, PHP e il Javascript assomigliano molto al Java o al C quindi se qualcuno vorrà cimentarsi su questi linguaggi troverà il passaggio molto semplice.
- Se è vero che Java non si usa (per ora) per scrivere programmi per i micro, le "app" per Android sono scritte in Java. Quindi l'apprendimento di Java è un buon investimento per coloro che vorranno divertirsi con lo smartphone.
Il calcolatore
Un PC, un telecomando gestito da un micro, o una centralina d'iniezione per un auto sportiva, sono in pratica la stessa cosa: macchine che eseguono programmi, sono calcolatori, detti anche computer. Ed anche la loro struttura è sostanzialmente la stessa. E questa è una bella notizia perché ci fa risparmiare tempo ed energie. Una rappresentazione molto semplificata (ma utile e necessaria) potrebbe essere questa.
In linea generale tutto il funzionamento del calcolatore è regolato dalla unità centrale di elaborazione che esegue il programma (sotto forma di codice macchina) presente nella memoria di programma, utilizza la memoria temporanea per memorizzare dati organizzati in vari modi (semplici valori numerici, sequenze di caratteri, tabelle) e per alcune strutture particolari, e comunica con il mondo esterno tramite i dispositivi di ingresso e uscita detti anche dispositivi di I/O, o semplicemente I/O.
A dire il vero non è esattamente così ma in generale lo è, e questa struttura è la stessa sia in un PC che in una scheda con un micro che controlla la vostra lavatrice. Vediamo ora dove sono le differenze:
- Il micro che controlla la scheda della lavatrice racchiude all'interno di un unico circuito integrato i quattro blocchi. In un PC questi blocchi sono realizzati con una moltitudine di circuiti integrati collegati fra di loro.
- I dispositivi di ingresso e uscita di un micro sono delle periferiche che attivano o disattivano i terminali (pin) del circuito integrato, o periferiche per il controllo di altri circuiti integrati e di potenza che, a loro volta, azioneranno il riscaldatore, acquisiranno la temperatura dell'acqua, il livello dell'acqua, i tasti del frontale ecc.. Purtroppo le applicazioni dei micro non sono sempre semplici come la scheda della lavatrice, sono molto più complessi ed i dispositivi elettronici collegati al micro sono, a volte, difficili e complicati da usare. La connessione e la gestione di questi dispositivi impiega una buona parte, in casi particolari quasi la totalità, del tempo utilizzato per scrivere il programma completo. Ad un micro si collega di tutto: dai semplici pulsanti a sensori o attuatori estremamente complessi dove il lavoro di interfacciamento bisogna farlo dall'inizio alla fine. Raramente si trovano librerie di funzioni già pronte.
- I dispositivi di ingresso uscita di un PC sono uno o più monitor, tastiera, mouse, uno o più dischi rigidi, lettore DVD, interfaccia USB per collegare altri dispositivi. Il controllo di questi dispositivi è, di solito, semplice perché esistono librerie di funzioni già scritte che rendono la vita facile.
- Nel micro della lavatrice la memoria di programma è un tipo di memoria che mantiene le informazioni (FLASH EPROM) perché il micro deve sempre e solo controllare la lavatrice. Sia la memoria di programma che quella temporanea sono di dimensioni molto inferiori rispetto a quelle di un PC. Nel PC esiste una memoria di programma di tipo FLASH ma serve solo per far partire la macchina. I programmi sono, di volta in volta, letti dal disco rigido, caricati nella memoria temporanea ed eseguiti. La memoria temporanea è quindi di dimensioni ragguardevoli perché devo poter contenere programmi assai grandi, diversi fra di loro e che vengono caricati di volta in volta.
Le differenze ci sono ma non sono un problema per quanto riguarda lo studio della programmazione perché questa resta sostanzialmente la stessa. Mi spiego meglio con un esempio: se ho una tabella contente un certo numero di valori e voglio ordinarla in modo da avere il valore più piccolo al primo posto, in cima alla tabella, ed il più grande nell'ultimo posto, il programma che farà questa operazione è lo stesso sia per il PC che per il micro.
Compilatori ed interpreti
I linguaggi C e Java sono linguaggi detti di alto livello perché permettono di scrivere programmi in modo che si può definire discorsivo quindi facile da usare e da comprendere. Ma c'è un problema: le unità centrali di elaborazione non sono in grado di eseguire direttamente un programma scritto in questi linguaggi perché eseguono solo il cosiddetto codice macchina chiamato anche codice oggetto che è una serie di istruzioni semplici scritte sotto forma di numeri binari. Inoltre unità centrali (processori) diverse usano diversi tipi di codice macchina. Non è pensabile che un programma in codice macchina che funziona su un PC possa funzionare su un micro o su un altro calcolatore che monta un processore diverso.
Questo problema viene risolto dal cosiddetto compilatore. In poche parole un compilatore è un programma che traduce il testo (sorgente) del programma che ho scritto, in una lista di istruzioni in codice macchina (codice oggetto) che il calcolatore può eseguire. Pare ovvio che il compilatore C per il PC produce un codice macchina diverso dal compilatore C per un micro, ma la buona notizia è che il linguaggio è lo stesso, cambia solo il cosiddetto target.
Per Java il discorso è un po' diverso. Un programma scritto in Java viene tradotto dal compilatore in un codice macchina che possiamo definire intermedio. Questo codice intermedio (chiamato bytecode) verrà eseguito da un programma speciale chiamato macchina vituale Java o JVM. Perché questa complicazione? Perché l'idea alla base di Java è quella di avere un linguaggio universale ed un codice macchina eseguibile da diversi tipi di processori. In questo modo è sufficiente che il tipo di macchina (PC, micro, ...) per la quale scriviamo il nostro programma disponga della propria JVM affinché il programma stesso funzioni. Questo vuol dire che se ho sviluppato un programma per windows, potrò prendere il bytecode così com'è e farlo funzionare su Linux o su Solaris perché ognuno di loro ha la sua JVM. Negli smartphone invece Java è tradotto, compilato, direttamente in codice macchina.
C'è anche da aggiungere che stanno incominciando a spuntare dei compilatori Java che producono direttamente il codice macchina, chiamato anche codice nativo, per diversi tipi di processore e/o sistemi operativi.
Alcuni linguaggi di alto livello non vengono tradotti da compilatori ma da un altro tipo di programmi detti interpreti. Come un compilatore, un interprete traduce le istruzioni di un programma di alto livello ma, a differenza del compilatore, l'interprete esegue ogni singola porzione di codice subito dopo averla tradotta invece di tradurre tutto il programma in una sola passata. In buona sostanza traduce ed esegue in continuazione. E' chiaro che l'operazione di traduzione si alterna all'esecuzione del programma vero e proprio, mentre con un compilatore lo traduce una sola volta ed il codice oggetto generato può venire utilizzato più volte.
Il risultato è che i programmi interpretati sono eseguiti più lentamente dei programmi compilati. La JVM di Java è appunto un interprete ed è questo il motivo per cui Java risulta essere più lento rispetto ad altri linguaggi.
Il microcontrollore
Se oggi quasi tutti sanno cos'è un calcolatore personale, un PC insomma, e quali sono i suoi campi di applicazione, lo stesso non si può dire per i micro. Infatti molte persone resterebbero assai stupite nello scoprire che dentro il loro spazzolino elettrico c'è un micro!
I microcontrollori sono comparsi nel 1975 (il famoso Intel 8048) e sono figli del microprocessore inventato dall'italiano Federico Faggin alla Fairchild nel 1968, con l'obbiettivo di integrare l'unità centrale di elaborazione in un solo chip (un piccolo rettangolo di silicio che è il cuore di ogni circuito integrato) per ridurre drasticamente i costi e le dimensioni dei calcolatori.
Il micro non è altro che un calcolatore con microprocessore, memorie e dispositivi di ingresso e uscita integrati in un solo chip. Essendo di piccole dimensioni i progettisti elettronici hanno avuto l'opportunità di progettare circuiti piccoli ma con una complessità logica di decine di circuiti integrati tradizionali. Anche in questo caso la riduzione dei costi, come pure l'aumento delle prestazioni, è stata enorme.
Da allora il prezzo dei micro e le loro prestazioni sono aumentate continuamente fino ad arrivare al punto in cui è più conveniente economicamente utilizzare un micro piuttosto che elettronica tradizionale.
Oggi troviamo in commercio micro piccolissimi, con 6 soli pin, che costano praticamente niente! Questo è il motivo per cui ce li ritroviamo anche negli spazzolini: perché è conveniente utilizzarli. La nostra casa, la nostra auto, sono invase da micro. A bordo di un'automobile o in una abitazione se ne potrebbero provare diverse decine ma pochi lo sanno.
Ed è un'invasione silenziosa, invisibile, che migliora la nostra esistenza, un'invasione buona.
La programmazione è progettazione
Un circuito elettronico è un oggetto che deve essere progettato, come pure un macchinario meccanico. Non è pensabile che, per progettare un circuito elettronico si proceda senza uno schema logico, magari mettendo componenti a caso sperando che poi il circuito funzioni. La stessa cosa si può dire per la progettazione di una bicicletta. Non si può pensare di partire prendendo in mano la saldatrice e saldando più o meno a caso dei tubi per formare il telaio.
Quando si deve progettare un circuito si parte da quello che il circuito deve fare, si trova una strategia per ottenere il risultato, si disegna il circuito, si dimensionano opportunamente i componenti calcolandone il valore ed infine si realizza il prototipo.
Lo stesso vale per un programma: prima di tutto si pensa ad una strategia per ottenere il risultato che si vuole ottenere, si dettagliano le diverse parti del programma e poi, alla fine, si scrive il programma. Anche nel caso dei programmi non è pensabile iniziare a progettare iniziando a scrivere con la tastiera perché sarebbe come saldare a casaccio i tubi per fare il telaio di una bicicletta. E come è sbagliato mettere nel telaio un tubo in più solo perché "male non gli fa" è anche sbagliato scrivere istruzioni inutili.
E' mia personale convinzione che per sviluppare un programma bisogna ragionare, analizzare, progettare con meticolosità le varie parti che lo compongono, ed usare la tastiera quando è il momento di scrivere, non prima. Quindi tanto ragionamento e poca tastiera.
Gli errori
Durante un qualsiasi lavoro di progettazione si possono fare degli errori. Nel caso della programmazione gli errori possono essere di tre tipi:
- Errori di sintassi. La sintassi è un insieme di regole rigide per la stesura di un programma da seguire con attenzione. Un errore di sintassi è un errore di grammatica, ad esempio una parola chiave o un operatore sbagliati. Il compilatore rileva l'errore in fase di compilazione e produce un messaggio di errore in modo da trovare nel migliore dei modi il punto dove è stato trovato l'errore. Tuttavia anche un programma privo di errori di sintassi può non funzionare per colpa di altri tipi di errore.
- Errori in fase di funzionamento. Sono anche chiamati errori di run time e sono quelli che si verificano quando il programma è in esecuzione. Un tipico errore di run time è l'inavvertita divisione per zero. Anche in questo caso i linguaggi di alto livello generano delle informazioni per trovare il punto dove questo errore si è verificato, alcune volte le informazioni non consentono di individuare il punto dove si è verificato l'errore. Sono errori subdoli proprio perché vengono rilevati nel momento in cui vengono eseguiti.
- Errori logici. A volte capita che un programma è sintatticamente corretto, non produce errori di run time ma non fa quello che deve fare, o lo fa solo parzialmente. Questi errori sono i più gravi ed è difficile scovarli perché si tratta di errori dovuti ad una progettazione errata del programma.
Il modo migliore per scrivere programmi corretti è quello di fare una buona progettazione del programma e scriverlo solo quando si è sicuri che sia privo di errori. In buona sostanza il modo migliore per eliminare gli errori è quello di non farne.
Nella mia breve esperienza universitaria ho potuto toccare con mano un metodo per evitare gli errori: scrivere le varie parti del programma a mano con carta e matita. E' un esercizio pesante ma potentissimo. Nel momento in cui si riesce a scrivere una porzione di codice a mano, senza errori, che viene compilata correttamente al primo tentativo e che in esecuzione fa quello che deve fare si riduce in modo drammatico la quantità di errori. Secondo il mio parere è un esercizio che andrebbe fatto, anche per rendersi conto che il programma non va "buttato giù" ma deve essere pensato, ragionato e scritto nel modo corretto.
Memorizzazione dei dati
Come vengono memorizzati i dati nella memoria di un calcolatore? La risposta esatta sarebbe "dipende" ma quello che interessa sono i concetti di locazione di memoria e di indirizzo, quindi la risposta è in locazioni di memoria indirizzabili direttamente. Queste locazioni di memoria sono composte da un certo numero di cifre binarie chiamate bit. Ogni bit può assumere solo due valori: 0 e 1. Ci sono calcolatori con locazioni di memoria composte da 8 bit, altri con locazioni composte da 16, 32, 64 e anche più bit.
Una locazione composta da 8 bit si chiama byte. Il byte può avere 256 valori differenti (2^8 = 256). In un byte si può memorizzare, ad esempio, un numero compreso tra 0 e 255, oppure un numero compreso tra -128 e +127, oppure uno dei 256 caratteri del codice ASCII esteso.
Per semplicità è utile immaginare la memoria come un elenco di caselle, di locazioni che possono contenere un byte, dove ogni casella è contrassegnata da un indirizzo che va da 0 (primo casella della memoria) all'ultima disponibile. Tuttavia i linguaggi di alto livello non trattano un unico tipo di dato, non trattano solo byte ma numeri di diverso tipo, strutture dati, sequenze di caratteri (dette stringhe), numeri con virgola mobile in doppia precisione. Ognuno di questi tipi di dati occupa un certo numero di byte ma ogni valore di qualsiasi tipo avrà un indirizzo di partenza per poterlo individuare, leggerne il valore e, se è modificabile, scrivere un nuovo valore.
Un esempio aiuterà a chiarire la cosa. Supponiamo di avere tre valori diversi che chiameremo a,b e c, e che a occupi 2 byte, b ne occupi 4 e c ne occupi uno solo. Supponiamo anche che possono essere memorizzati a partire dall'indirizzo 200. Avremo questa situazione
E, in base a quanto si vede possiamo dire che:
- Il valore "a" occupa 2 byte ed è posto all'indirizzo 200
- Il valore "b" occupa 4 byte ed è posto all'indirizzo 202
- Il valore "c" occupa 1 byte ed è posto all'indirizzo 206
E' importante che ogni locazione di memoria abbia il suo indirizzo perché l'unità centrale deve avere la possibilità di ispezionare o comunque lavorare sul contenuto dei valori. L'indirizzo è anche detto riferimento. Quindi quando si dice che un qualcosa "fa riferimento al valore a" vuol dire che questo qualcosa contiene l'indirizzo della prima locazione dove il valore "a" è memorizzato.
Considerazioni finali
Per quanto riguarda il C suggerisco un libro che si può considerare il testo sacro, è disponibile gratuitamente e si chiama "C - A Reference Manual". In quel libro c'è tutto quello che riguarda il C.
Tutorial sul C se ne possono trovare parecchi in rete e sarebbe bene seguirne uno per avere più informazioni sul linguaggio, conoscere le librerie ed il loro utilizzo riempiendo così le lacune e le omissioni di questo tutorial.
Per quanto riguarda il Java suggerisco il libro di Walter Savitch - Programmazione di base ed avanzata in Java. E' scritto molto bene, con uno stile molto comprensibile ed è anche manuale di riferimento. E' in formato caratceo e costicchia ma, con un po' di buona volontà, forse si riesce a trovare in formato pdf.
Sempre per il Java il programma Jelliot. Si tratta di un programma che permette di vedere graficamente, mediante finestre animate, cosa fa il calcolatore quando esegue un programma in Java. Dire che è bellissimo ed utilissimo è dire poco!