Sei sulla pagina 1di 63

Programmare e Progettare

con Java
Lezione 0 - Introduzione alla Programmazione
ALGORITMI
ALGORITMI… MA CHE SIETE? (1)
Il termine algoritmo ha diverse definizioni:

★ Generica: “Procedimento per la risoluzione di un problema.”


★ Informatica: “Procedimento computazionale.”
★ Formale: “Insieme ORDINATO e FINITO di PASSI ESEGUIBILI e NON
AMBIGUI che definiscono un PROCESSO CHE TERMINA.”

Dato un insieme di istanze DI e un insieme di soluzioni DS, l’algoritmo a è una


funzione fa:DI→DS che trasforma gli elementi del dominio in opportuni elementi del
codominio.
ALGORITMI… MA CHE SIETE? (2)
Dato un problema P, si definiscono:

➢ Istanza: x∈DI
➢ Soluzione: f(x)∈DS

Si dice che un algoritmo a risolve il problema P se ∀x∈DI, fa(x)=f(x).

ES - Moltiplicazione di z interi.

DI= NxN (coppie di interi) Istanza: (x,y)∈NxN

DS= N (intero) Soluzione: x*y∈N


DALL’ALGORITMO AL PROGRAMMA
L’algoritmo è quindi un procedimento di risoluzione di un problema e può essere
espresso in molti modi.

Un programma è un’espressione dell’algoritmo in un linguaggio comprensibile da


colui che lo esegue: l’elaboratore! Esso è un insieme FINITO e ORDINATO di
istruzioni scritte secondo le regole di un linguaggio di programmazione. Tali regole
possono essere:

● Sintattiche: come metto insieme una frase?


● Semantiche: che senso hanno le parole?
DIAGRAMMI DI FLUSSO (1)
Un diagramma di flusso è un linguaggio di modellazione di tipo grafico che
permette di rappresentare le operazioni effettuate da un algoritmo e la sequenza
con cui queste vengono effettuate.

Come vedremo, UML farà molto di più… ma per ora restiamo con i piedi ben saldi
per terra.
DIAGRAMMI DI FLUSSO (2)
Esistono molteplici SW per la realizzazione dei flowcharts, eccone un paio:

★ draw.io: Web Application gratuita.


★ StarUML: SW disponibile per molti SO, anch’esso gratuito.

Scegliete quello che preferite, apritelo e navigate le opzioni per scegliere


Flowchart.

Come potete vedere, i flowchart non sono gli unici diagrammi che questi SW
permettono di disegnare. La cosa ci tornerà utile...
DIAGRAMMI DI FLUSSO (3)
Come si legge un flowchart?

1. Parti dall’inizio.
2. Segui la freccia fino al prossimo blocco.
3. Termina se sei alla fine.
4. Esegui il contenuto del blocco raggiunto.
5. Torna a 2.

...aspettate...
DIAGRAMMI DI FLUSSO (4)
BLOCCHI ELEMENTARI:

● INIZIO: Forma ellittica, specifica da dove comincia l’algoritmo.

● FINE: Forma ellittica, specifica dove termina l’algoritmo.

● I/O: Parallelepipedo, indica una lettura o scrittura di dati.

● ELABORAZIONE: Rettangolo, dichiara le operazioni.

● TEST: Forma romboidale, effettua un test su condizioni.


DIAGRAMMI DI FLUSSO (5)
DIAGRAMMI DI FLUSSO (ESERCIZI)
Usando un SW opportuno a vostra scelta, pensate ad algoritmi risolutivi per i
seguenti problemi e modellateli tramite flowchart:
1. Dati 2 numeri in ingresso, calcolare e restituire il loro prodotto usando solo
l’operazione di somma.
2. Dati 3 numeri in ingresso, stabilire e restituire il maggiore fra loro.
3. Data una parola in ingresso, stabilire e comunicare se essa è palindroma.
4. Data la base e l’altezza di un triangolo calcola l’area. Se essa è maggiore di
100, leggi altri due valori di base e altezza e ripeti il calcolo con i nuovi valori,
sommando quest’area alla precedente. Se la somma è maggiore di 9000
restituisci “IT’S OVER 9000!”, altrimenti ripeti da capo. Se la prima area è
minore o uguale a 100, restituisci “SHAME...”.
Soluzione Proposta 1
Soluzione Proposta 2
Soluzione Proposta 3
Soluzione Proposta 4
VALUTAZIONE DEGLI ALGORITMI (1)
Spesso un problema può essere risolto da molteplici algoritmi. Ogni algoritmo a
che risolve un problema P si dice essere corretto.
La correttezza però non è tutto: se da Milano devo andare in Francia è inutile che
io prima passi per Venezia anche se ho comunque abbastanza carburante per
completare il viaggio!
Oltre che alla correttezza infatti si valutano molti altri aspetti, principalmente la
complessità spaziale e quella temporale.
Vediamo come valutare in modo molto basilare gli algoritmi sotto il punto di visto
della complessità temporale.
VALUTAZIONE DEGLI ALGORITMI (2)
PROBLEMA: MOLTIPLICAZIONE. Quale è il migliore?

