Programmazione e algoritmi
Un algoritmo è una sequenza di operazioni più o meno elementari che devono essere eseguite al fine di risolvere un problema. Il primo algoritmo conosciuto è quello di Euclide scritto nel 300 a.C. ed è utile a calcolare il massimo comune divisore tra due numeri. Pertanto avremo come "input" due numeri x e y, mentre in output avremo MCD(x,y).
Passaggi per calcolare MCD
- Leggi x, y
- Calcola r <- Mod(x, y) // mod(x, y) rappresenta il resto della divisione
- Se r != 0 allora x <- y, y <- z, torna a 2
- Scrivi y
y sarà quindi l'MCD tra i due numeri.
Definizioni di algoritmo e programma
- Algoritmo: Sequenza ordinata di passi eseguibili e non ambigui che determina un procedimento finito.
- Programma: Formulazione in un linguaggio appropriato di un algoritmo al fine di renderlo immediatamente comprensibile ed eseguibile da un esecutore (non necessariamente una macchina).
Il modello di Von Neumann
Innanzitutto bisogna precisare che vi sono moltissime variazioni di architettura, l'hardware può essere alquanto differente tra un calcolatore ed un altro. Tuttavia, ad alto livello, ogni calcolatore fa riferimento a un modello particolare creato nel 1950 dal fisico John Von Neumann. Quindi oggi ogni calcolatore è basato su questa idea.
Per Von Neumann una macchina calcolatrice è costituita da tre componenti fondamentali: un dispositivo che funge da unità di calcolo centrale (CPU), la memoria centrale del calcolatore (memoria RAM) e infine l'ultima componente principale è il bus di sistema, ossia i canali che permettono di comunicare i dispositivi interni all'elaboratore.
Inoltre, la macchina di Von Neumann, quando termina la sua esecuzione, viene riportata allo stato originario (infatti non vi sono memorie di massa utili a salvare modifiche effettuate durante l'esecuzione).
Bisogna notare che al Bus si collegano altri dispositivi: le periferiche. Le periferiche sono altri dispositivi che interagiscono con la macchina di Von Neumann e che, al fine di ottenere ciò, necessitano di utilizzare il Bus. Periferiche sono tutti i dispositivi che vengono aggiunti al sistema di calcolo di Von Neumann, ad esempio la tastiera, un HDD, ...
Pensandoci si può affermare che in qualsiasi elaboratore ci sono le tre componenti fondamentali (CPU, memoria e bus).
La CPU
La CPU, detta anche processore, è il cuore del calcolatore. È colui che esegue i programmi, il nostro elaboratore. La CPU esegue passo dopo passo gli algoritmi scritti in un linguaggio a lei comprensibile.
Un parametro fondamentale per classificare una CPU è la velocità di calcolo, misurata in Hertz. La velocità naturalmente è la frequenza di clock. Con un hertz possiamo dire che il processore esegue un'operazione anche se ciò è inesatto. I processori odierni hanno processori con frequenza di clock nell'ordine dei GHz (1GHz = un miliardo di operazioni al secondo).
La velocità di una CPU non è basata solo sulla frequenza di clock, ma anche dal grado di parallelismo ad esempio. Un quad-core da 1.5GHz è migliore di un monocore da 3GHz.
La CPU è divisa in due parti: ALU e CU. L'Arithmetical Logic Unit è utilizzata per le operazioni aritmetiche e logiche, mentre la Control Unit è responsabile del ciclo macchina, ossia è la parte che scandisce il tempo tra le varie fasi del ciclo macchina (spiegato in seguito).
Inoltre, nella CPU vi sono i registri, memorie ad alta velocità, ma bassa capacità. I registri sono "banchi di lavoro" in cui vengono salvati i dati cui la CPU opera (per esempio numeri che devono essere sommati, confrontati, ... a seconda delle esigenze del programma). Il risultato delle operazioni tra numeri contenuti nei registri viene salvato in memoria, non nei registri. È importante considerare che terminato il programma i registri non vengono svuotati.
La memoria centrale
RAM è acronimo di Random Access Memory. Qui risiedono tutti i dati che sono utili alla CPU per poter funzionare. Il sistema operativo stesso risiede nella memoria RAM quando il computer è acceso. Genericamente si può affermare che in RAM ci sono tutti i dati e i programmi utili per essere utilizzati dalla CPU.
Internamente la memoria RAM è costituita da celle. Ognuna di queste è detta cella di memoria. Ad ognuna di queste è associato un indirizzo, ossia un numero utile per ritrovare un dato all'interno di una cella. In ultima analisi si può dire che la memoria RAM è una sequenza di celle. All'interno di ogni cella vi è una stringa di bit, utili a rappresentare una determinata informazione. Il numero di bit contenuti in ogni cella è costante. L'informazione viene richiesta dalla CPU mediante l'indirizzo. Sempre mediante un indirizzo la CPU scrive informazioni in memoria RAM in una determinata posizione.
Viene detta "ad accesso casuale" perché la velocità di prelievo e scarico dei dati è sempre costante indipendentemente dall'indirizzo. In particolare, se prelevare un'informazione in una posizione p1 impiego un certo tempo T, per prelevare invece un'informazione in un'altra posizione p2 impiegherò sempre lo stesso tempo T. Di una memoria RAM viene considerata la velocità e la capacità, ossia la quantità di dati caricabili in essa. La capacità viene misurata in byte. Su un computer standard si possono trovare 2GB di RAM, in computer più professionali se ne possono trovare 8GB o più. La memoria RAM è espansibile finché la CPU è capace di indirizzarla.
Supponiamo di avere una memoria RAM con capacità di 256 MB, e che sia divisa in celle costituite da 4 byte. Si vuole sapere qual è l'indirizzo più alto delle celle.
Innanzitutto devo considerare quante celle vi sono in memoria, pertanto (256*220)/4 ossia (64*220) tuttavia questo non è il massimo indirizzo perché al numero delle celle devo togliere 1 in quanto si parte a contare da 0.
La memoria RAM è volatile, poiché quando viene spento l'elaboratore le informazioni contenute in essa vengono perse. Diversamente, le memorie di massa mantengono le informazioni anche quando il computer viene spento (ciò è possibile, ad esempio, mediante magnetizzazione). Tipicamente le memorie di massa sono più lente di una memoria RAM.
Il bus
È un dispositivo elettronico utile per permettere il passaggio delle informazioni tra le varie componenti del computer. Funge quindi da canale di comunicazione. Semplicemente si può asserire che siano fili. Un parametro importante del bus è la velocità di trasferimento dei dati, misurata in bit/sec.
Esecuzione dei programmi nella macchina di Von Neumann
Affinché possa essere eseguito, il programma viene caricato istruzione dopo istruzione nelle celle della memoria RAM. In particolare, le istruzioni devono essere caricate in maniera ordinata.
Una volta che il programma è stato caricato completamente in RAM, parte l'esecuzione, in particolare la CPU preleva un'istruzione, la decodifica, la esegue e preleva l'istruzione successiva. Questo ciclo, detto ciclo macchina, avviene finché tutte le istruzioni non sono state eseguite. Le tre fasi del ciclo sono chiamate:
- Fetch (prelievo)
- Decode
- Execute
Al fine di comunicare con la CPU, dobbiamo scrivere le istruzioni in una maniera a lei comprensibile, mediante un linguaggio di programmazione. Chiaramente le istruzioni contenute in RAM sono codificate con stringhe di bit. Quindi dire che un linguaggio di programmazione è un linguaggio comprensibile alla CPU non è esatto, poiché, essendo la CPU un dispositivo digitale, comprende solo il linguaggio binario (o linguaggio macchina).
Naturalmente la praticità nello scrivere un programma in linguaggio macchina è inesistente, per questo esistono linguaggi di programmazione ad alto livello che ci permettono di comunicare con la CPU. Il linguaggio ad alto livello, ovviamente, viene trasformato poi in linguaggio binario per essere eseguito dal processore.
Il linguaggio macchina è detto linguaggio a basso livello, mentre altri linguaggi, come Java, C, Python, ... sono detti linguaggi ad alto livello.
Categorie di istruzioni in un linguaggio macchina
- Operazioni di load, o di lettura (da RAM a CPU)
- Operazioni di store, da CPU a RAM
- Operazioni aritmetiche (somme, sottrazioni, ...)
- Operazioni di salto, jump
Linguaggio Assembly
All'inizio degli anni '60 sono stati introdotti i linguaggi assembly, che sono un piccolo passo avanti rispetto al linguaggio macchina che era necessario usare negli anni precedenti. L'innovazione nell'utilizzo del linguaggio assembly è chiara: il programmatore non deve usare una sequenza di 0 e 1 ma può scrivere istruzioni come load, mov, ...
Ogni linguaggio assembly è dipendente dal processore utilizzato, si potrebbe dire che sono tutti diversi, ma in realtà si somigliano. Logicamente il processore capisce solo il linguaggio macchina e non assembly, pertanto serve qualcosa per tradurre da assembly a linguaggio binario.
La programmazione mediante assembly o linguaggio macchina è detta programmazione a basso livello, perché bisogna stare molto conformati all'architettura dell'elaboratore. Pertanto bisogna porsi a livello dell'esecutore.
Costruire programmi in assembly presenta diversi svantaggi:
- Per il programmatore: per via della macchinosità nello scrivere le istruzioni e per via del fatto che questo stile di programmazione ci costringe a cambiare il nostro naturale modo di pensare.
- Non c'è portabilità del software: un programma scritto in un determinato linguaggio assembly per un determinato esecutore probabilmente lo stesso programma portato su un'altra CPU non funziona.
- Utilizzare questo tipo di linguaggi obbliga al programmatore la conoscenza dell'architettura dell'elaboratore.
Per via di questi problemi si è pensata la creazione di linguaggi di programmazione ad alto livello.
Programmazione ad alto livello
I linguaggi di programmazione ad alto livello permettono un più alto livello di astrazione, non bisogna infatti conoscere la macchina per poter creare un algoritmo comprensibile ad essa. I linguaggi ad alto livello nascono nel 1970. Java nasce negli anni novanta. Esempi di linguaggi di programmazione ad alto livello sono: C, Pascal, BASIC, Python, ...
Questi linguaggi sono nettamente più comprensibili all'essere umano. Mentre utilizzando il linguaggio macchina il programmatore parla direttamente all'elaboratore, utilizzando un linguaggio ad alto livello si parla ad un "interprete" che traduce il linguaggio ad alto livello in linguaggio macchina.
Tale interprete è detto compilatore. Il compilatore è diverso per ogni linguaggio di programmazione (compilatore C, compilatore Pascal, ...). Questi compilatori evitano quindi al programmatore il compito di trasformare i propri algoritmi in linguaggio macchina.
Solitamente il programma scritto in linguaggio ad alto livello che deve ancora essere compilato è detto sorgente, mentre il programma trasformato in maniera comprensibile dall'elaboratore è detto oggetto.
Prendendo in esame un particolare linguaggio di programmazione ad alto livello, esso avrà un sorgente uguale per qualunque macchina e un compilatore diverso per ogni sistema operativo (es. sorgente programma Java, compilatore Java Windows, compilatore Java Linux). Questo almeno in teoria, perché, ad esempio, si possono usare particolari istruzioni relative a un determinato O.S. Però è trascurabile come cosa. Approssimativamente il 90% dei programmi non vanno modificati in alcun modo nel portarlo da un O.S. all'altro.
Bisogna specificare che anche assembly ha il suo compilatore: l'assemblatore.
C'è un altro modo per eseguire i programmi ad alto livello, qui non viene usato un compilatore, ma un interprete. Mentre il compilatore in prima approssimazione produce linguaggio macchina eseguibile, l'interprete interpreta passo passo le istruzioni. Una sorta di macchina virtuale che comprende le istruzioni scritte ad alto livello senza traduzione. Per molti linguaggi di programmazione ad alto livello si può scegliere se compilarlo o interpretarlo. Quindi l'interpretazione non è un passo di generazione di un file eseguibile, ma è l'esecuzione stessa, senza traduzioni.
Un linguaggio interpretato tipicamente è molto più lento che un linguaggio compilato perché nel primo la macchina deve passo passo interpretare ed eseguire il programma, mentre con la compilazione il programma è già eseguibile.
L'interpretazione invece risulta vantaggiosa nella scrittura dei programmi: infatti mediante interpretazione si può subito risalire alla linea in cui è presente l'errore, infatti in caso di errore, l'interprete si "inchioda".
Java è un linguaggio interpretato, quindi a livello di prestazioni è più scomodo rispetto ad altri linguaggi quali il C e C++.
Il ciclo di vita del software
Rappresenta i passaggi dalla scrittura all'esecuzione.
- Progetto: i programmi prima devono essere pensati, ossia deve essere individuato l'algoritmo utile per risolvere il problema.
- Editing: l'algoritmo viene scritto nella sintassi imposta dal linguaggio di programmazione scelto. Qui viene scritto il sorgente.
- Compilazione: il programma viene tradotto per poter essere eseguito.
- Linking: se il problema da risolvere è molto complesso, è tipico che il problema sia stato suddiviso in moduli (parti del programma), ad ogni modulo ci lavora un team diverso. Ci sono quindi programmi detti "linker" che assemblano i vari moduli e generano il sorgente finale.
- Esecuzione: il programma viene messo in memoria centrale ed eseguito.
Errori
Tralasciando gli errori nella digitazione e nella progettazione del software, in fase di editing vi possono essere errori, tipicamente questi errori sono quelli di sintassi (errori come non mettere il ; dopo l'istruzione), questi errori li si trova in fase di compilazione. Ci possono inoltre essere errori in fase di linking (difficoltà nel ritrovare informazioni in moduli diversi per via di errori nei riferimenti).
Anche durante l'esecuzione del programma vi possono essere errori, detti errori "runtime", questi errori avvengono, ad esempio, nel caso in cui vi sono errori di logica nel programma, i famosi bug del software (non considerare la divisione per zero in un programma di divisioni).
Strumenti necessari per la progettazione e il funzionamento di un programma Java
Java necessità sia di interpretazione che di compilazione: una volta scritto il programma, il programma passa attraverso una prima fase di compilazione, la compilazione produce un linguaggio simile al linguaggio macchina detto byte code, che non può essere eseguito, ma può essere interpretato da un componente software detto JVM (Java Virtual Machine). Quindi va installato sia il compilatore che la JVM.
Si è scelto questo approccio perché si è voluto estendere la portabilità non solo del sorgente, ma anche del byte code. Il byte code è sempre lo stesso, indipendentemente dal sistema operativo. Mentre le JVM sono diverse per ogni o.s.
Questo è il concetto della doppia portabilità: sia il codice sorgente che il byte code sono invariati indipendentemente dal sistema operativo usato durante la compilazione e la scrittura del programma. Si è voluto estendere la portabilità del byte code perché avere delle applicazioni direttamente eseguibili sono utili, ad esempio, nell'esecuzione di applicazioni da pagine web.
In HTML infatti vi sono delle applet, il byte code di applicazioni Java. Quando viene cliccato il link, la JVM (integrata nel browser) interpreta il byte code ed esegue l'applicazione.
Passi per la programmazione in Java
- Procurarsi gli strumenti: installare il compilatore Java e la JVM, in seguito bisogna settare le variabili d'ambiente.
- Scrivere il codice sorgente: si produce il "listato Java". Il sorgente, scritto con qualunque text editor, deve essere salvato in formato ".java".
- Compilare il sorgente: verrà prodotto un file contente il byte code in formato ".class". Per attivare la compilazione, a linea di comando tramite terminale, bisogna scrivere "javac <percorso/nome_sorgente.java>".
- Eseguire il byte code: mediante la JVM si esegue il byte code. Sempre mediante terminale, bisogna scrivere: "java <percorso/nome_sorgente>". Notare che qui non bisogna scrivere l'estensione del file byte code, se lo si fa dà errore.
Costrutti fondamentali dei linguaggi di programmazione ad alto livello
Qualunque linguaggio ad alto livello uno decida di utilizzare, troverà concetti comuni su cui i linguaggi di programmazione si basano, ossia ci sono regole di base universali.
- Sequenza: si scrive una lista di istruzioni che saranno eseguite...
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
Scarica il documento per vederlo tutto.
-
Programmazione java
-
Programmazione Java
-
Programmazione - programmazione Java 2
-
Programmazione - programmazione Java 1