Materia:Programmazione
Da Wikiversità, l'università aperta.
Questa materia è incompleta
Tutti i contributi sono ben accetti perché sono state scritte poche (o nessuna) lezioni di questa materia.
Questa pagina contiene il testo delle lezioni legate alla materia generale, per una più agevole lettura necessita di essere divisa in lezioni.
Per una lista completa delle materie da suddividere in lezioni, consulta la relativa categoria.
| Programmazione | ||
|
SSD = INF/01 |
||
|
Presentazione Questo "corso" di programmazione vuole essere inteso come un'opera che insegna i principi base della programmazione, non seguendo un linguaggio specifico, ma dandone esempio con i principali linguaggi (Java,C,Php,Pascal). Pertanto potrà essere utilizzato da persone che vogliono avvicinarsi alla programmazione rimanendo indipendente dal linguaggio o comunque permetterà a chi non conosce la programmazione di capire i misteri di questa raffinata arte...
ATTENZIONE: Alla fine del "corso" non si avrà una piena conoscenza dei linguaggi di programmazione ma si avrà un'ottima base per poter studiare qualsiasi tipo di linguaggio di programmazione. Programmare o meglio ancora creare un programma vuol dire creare una serie di istruzioni in un determinato linguaggio per istruire il computer per la soluzione di un determinato problema. In pratica noi "diciamo" al computer cosa fare ma soprattutto cosa fare per noi. Per dare un esempio concreto alla cosa, si pensi ad un navigatore satellitare; il luogo di partenza sono i nostri dati iniziali di cui disponiamo, il luogo di arrivo è il risultato finale, la programmazione è il tragitto. Quest'ultimo è la metafora ideale perché il tragitto tra due luoghi può essere lungo o breve come in programmazione un codice può essere scritto bene o male per ottenere lo stesso risultato, il tragitto può presentare degli imprevisti come un programma all'apparenza semplice può presentare un problema al quale non avevamo pensato, ed infine, come non sempre un tragitto corto è sinonimo di velocità di arrivo tanto meno un codice lungo vuol dire un codice ben scritto. |
Programma
|
|
Risorse |
Verifiche d'apprendimento È possibile, e fortemente consigliato, integrare le lezioni e valutare la propria preparazione attraverso queste esercitazioni. È possibile verificare la conoscenza di un argomento specifico o dell'intero programma.
|
|
Utenti interessati |
Architettura dell'elaboratore
Esula dallo scopo di questo corso una descrizione dettagliata dell'architettura dell'elaboratore, ma è utile fare una brevissima introduzione. Un elaboratore nella forma originariamente descritta da Von Newman è composto da due elementi principali, l'unità di elaborazione (CPU) e la memoria principale (RAM), con la CPU che preleva dalla memoria principale le istruzion ed i dati da elaborare e salva sulla stessa i risultati.
Facendo una semplificazione che non fa certamente onore alla complessità di questo elemento, la CPU è una lunga catena di circuiti logici che riceve in ingresso dei segnali elettrici e sulla base di questi modifica i segnali all'uscita.
Codice sorgente e codice oggetto
Un programma è una serie di istruzioni in codice binario che vengono eseguite dalla CPU. Si può pensare come una lunga catena di 1 e 0 (i segnali elettrici) che attivano all'interno della CPU i corretti circuiti logici che effettueranno i calcoli e le operazioni necessarie. Ogni operazione è richiamabile da una precisa sequenza di 1 e 0. L'insieme delle combinazioni che identificano tutte le operazioni possibili è denominato API, mentre il codice che viene eseguito è definito codice macchina. Ogni CPU ovviamente ha un'API diversa da tutte le altre architetture, e pertanto un codice macchina creato per una determinata architettura non può funzionare su altre.
Inizialmente i programmatori erano costretti a definire il programma scrivendo direttamente il codice macchina, ovvero precisamente le combinazioni corrette di 1 e 0 che la CPU doveva eseguire. Come è facile immaginare non era un lavoro facile e la probabilità di commettere errori alta. Col tempo sono stati introdotti strumenti sempre più complessi per facilitare lo sviluppo delle applicazioni, consentendo la scrittura di codice più comprensibile per gli uomini, poi codificato in linguaggio macchina da altri programmi. Questi ultimi sono i compilatori e verranno trattati nel prossimo capitolo.
Esiste quindi una differenza sostanziale tra quello che il programmatore scrive, chiamato codice sorgente, e quello che effettivamente è l'eseguibile, ovvero le istruzioni binarie frutto della compilazione, chiamato anche codice binario o oggetto. Il codice sorgente è scritto in un linguaggio, detto appunto linguaggio di programmazione, ed è indipendente dal linguaggio macchina. Sarà infatti il compilatore ad assumersi il compito di tradurre il sorgente in codice binario seguendo le API dell'architettura su cui il programma dovrà poi essere eseguito.
L'evoluzione dei linguaggi di programmazione, unita all'incremento della potenza di calcolo, ha visto la nascita anche di linguaggi che non sono compilati ma interpretati. Un esempio è il ben noto javascript, utilizzato nei siti web. Questi linguaggi di programmazione sono letti da un programma, detto interprete, che esegue le operazioni previste. Intuitivamente svolge a livello software quello che fa la cpu a livello hardware. Le istruzioni attivano le varie funzioni dell'interprete che eseguirà poi il lavoro vero e proprio. Si aggiunge quindi un ulteriore livello di astrazione, che porta molti vantaggi tra cui la portabilità del programma, ma anche lo svantaggio di avere minori prestazioni.
Cosa significa programmare
La parola programmare ha un significato molto diverso da quelli che pensano alcuni che si credono programmatori. Programmare non significa scaricare da internet un programma che ti cambia l'aspetto grafico del pc o incrementa le prestazioni. Quando programmi, il programma te lo fai tu e solo tu, non usi programmi degli altri (eccetto ovviamente compilatore, linker, ecc.).
Programmi necessari
Compilatore
Un compilatore è il programma base per compilare. Questo scorre il codice sorgente e lo traduce nel corrispondente binario per l'architettura su cui questo dovrà essere eseguito.
Spesso la compilazione non è una traduzione "letterale" ma quanto scritto dal programmatore è verificato e dove possibile ottimizzato.
Il primo controllo eseguito dal compilatore è quello sintattico. Ovvero come ogni linguaggio anche quello di programmazione ha le sue precise regole. Il controllo sintattico non fa altro che verificare se queste regole sono state rispettate e restituisce un errore se così non fosse. Un secondo controllo che il compilatore fa è quello semantico, ovvero controlla che le istruzioni siano coerenti tra loro. Ad esempio se un'operazione richiede due numeri interi e gliene viene dato solo uno il compilatore si fermerà generando un errore. Alcuni compilatori sono in grado di trovare anche errori logici, ad esempio un ciclo infinito di istruzioni che non termina mai. Ai compilatori inoltre è affidato l'arduo compito di ottimizzare il codice binario risultante, a volte anche sopperendo ad una programmazione non proprio attenta. Spesso questo avviene eliminando istruzioni inutili, ignorando codice che non potrà mai essere eseguito o altro ancora. Ma soprattutto l'ottimizzazione avviene sfruttando al massimo le opportunità offerte dall'architettura di destinazione. Il compilatore quindi può essere determinante per la velocità finale di un programma. Pur non potendo sostituire l'uso di tecniche di programmazione efficaci, l'impatto del compilatore sulle prestazioni può essere sensibile. Tuttavia una compilazione troppo spinta che sfrutti euristiche ancora non ben testate può comportare la creazione di un codice binario errato, che andrà in errore anche se non per colpa del programmatore.
Non necessariamente il compilatore crea un binario che può essere eseguito da solo. Capita molto spesso infatti che siano compilate delle librerie di codice e non un programma vero e proprio. Tali librerie non sono altro che un insieme di funzioni, anche molto generiche, che sono poi riutilizzate da altri programmi. Il sorgente di una libreria viene quindi compilato in una maniera particolare per cui il risultato è un file binario che dovrà poi essere collegato al programma vero e proprio che ne vorrà utilizzare le funzionalità. Quest'ultimo procedimento viene eseguito all'avvio del programma dal Linker.
Linker
Il linker è il programma che si occupa di collegare l'eseguibile alle librerie da questo usate. Quest'operazione può essere eseguita in due tempi diversi, in fase di compilazione oppure all'avvio del programma. Nel primo caso si parlerà di link statico, nel secondo di link dinamico.
Link statico
Il link statico delle librerie avviene direttamente in fase di compilazione. Linker più semplici inglobano nell'eseguibile finale l'intera libreria, mentre quelli più avanzati solo la parte di codice realmente usata, evitando quindi inutile spreco di spazio e riducendo i tempi di avvio del programma e la quantità di memoria principale usata.
Il vantaggio del link statico sta nella facilità di distribuzione dell'eseguibile. Dal momento che tutte le funzioni usate sono già incluse in esso è sufficiente distribuire il solo eseguibile, senza preoccuparsi di eventuali dipendenze o conflitti tra librerie. Tuttavia vi sono molti aspetti tecnici che lo rendono poco preferibile. Questo concetto si capirà meglio affrontando il link dinamico.
Link dinamico
Il link dinamico viene eseguito dal linker al momento del caricamento del programma in memoria. In questo caso l'eseguibile è compilato in modo tale da contenere richiami a librerie esterne che il linker dovrà trovare e collegare prima di poter eseguire il programma. Se sul sistema in cui si tenta di eseguire il programma manca una di queste librerie verrà generato un errore. Un errore può essere generato anche nel caso in cui la libreria installata non contenga la funzione richiesta. Questo potrebbe avvenire se non è della versione attesa. Se precedente, infatti, la funzione usata potrebbe non essere ancora stata implementata, mentre se è una versione sucessiva la funzione usata potrebbe essere stata rimossa. Com'è comprensibile l'uso del link dinamico può comportare non pochi problemi di distribuzione del programma proprio per via delle dipendenze con le varie librerie o i conflitti che si possono generare. Anche le librerie, infatti, possono fare riferimento ad altre librerie, generando così una catena di dipendenze anche molto lunga. Si tratta, tuttavia, di problemi quasi sempre superabili con un'attenta configurazione del sistema. Pur con questi inconvenienti il link dinamico è usato molto spesso perché offre moltissimi vantaggi, tali da compensare i disagi sopra esposti. Questi sono:
- Condivisione delle librerie. Se una libreria è usata da molti programmi il link dinamico permette di installare sul sistema la libreria una sola volta. Non solo, può essere caricata nella memoria principale un'unica volta. Ipotizzando che dieci programmi usino una libreria grande 10 MB il link dinamico fa si che questa sia caricata una sola volta e che quindi occupi solo 10 MB di ram. Diversamente, se la libreria fosse compilata in maniera statica, lo stesso codice sarebbe ripetuto dieci volte nella memoria, occupando 100 MB.
- Minore dimensione dell'eseguibile. Mancando il codice incluso nelle librerie il file eseguibile è più piccolo. Questo comporta un risparmio di spazio a volte consistente, favorendone la distribuzione.
- Migliora la mantenibilità. Se una libreria è stata aggiornata è sufficiente sostituire solo quella per far si che tutti i programmi che l'usano si giovino delle modifiche. Diversamente il link statico obbligherebbe a ricompilare ed aggiornare tutti i programmi. Se la libreria è molto usata questo può richiedere un'operazione molto laboriosa. A titolo d'esempio si può ricordare la scoperta in passato, quasi contemporanea, di un bug all'interno di due librerie molto usate per la visualizzazione di immagini che comportavano seri problemi di sicurezza. La prima, usata su di un sistema in cui i programmi sono compilati in maniera statica, impose l'aggiornamento di numerosi programmi, dal browser e la posta elettronica ai programmi di videoscrittura e di foglio elettronico. La seconda, usata su un sistema in cui i programmi sono tipicamente compilati in maniera dinamica, fu sufficiente modificare la libreria di pochi kb per risolvere definitivamente ed in maniera sicura il problema. Nel primo caso, infatti, l'unico modo che gli utenti avevano per essere sicuri era quello di non usare i programmi incriminati fino a che non fosse disponibile la versione aggiornata. Ma trattandosi di una funsionalità così diffusa era anche difficile per un utente rendersi conto di quali programmi fossero o meno a rischio.
IDE
L'IDE non è necessario in tutti i linguaggi, ma è sempre consigliato averne uno. Esso aiuta la scrittura del codice, ad esempio colorando le parole in modo diverso, sottolineandole o mettendole in grassetto. E' buona norma averne uno, ma esso non deve avere troppe funzionalità, altrimenti "sporcherebbe" il codice, rallentando l'esecuzione del programma
Concetti base
Algoritmo
L'algoritmo è l'insieme delle istruzioni necessarie per eseguire un determinato compito.
Compilazione
La compilazione è il processo di trasformazione dei che contengono il codice scritto dal programmatore (detti "sorgenti") nel codice eseguibile finale.
Ciò passerò attraverso 4 fasi, più una usata per linguaggi per cui non esistono compilatori diretti:
- Conversione del codice dal linguaggio di programmazione usato a quello per cui esiste il compilatore (accade, ad esempio, per il linguaggio di programmazione FORTRAN, ove tutti i compilatori traducono i codici sorgenti FORTRAN in codici sorgenti C, data l'estinzione di compilatori diretti);
- Eliminazione delle righe di commento e conversione delle macro e dei valori simbolici in numeri calcolabili;
- Trasformazione del codice sorgente in codice Assembly, con dettagliate istruzioni di come agire passo passo per eseguire le funzionalità del programma;
- Trasformazione del codice Assembly in codice oggetto, ove vi sarà bit per bit quanto il programma deve eseguire;
- Trasformazione del codice oggetto in file eseguibile (con estensione .exe in ambiente Windows), che permette l'esecuzione del programma con l'avvio del file.
La fase della creazione del codice oggetto può prevedere anche l'incorporamento di file di libreria o di moduli del codice legati al file di codice sorgente principale attraverso dei richiami inseriti nei file (o attraverso la creazione di un file di progetto che indichi all'atto della compilazione tutti i file da compilare).
Linguaggi di programmazione
Paradigmi
Scelta del linguaggio
Scelta degli strumenti e dei linguaggi per il corso
Scegliere il linguaggio di programmazione per un corso di programmazione non è cosa facile ed esistono molte scuole di pensiero in materia. Nel tentativo di dare la maggior panoramica possibile sia per quanto riguarda i diversi paradigmi sia per quanto riguarda le diverse scelte a livello concettuale la scelta ricade sui seguenti:
- C/C++
- Java
- Phyton
Concetti base di programmazione
In questa parte del corso verranno presi in esame concetti basilari della programmazione. Concetti che si possono incontrare in tutti i linguaggi di programmazione imperativa e ad oggetti. Alcuni di questi concetti, invece, non sono usati nella programmazione funzionale, che però sarà trattata più esaurientemente più avanti.
Hello World
L'Hello World è tipicamente il primo programma che viene insegnato a scrivere agli aspiranti programmatori; esso è solitamente semplice nella scrittura, ma usa già diverse strutture del linguaggio di programmazio0ne preso in oggetto.
Il programma esegue semplicemente la stampa a video su una finestra di shell (su Windows, di MS-DOS) della scritta Hello World, rimanendo in quello stato fino a quando l'utente non chiuderà la finestra relativa al programma (o non terminerà l'esecuzione dello stesso, se eseguito all'interno di un ambiente di shell).
In C, esso si scrive:
#include<stdio.h>
{
printf ("Hello World");
system pause;
return 0;
}
La riga
#include<stdio.h>
serve ad indicare che vanno caricati i valori ed i comandi relativi alla libreria stdio.h, senza i quali il programma non può nemmeno partire.
La parentesi graffa aperta indica l'inzio del codice che comporrà il programma.
La riga
printf ("Hello World");
serve ad indicare al calcolatore che dovrà visualizzare su schermo la scritta riportata fra le parentesi e le virgolette, in questo caso Hello World; il punto e virgola indica la fine della riga di codice, e se omesso porta alla non compilazione dello stesso.
La riga
system pause;
che non funziona su tutti i sistemi operativi per il quale il linguaggio C è scrivibile, indica che il sistema operativo deve mettere in pausa il programma in esecuzione fino alla pressione di un tasto qualsiasi.
La riga
return 0;
indica di ritornare al sistema operativo il valore zero e di terminare il programma (zero è un valore che indica la corretta esecuzione del programma, valori diversi da zero indicano un'interruzione improvvisa dello stesso.
La parentesi graffa chiusa indica la fine delle righe di codice che compongono il programma.
Variabili ed operazioni matematiche
Ogni linguaggio di programmazione deve essere in grado di manipolare e conservare dati: la natura (o il tipo) di tali dati dipende dal linguaggio.
Tipi di dati comuni a molti linguaggi sono: tipo carattere, tipo stringa, tipo numerico intero, tipo numerico decimale ecc.
I linguaggi di programmazione danno la possibilità di fare riferimento all'area di memoria nello spazio degli indirizzi riservato al programma, nella quale è memorizzato il valore di un dato: il programmatore può quindi utilizzare nomi logici (ad esempio: stipendio, imponibile, aliquota, cognome ecc.) per fare riferimento alle aree di memoria nelle quali sono memorizzati i valori di questi dati.
Ognuno di questi nomi logici prende il nome di variabile.
Definizione: una variabile in un programma è un nome logico che individua l'area di memoria nella quale è ospitato un dato.
I dati da solo sarebbero poco utili se non vi fosse la possibilità di manipolarli in qualche modo.
Anche le operazioni ammesse sui dati e la loro sintassi possono variare da linguaggio a linguaggio.
Comparazioni, operazioni logiche semplici ed istruzioni di salto
Cicli
I/O base
Array
Funzioni
Overloading di funzioni
Classi, Oggetti, Istanze
Creazione ed uso di librerie esterne
Concetti intermedi
Ereditarietà
Polimorfismo
Passaggio dati per riferimento
Operazioni bitwise
Concetti avanzati
I concetti che verranno di seguito esposti sono considerati avanzati non tanto per la difficoltà che essi comportano, anche se in alcuni casi superiore ad altri argomenti visti precedentemente, quanto per la minore diffusione ed uso di questi strumenti. Di fatto si tratta di funzionalità usate solo nella programmazione con C o C++, ma che può essere interessante ed utile esaminare.
Puntatori
Costruttori, Distruttori, Copy contructor
Un costruttore può essere considerato un metodo che i clients di una classe sono costretti ad invocare una ed una sola volta, in particolare subito dopo l'istanziazione di un nuovo oggetto. I costruttori vengono tipicamente usati come metodi di inizializzazione.
Un distruttore può essere considerato un metodo chiamato automaticamente su di un dato oggetto una ed una sola volta, in particolare subito prima che esso venga distrutto (perché non più utilizzato). I distruttori vengono tipicamente utilizzati per rilasciare risorse precedentemente utilizzate dall'oggetto e che, altrimenti, resterebbero allocate, con un conseguente spreco di risorse da parte del sistema.
Ereditarietà multipla
Overloading degli operatori
Strutture dati
Stack
Code
Liste
Alberi binari
Grafi
Programmazione parallela
Creare thread
Un thread è un insieme di operazioni eseguite parallelamente rispetto ad altre. Tramite l'uso dei threads, un programma può fare più cose contemporaneamente, velocizzando la propria esecuzione.