Algoritmo molt1 (int a, int b) ⇾ int Algoritmo molt2 (int a, int b) ⇾ int

1. prodotto ⇽ 0 1. prodotto ⇽ 0
2. WHILE b>0 DO 2. WHILE b>0 DO
3. prodotto ⇽ prodotto+a 3. IF b.isDispari() THEN
4. b ⇽ b-1 4. prodotto ⇽ prodotto + a
5. RETURN prodotto 5. a⇽a+a
6. b ⇽ b/2
7. RETURN prodotto
VALUTAZIONE DEGLI ALGORITMI (3)
Contare le righe è un primo modo per valutare la complessità perché si contano le
operazioni che l’algoritmo dovrà effettuare.
molt1 esegue le righe 1,5 per 1 volta (totale 2); 3,4 per b volte (totale 2b); 2 per
b+1 volte. In totale, molt1 esegue 3b+3 righe.
molt2 esegue le righe 1,7 per 1 volta (totale 2); 3,5,6 per b volte (totale 3b); 4 per
≤b volte (al massimo b, quindi); 2 per b+1 volte. Nel peggiore dei casi, molt2
esegue 5b+3 righe. Nel migliore, 4b+1.
Direi che è evidente quale sia l’algoritmo migliore.
Provate!
VALUTAZIONE DEGLI ALGORITMI (ESERCIZI)
Valutare la complessità temporale in termini di righe degli algoritmi progettati nella
scorsa sezione. Cimentarsi nello scrivere pseudocodice se si ha tempo o se si è
in difficoltà.
TEORIA DI BASE DELL’INFORMAZIONE
LO PARLI IL BINARIO?
Il bit è l’unità di informazione di base che può assumere solo due valori: 0 e 1. A
livello elettronico essi vengono rappresentati con due range di livelli di tensione:
bassa per 0 e alta per 1. Ma a noi l’elettronica non interessa…
Sapete parlare il binario?
Lo 0 e l’1 del bit possono essere interpretati in molti modi: spento e acceso, no e
sì, falso e vero (Il Boolean è un grande amico di quest’ultima interpretazione).
Per informazioni più grandi ci vogliono più bit. In generale:
k bit identificano 2k informazioni!
TASSONOMIA DEL BIT
Il bit (b) è l’unità base di rappresentazione dell’informazione. Grandi informazioni
sono più comodamente rappresentati con multipli.

Il multiplo più importante è il Byte (B) = 8 bit.

Kb 1000 bit KB 8000 bit

Mb 106 bit MB 8*106 bit

Gb 109 bit GB 8*109 bit

Tb 1012 bit TB 8*1012 bit


ASCII
L’ASCII è una codifica per caratteri alfanumerici
basata su sequenze binarie su 8 bit (quanti simboli
quindi?). Alcune di queste codifiche identificano
segnali di controllo (0-31, quanti bit?) mentre le
altre dei caratteri.

Vedremo che è possibile operare su Stringhe e


caratteri utilizzando la loro codifica ASCII.
CONVERTIRE BINARIO DECIMALE
DA DECIMALE A BINARIO DA BINARIO A DECIMALE
CONVERSIONI GENERICHE
In realtà gli algoritmi appena presentati sono applicabili per conversioni da
qualsiasi sistema numerico a qualsiasi altro. Tecnicamente, un sistema numerico
è in realtà detto avere una numerazione in base n, dove n è il numero massimo di
valori rappresentabili.

Per estendere gli algoritmi visti, cambiare le basi!

● Da decimale a numerazione in base n: dividere per n!


● Da numerazione in base n a decimale: elevare nk!
HEX NON STREGA SOLTANTO!
Il sistema di numerazione esadecimale (abbreviato in Hex) è un sistema di
numerazione estremamente utile perché può essere visto come forma compatta di
rappresentazione dei numeri binari.

Simboli usati: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F}

● Da Hex a binario: ogni simbolo viene convertito singolarmente in gruppi di 4


bit.
● Da binario a Hex: quartetti di bit convertiti in simbolo.
CONVERSIONI (ESERCIZI)
Convertire in binario i seguenti numeri decimali: 2, 16, 128, 2345, 110011

Convertire in decimale i seguenti numeri binari: 11010, 10000, 11111010

Convertire in binario i seguenti numeri Hex: A2, 4FB, 14

Convertire in Hex i numeri binari sopra presentati.


SOMMA E SOTTRAZIONE (1)
L’operazione di somma tra numeri binari si fa in modo analogo a quella tra decimali: il riporto
viene aggiunto quando si presenta 1+1=0!
La sottrazione è più complicata per via del problema della codifica dei numeri negativi.
La prima idea è quella del complemento a 1: a+(-a)=0, quindi -a è a con valori scambiati. Si
presenta però un problema: c’è una doppia codifica per lo 0 (00 e 11 su 2 bit).
Si usa quindi il complemento a 2 che si realizza in 2 passi:
1. fare il complemento a 1 di a;
2. aggiungere 1 al complemento.
Se non bastano i bit: overflow numerico.
Si può notare che le cifre più significative identificano il segno! 0 == + e 1 == -.
SOMMA E SOTTRAZIONE (2)
La sottrazione viene quindi eseguita nei seguenti passaggi:

1. Calcolando il complemento a 2 del sottraendo.


2. Effettuando la somma.
SOMMA E SOTTRAZIONE (ESERCIZI)
Calcolare le seguenti somme in binario: 12+9, 99+111

Dati 4 bit, convertire i seguenti numeri in binario: -3, -8

Calcolare le seguenti sottrazioni in binario, dati 8 bit: 100-23, 77-56


ALGEBRA BOOLEANA (1)
Fondamentale per comporre buone condizioni: l’algebra booleana esplora come
combinare valori di V (vero) e F (falso) per ottenere funzioni logiche complesse. La logica
binaria è eccelsa nel rappresentare quella booleana!
Vero = 1 e Falso = 0!
Tutta quanta la logica booleana può essere ricondotta a 3 operazioni fondamentali:
● AND
● OR
● NOT
Qualsiasi funzione logica può essere ricondotta a queste operazioni a prescindere dalla
sua natura.
ALGEBRA BOOLEANA (2)
Le funzioni logiche, ovvero combinazioni di operazioni logiche sugli input così da
ottenere uno o più determinati risultati data una certa combinazione di argomenti.
Sono funzioni con n ingressi e m uscite e per ogni 2n possibili combinazioni di ingresso è
possibile determinare il valore di ogni uscita. L’ingresso subisce una serie di operazioni
and/or/not fino ad essere trasformato nel risultato finale.
Si ricorda che una funzione può avere diverse rappresentazioni, dette espressioni. Ad
esempio una funzione f:(a,b)⇾c potrebbe avere entrambe queste espressioni
equivalenti:
● c = a+b
● c = a+a+b-a
● ...
ALGEBRA BOOLEANA (3)
Una funzione logica è facilmente rappresentabile attraverso una tabella della
verità.

2n righe per ogni combinazione possibile in input.

0 indica un valore booleano falso, 1 vero.

A seconda dei valori di ingresso, troviamo il risultato di f.


NOT
Il NOT è un “invertitore” logico. Il suo funzionamento è basilare: inverte il valore in
ingresso!

if (A==1) A=0; else A=1;

Si può scrivere in vari modi: NOT A; Ā; !A, ㄱA.

A ㄱA

0 1

1 0
AND
L’operatore AND necessita due valori di input per funzionare. Modella il costrutto logico della
congiunzione, ovvero la ”e”. Esso restituisce Vero (1) solo se entrambi gli ingressi sono Veri.

“Vado in spiaggia solo se c’è il sole e se c’è la gnocca che mi piace / il figo dietro a cui sbavo /
inserire altro”.

if (a == b) return 1; else return 0;

Può essere scritto in vari modi: a AND b; a・b; ab.


OR
L’operatore OR è simile all’AND: accetta 2 input e modella il costrutto logico dell’
“oppure”. Esso restituisce Vero (1) se almeno un ingresso è Vero.
“Se c’è il sole o la gnocca che mi piace / il figo dietro a cui sbavo / inserire altro, allora
vado in spiaggia”.
if (a == 1) return 1; else if (b == 1) return 1; else return 0;
Può essere scritto in vari modi: a OR b; a+b.
SOMMA E PRODOTTO LOGICO
C’è un motivo se AND e OR si scrivono rispettivamente come ・e +.

Controllando le tabelle della verità, si può notare che un qualsiasi valore in AND
con 0 fa 0, proprio come accade con la moltiplicazione; mentre basta un 1 per
l’OR per risultare 1.

Per questo motivo essi prendono il nome di somma logica (OR) e prodotto
logico (AND). Inoltre, proprio come nell’algebra classica le operazioni di AND
precedono quelle di OR.
MANIPOLAZIONE ALGEBRICA
Identità 1x=x 0+x=x

Elemento Nullo 0x=0 1+x=1

Inverso ㄱx x = 0 ㄱx+x = 1

Idempotenza xx=x x+x=x

Doppia Inversione !(!x)=x

Associativa (xy)z=x(yz) (x+y)+z=x+(y+z)

Commutativa xy=yx x+y=y+x

Distributiva x(y+z)=xy+xz x+yz=(x+y)(x+z)

Assorbimento x(x+y)=x x+xy=x


PRINCIPIO DI DUALITÀ E DE MORGAN
Nell’algebra di Boole vige il principio di dualità: il duale di una funzione booleana
si ottiene sostituendo gli AND con gli OR (e viceversa) e gli 0 con gli 1 (e
viceversa).

Da questo seguono i teoremi di De Morgan:

● !(xy)=!x+!y
● !(x+y)=!x!y
ALGEBRA BOOLEANA (ESERCIZI)
Definire una funzione logica rappresentando con una tabella della verità e definendo quindi
un’espressione per i seguenti scenari:
A. Voglio uscire di casa solo se sono sano e non c’è un temporale; oppure se un amico ha
bisogno.
B. Posso rilassarmi se è sera o se ho già mangiato o se sono stanco. In ogni caso non posso
riposarmi se in TV c’è Baywatch.
Calcolare la tabella della verità per la seguente espressione (suggerimento: semplificate!):
!D!ABC+!DABC+!D!AB!C+!DAB!C
Date queste espressioni, definire espressioni equivalenti che usano solo l’AND:
● A+B
● AB+B(A+C)
CENNI ALL’ARCHITETTURA DEL
CALCOLATORE
Macchina di Von Neumann
COMPONENTI PRINCIPALI DELL’ELABORATORE
● CPU: Il cuore (o meglio, cervello) dell’elaborazione. Tutte le operazioni viste
fino ad ora e alcune altre vengono eseguite al suo interno. Composta da:
○ ALU: Unità aritmetico-logica che si occupa di eseguire i calcoli.
○ UC: Unità di controllo che si occupa di controllare il funzionamento della CPU.
○ Registri: Memorie interne alla CPU estremamente veloci e piccole che contengono i dati
fondamentali per l’esecuzione.
● Memoria Principale: In genera conosciuta più volgarmente con il termine
“RAM”. Contiene tutti quei dati e quelle istruzioni dei programmi eseguiti che
non possono essere contenuti nei registri. Più grandi ma più lente dei registri.
● Bus: Collegamenti tra componenti che trasportano informazioni.
● I/O: Interfacce con in mondo esterno.
● Memorie di Massa: (In realtà sono viste come dispositivi I/O) Memorie molto
grandi ma molto lente per l’archiviazione dei dati.
GERARCHIA DELLA MEMORIA
Le memorie sono intese per essere viste come disposte in una gerarchia: più alto si è
nella gerarchia più è alta la velocità di accesso; più si è in basso più cresce la grandezza.
L’idea della gerarchia è cercare di offrire la disponibilità di grandezza dei livelli inferiori ai
livelli superiori.
Il processore non può tenere tanti dati nei registri, quindi l’accesso alla memoria è
inevitabile!
CICLO FDE (1)
Il ciclo FDE è il la sequenza di operazioni che il processore deve eseguire per
ottenere le informazioni di cui ha bisogno per eseguire le istruzioni dei programmi.
CICLO FDE (2)
Il processore:
1. FETCH: Accede alla memoria e preleva l’istruzione corretta caricandola in un
registro apposito (IR).
2. DECODE: Decodifica l’istruzione prelevata per comprendere quali operazioni
deve eseguire. La UC è colei che opera la decodifica e prepara la CPU
all’esecuzione.
3. EXECUTE: L’operazione viene eseguita. Se l’istruzione decodificata indica un
prelievo di dati dalla memoria da trasferire nei registri, viene fatto un accesso
alla memoria. Se l’operazione eseguita ha risultati da mantenere, la memoria
ha bisogno di essere acceduta e l’informazione deve essere lì scritta.
COME SI ACCEDE ALLA MEMORIA?
L’accesso alla memoria è indicizzato: la memoria
principale è divisa in indirizzi contenenti un certo numero
di “parole”. Le parole sono sequenze di un certo numero
di bit la cui grandezza dipende dal SO in uso.

La CPU accede alle informazioni in memoria inviando un


indirizzo alla memoria principale, ottenendo come
risposta il contenuto di quell’indirizzo.

Noi programmatori gestiamo l’accesso alla memoria


tramite i puntatori.
FONDAMENTI DI PROGRAMMAZIONE
IL NOSTRO RUOLO
Nell’ambito della creazione di un prodotto software, lo scopo del programmatore
si divide principalmente in due parti: progettare e scrivere il programma.

Progettare significa operare la traduzione di requisiti informali in una specifica e


da essa operare una nuova traduzione in quella che sarà la struttura del
programma.

Scrivere significa implementare la struttura tramite un linguaggio di


programmazione. Esistono molti linguaggi di programmazione e ciascuno di essi
ha i suoi vantaggi e i suoi svantaggi: linguaggi efficienti sono spesso poco sicuri,
linguaggi sicuri sono spesso meno performanti.
ASTRAZIONE
Esistono diversi livelli di astrazione quando si parla di
“programmazione”.

L’astrazione è misurata in termini di distanza dal linguaggio macchina


(composto da soli 0 e 1) con cui ragiona il processore. Più si è vicini al
linguaggio macchina, più si parla di “livello di astrazione basso”.

Il modo di ragionare umano è considerato essere il livello più alto.

La specifica spesso esplora livelli di astrazione man mano inferiori che


culminano nella traduzione nel linguaggio di programmazione.

I linguaggio di programmazione viene quindi trasformato in linguaggio


macchina.
COMPILATORE
Colui che opera la prima traduzione del linguaggio di
programmazione è il compilatore: programma che
prende in ingresso il codice sorgente (serie di
istruzioni espresse nel linguaggio di programmazione)
e che lo trasforma in codice oggetto.

Il codice oggetto è logicamente separato in due parti:


codice eseguibile e informazioni per il linker.
LINKER E LIBRERIE
Una libreria è un frammento di codice contenente
strutture dati e funzioni che possono essere collegate
al codice oggetto per ottenere un codice eseguibile
finale.

Tale collegamento è operato dal linker.

Il discorso è: perché scrivere ciò che è già stato


scritto?
Il codice eseguibile finale può quindi essere caricato dal loader in RAM. Da qui
potrà essere eseguito.
APPROCCI ALLA PROGRAMMAZIONE
Oggi ci sono due approcci principali alla programmazione:

● Programmazione orientata alle funzioni


● Programmazione orientata agli oggetti (OO)

Il linguaggio Java trattato da questo corso, così come il linguaggio C# visto


precedentemente, appartengono alla seconda categoria.

Esempio classico per la prima categoria: C.


PROGRAMMAZIONE ORIENTATA ALLE FUNZIONI
Approccio classico. I programmi sono scritti come sequenze di istruzioni
circoscritte in un programma principale chiamato main.

Per evitare di dover scrivere più e più volte lo stesso codice è stato introdotto il
concetto di funzione (aka: metodi in C# e Java): frammenti di codice invocabili per
nome che accettano un certo numero di argomenti (ingressi) e che lavorano a
partire da essi per effettuare operazioni e, spesso, restituire un risultato.

Per rappresentare tipi di dato non base, composti da aggregazioni di altri tipi, sono
state introdotte le strutture dati: dati contigui in memoria di tipi diversi
interpretabili come un insieme per definire un’entità.
ESEMPIO DI STRUTTURA IN C
ALLOCAZIONE IN MEMORIA ESPLICITA
I linguaggi di programmazione orientati alle funzioni hanno spesso una gestione della
memoria esplicita.
Mentre tipi di dato base sono allocati in memoria staticamente al momento della
dichiarazione della variabile, tipi di dato non base hanno bisogno di un indirizzo per
essere individuate in memoria poiché vengono allocati dinamicamente.
Quando si crea una variabile di tipo non base, bisogna prima allocare spazio in memoria
strutturato adeguatamente per contenere i dati di tale variabile. Può essere fatto
staticamente, così che lo spazio viene dedicato in compilazione, che dinamicamente
tramite l’operazione di malloc.
PUNTATORI
I puntatori sono tipi speciali che vengono spesso trattati come interi nei linguaggi
di programmazione orientati alle funzioni. Essi rappresentano un indirizzo in
memoria e sono utilizzati per accedervi.

Spesso sono ritenuti un incubo per ogni programmatore.

Siamo fortunati: Java li gestisce AUTOMATICAMENTE!

Ma preparatevi all’incubo…

NULL POINTER EXCEPTION


PROGRAMMAZIONE ORIENTATA AGLI OGGETTI
Evoluzione della programmazione orientata alle funzioni.

Strutture e funzioni diventano attributi e metodi.

Dall’esperienza si è visto che gestire le strutture dati manualmente è scomodo ma


che esse sono estremamente utili. Inoltre si è notato che alcune funzioni vengono
usate solo per gestire una singola struttura.

Il mondo è visto come un insieme di oggetti che interagiscono tra loro


scambiandosi messaggi.
CLASSE E OGGETTO
Una classe descrive tutte le caratteristiche di un’entità sotto forma di attributi e le azioni che
questa può compiere sotto forma di metodi. La classe in sé non fa nulla (salvo in alcuni casi, che
vedremo).
Un oggetto è un’istanza di classe. Gli attributi acquisiscono valori e i metodi possono essere
invocati. L’oggetto viene prodotto da uno speciale metodo della classe: il costruttore.
Si pensi al modello relazionale: estensione ed intensione.
C vs Java
C JAVA
● Prestazioni elevate ● Prestazioni inferiori rispetto a C dati
● Gestione esplicita della memoria controlli maggiori e servizi runtime
● Controllo della rappresentazione dei dati a ● Maggiore impiego della memoria
basso livello ● Più verbosità nelle dichiarazioni dei tipi
● Possibile riusare codice già esistente ● Necessario porting per utlizzare codice
scritto in C scritto in linguaggi simili a Java

Eppure risolve molti problemi di linguaggi come


C.
UN DISCORSO DI SICUREZZA
Oltre la comodità di lavorare con gli oggetti, approccio indiscutibilmente più utile
per sviluppare SW applicativo, Java offre molta sicurezza nell’uso. C, dal canto
suo, ha diversi problemi:
● Type Cast non controllato, con rischio di perdita di informazione
● Aritmetica dei puntatori, che essendo interi consentono operazioni così da
andare ad accedere a zone di memoria di tipo diverso
● Non memory safe: l’aritmetica dei puntatori può essere usata per accedere a
zone di memoria a cui non si dovrebbe, sia per errore che non
● Dangling Pointers, puntatori alla memoria continuano a puntarvi anche dopo
che è stata liberata, causando errori di SO o accesso a dati a cui non si
dovrebbe poter accedere.
SOMMARIO
● Algoritmi
● Teoria di base dell’informazione
● Architettura dell’elaboratore
● Fondamenti di programmazione

In caso qualcosa non sia chiaro, scrivetemi!

Potrebbero piacerti anche