Docsity
Docsity

Prepara i tuoi esami
Prepara i tuoi esami

Studia grazie alle numerose risorse presenti su Docsity


Ottieni i punti per scaricare
Ottieni i punti per scaricare

Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium


Guide e consigli
Guide e consigli


Programmazione C e Java (teoria), Dispense di Programmazione Avanzata

Sono una studentessa del primo anno del corso di Sicurezza dei Sistemi e delle Refi Informatiche in Statale a Milano. Questa è la dispensa completa di Programmazione del primo anno (sia materiali del docente che appunti presi a lezione, riordinati e utilizzati all’esame con esito 30 e lode)

Tipologia: Dispense

2025/2026

Caricato il 20/05/2026

giulia-mxp
giulia-mxp 🇮🇹

2 documenti

1 / 12

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
PROGRAMMAZIONE
Il programma deve risolvere il problema ed è scritto per un esecutore che lo comprende. Bisogna trovare lo strumento giusto per farlo.devo adattare
il problema allo strumento che ho.
Il programmatore di un tempo si occupava di tutto: soluzione, programmazione, uso. SDK, tool, AI automatizzano ma servono comunque
competenze di sviluppo e di inventiva. Non è più considerato factotum ma parte di un team, è uno specialista verticale.
—> silos di competenze del waterfall e processi DevOps.
FORMALIZZAZIONE DLE LINGUAGGIO:
Quando si vuole scrivere un programma si deve usare un linguaggio formale, ovvero, parlando informalmente, un insieme di simboli
convenzionare di regole per combinare questi simboli in degli enunciati.
Possiamo distinguere queste regole su 3 livelli:
-sintattico: ovvero il modo in cui i simboli possono essere combinati e, quindi, se una parola (nel senso comune del termine) è scritta in modo
corretto.
-semantico: le modalità con cui ogni simbolo e enunciato viene legato a un significato: ci permettono di capire se un enunciato ha un significato.
-pragmatico: si riferisce invece alle implicazioni pratiche dell’enunciato.
alfabeto: viene definito come un insieme finito e non vuoto di simboli o caratteri. Volendo proporre un esempio di un alfabeto, potremmo
portare il binario. in cui abbiamo un alfabeto = (0, 1), dalla quale posso generare una stringa (o parola). Esiste la possibilità di generare una stringa
che non na nessun carattere: questa si chiama stringa vuota e si indica con E. La stringa è quindi una sequenza finita di simboli. Posso parlare di
nozioni di lunghezza (cardinalità di |s|). Si parla di concatenazione ovvero l’unire una stringa a ad una stringa b per ottenere ab.
la Star di Kleene (o anche chiusura di Kleene) che viene indicata con * è l'insieme di tutte le possibili stringhe generabili all'interno di un
alfabeto . Comprende anche E.
la Chiusura positiva che viene indicata, in maniera piuttosto intuitiva, con + è l'insieme di tutte le possibili stringhe generabili all’interno di
un alfabeto , eccezion fatta per E.
Quindi un linguaggio formale L si definisce come un sottoinsieme di stringhe generate su un alfabeto
L’’obiettivo è trovare le regole necessarie:
-cardinalità: numero di stringhe
-concatenazione
-potenza n-esima = insieme delle stringhe concatenate con possibili ripetizioni
-chiusura L+ = con n>0
-chiusura star = comprende anche la parola vuota.
I Simboli (terminali) o Token (anche se quest'ultima espressione ha un'accezione più pratica) si definiscono come gli atomi che costituiscono un
enunciato in un linguaggio.
-gerarchia: caratteri —> simboli terminali —> stringhe —> linguaggio
un codice è un linguaggio in cui ogni parola generata è univocamente interpretabile (è solo quella sequenza di parole che genera quella frase).
Se rendiamo ad esempio un linguaggio C = (1, 10) si tratta di un codice: in qualunque modo
disponiamo i simboli, possiamo interpretarli univocamente, “11010110”, per esempio, viene
interpretato come “1”, “10”, “10”, “1”, “10”.
C' = (bab, aba, ab) invece non è un codice. Questo perché se, ad esempio, prendiamo la stringa “abababab” può essere interpretata come “ab”, “ab”,
“ab”, “ab”, sia come “ab”, “aba”, “bab”.
Prendendo una generica parola w e * e un linguaggio L, esistono due possibilità:
-w e L (w rappresenta un enunciato nel linguaggio L)
-w non e L (w non rappresenta un enunciato nel linguaggio L)
Per capire se appartiene o no esistono due metodi:
-approccio riconoscitivo: si tratta di riconoscere se una parola appartiene a un linguaggio mediante una procedura algoritmica e per fare
questo si sfrutta un automa a stati finiti (o macchina a stati).
-approccio generativo: si parte dalle regole, da una grammatica del linguaggio, e si cerca di ricostruire la stringa data in input: se si riesce a
farlo, allora la parola è parte del linguaggio, altrimenti no.
—> proprietà del programma:
-sintassi
-verifica proprietà
-traduzione da un linguaggio ad un altro
È importante tenere conto anche del contesto.
AUTOMI A STATI FINITI
Si tratta di modelli matematici che dato un ingresso, producono un’uscita. È importante evidenziare che il numero di input e di output sono
discreti.
Il sistema si trova in uno stato che può cambiare con l’ingresso di un input passando dallo stato iniziale allo stato corrente e il passaggio si chiama
transizione.
È importante perchè modella sistemi complessi. Riconosce un linguaggio come una black box (ingresso = parole, uscite = valore tra o e 1) ovvero se
viene accettata o no). Si muove di stato in stato per capire.
In una parola ogni lettera è un input, riconosco la parola grazie alla sequenza di lettere.
pf3
pf4
pf5
pf8
pf9
pfa

Anteprima parziale del testo

Scarica Programmazione C e Java (teoria) e più Dispense in PDF di Programmazione Avanzata solo su Docsity!

PROGRAMMAZIONE

Il programma deve risolvere il problema ed è scritto per un esecutore che lo comprende. Bisogna trovare lo strumento giusto per farlo.devo adattare il problema allo strumento che ho. Il programmatore di un tempo si occupava di tutto: soluzione, programmazione, uso. SDK, tool, AI automatizzano ma servono comunque competenze di sviluppo e di inventiva. Non è più considerato factotum ma parte di un team, è uno specialista verticale. —> silos di competenze del waterfall e processi DevOps. FORMALIZZAZIONE DLE LINGUAGGIO: Quando si vuole scrivere un programma si deve usare un linguaggio formale , ovvero, parlando informalmente, un insieme di simboli convenzionare di regole per combinare questi simboli in degli enunciati. Possiamo distinguere queste regole su 3 livelli:

- sintattico : ovvero il modo in cui i simboli possono essere combinati e, quindi, se una parola (nel senso comune del termine) è scritta in modo

corretto.

- semantico : le modalità con cui ogni simbolo e enunciato viene legato a un significato: ci permettono di capire se un enunciato ha un significato.

- pragmatico : si riferisce invece alle implicazioni pratiche dell’enunciato.

alfabeto : viene definito come un insieme finito e non vuoto di simboli o caratteri. Volendo proporre un esempio di un alfabeto, potremmo portare il binario. in cui abbiamo un alfabeto = (0, 1), dalla quale posso generare una stringa (o parola). Esiste la possibilità di generare una stringa che non na nessun carattere: questa si chiama stringa vuota e si indica con E. La stringa è quindi una sequenza finita di simboli. Posso parlare di nozioni di lunghezza (cardinalità di |s|). Si parla di concatenazione ovvero l’unire una stringa a ad una stringa b per ottenere ab. la Star di Kleene (o anche chiusura di Kleene ) che viene indicata con * è l'insieme di tutte le possibili stringhe generabili all'interno di un alfabeto. Comprende anche E. la Chiusura positiva che viene indicata, in maniera piuttosto intuitiva, con + è l'insieme di tutte le possibili stringhe generabili all’interno di un alfabeto , eccezion fatta per E. Quindi un linguaggio formale L si definisce come un sottoinsieme di stringhe generate su un alfabeto L’’obiettivo è trovare le regole necessarie:

- cardinalità: numero di stringhe

- concatenazione

- potenza n-esima = insieme delle stringhe concatenate con possibili ripetizioni

- chiusura L+ = con n>

- chiusura star = comprende anche la parola vuota.

I Simboli (terminali) o Token (anche se quest'ultima espressione ha un'accezione più pratica) si definiscono come gli atomi che costituiscono un enunciato in un linguaggio.

- gerarchia: caratteri —> simboli terminali —> stringhe —> linguaggio

un codice è un linguaggio in cui ogni parola generata è univocamente interpretabile (è solo quella sequenza di parole che genera quella frase). Se rendiamo ad esempio un linguaggio C = (1, 10) si tratta di un codice: in qualunque modo disponiamo i simboli, possiamo interpretarli univocamente, “11010110”, per esempio, viene interpretato come “1”, “10”, “10”, “1”, “10”. C' = (bab, aba, ab) invece non è un codice. Questo perché se, ad esempio, prendiamo la stringa “abababab” può essere interpretata come “ab”, “ab”, “ab”, “ab”, sia come “ab”, “aba”, “bab”. Prendendo una generica parola w e * e un linguaggio L, esistono due possibilità:

- w e L (w rappresenta un enunciato nel linguaggio L)

- w non e L (w non rappresenta un enunciato nel linguaggio L)

Per capire se appartiene o no esistono due metodi:

- approccio riconoscitivo : si tratta di riconoscere se una parola appartiene a un linguaggio mediante una procedura algoritmica e per fare

questo si sfrutta un automa a stati finiti (o macchina a stati).

- approccio generativo : si parte dalle regole, da una grammatica del linguaggio, e si cerca di ricostruire la stringa data in input: se si riesce a

farlo, allora la parola è parte del linguaggio, altrimenti no. —> proprietà del programma:

- sintassi

- verifica proprietà

- traduzione da un linguaggio ad un altro

È importante tenere conto anche del contesto. AUTOMI A STATI FINITI Si tratta di modelli matematici che dato un ingresso, producono un’uscita. È importante evidenziare che il numero di input e di output sono discreti. Il sistema si trova in uno stato che può cambiare con l’ingresso di un input passando dallo stato iniziale allo stato corrente e il passaggio si chiama transizione. È importante perchè modella sistemi complessi. Riconosce un linguaggio come una black box (ingresso = parole, uscite = valore tra o e 1) ovvero se viene accettata o no). Si muove di stato in stato per capire. In una parola ogni lettera è un input, riconosco la parola grazie alla sequenza di lettere.

Di solito un automa a stati finiti viene rappresentato o con una tabella o, in alternativa. con un grafo e queste macchine sono molo frequenti. Prendiamo l'esempio di una macchinetta che richiede 50c per una bibita, che accetti monete da 20c e 10c e non :dia resto. Possiamo costruire la seguente macchina a stati (avendo come stati il totale delle monetine inserite fino a quel momento). Non è completamente specificato. —> enigma del barcarolo. Un automa a stati finiti viene caratterizzato dalle seguenti proprietà:

- dinamicità: questo perché evolve cambiando stato

- discretezza: sia gli input che gli stati sono quantificati con un numero finiti.

Gli automi a stati finiti si dividono in:

- automi a stati finiti deterministici (ASFD), ovvero quelle viste finora. A un dato input, la transizione, e quindi lo stato che assume la macchina, è

sempre lo stesso.

- automi a stati finiti non deterministici (ASFND): si tratta di automi che non sempre fanno corrispondere a un input una sola transizione, ma può

assumere un certo stato e un altro anche con lo stesso input. —> MACCHINA DI TURING = macchina teorica equivalente ad un calcolatore e si basa sugli automi a stati finiti. Permette di analizzare e definire il concetto di algoritmo ed è in grado di spostarsi da uno stato all’altro in maniera automatica. Serve per calcolare la calcolabilità/risolubilità , la complessità e l’equivalenza di algoritmi. È un automa a stati finiti avente nastro potenzialmente ed è infatti più potente di un elaboratore che ha memoria finita. L’automa definisce quello che si chiama dispositivo di controllo. Il nastro è formato da una sequenza di celle e ogni cella contiene un elemento dell’alfabeto che legge (possono anche essere vuote). Sul nastro agisce una testina che può muoversi in entrambe le direzioni sul nastro partendo da un contenuto iniziale e può leggere e scrivere in ogni cella del nastro. La testina di lettura-scrittura:

  • Spostamento di una posizione (a sinistra o a destra).
  • Scrittura o lettura di un simbolo nella cella sotto la testina (sostituendo il contenuto precedente). Il dispositivo di controllo in ogni istante si trova in uno stato tra un insieme finito di stati. A seconda dello stato e del simbolo letto

- Cambia stato

- Esegue una azione

Calcola una funzione, prende lo stato iniziale del nastro e crea un output y = F(x)

- x = nastro alla stato iniziale

- Y = nastro nello stato finale

- F = d(MdT) posizione iniziale, stato iniziale e tabella di transizione

Non sempre esiste un un algoritmo che consenta, a partire dai dati di ingresso, di ottenere i risultati come per il problema dell’arresto (indecidibilità). Il problema dell'arresto chiede se sia sempre possibile, dato un algoritmo e un determinato ingresso finito, stabilire se l'algoritmo in questione termina o continua la sua esecuzione all'infinito. È stato dimostrato che non può esistere un algoritmo generale in grado di risolvere il problema per tutti i possibili ingressi. —> dimostrazione: Supponiamo per assurdo che sia possibile scrivere un programma T che accetti in ingresso la coppia < P, x>, formata da un qualsiasi programma P e dal suo ingresso x, e restituisca in uscita ⊤ , se P(x) termina in un tempo finito, e ⊥ altrimenti. Consideriamo il programma P∗ che realizza il seguente algoritmo, per ogni ingresso x:

  1. Esegui T sull’ingresso < P∗,x >;
  2. Se T restituisce ⊥, termina;
  3. Vai al Passo 1. Dimostriamo l’assurdo supponendo di eseguire T sull’ingresso .
  4. T termina con valore di uscita ⊥; ma allora P∗ termina al Passo 2 e quindi il risultato restituito da T è scorretto;
  5. T termina con valore di uscita ⊤; ma allora P∗ non terminerebbe mai al Passo 2 e continuerebbe a ciclare all’infinito: anche in questo caso il risultato restituito da T è scorretto;
  6. T non termina: anche in questo caso, ciò significherebbe che T non funziona come abbiamo supposto. Non esistono modalità di descrizione di soluzioni alternative che consentano di trasformare un problema non computazionale in un problema computazionale.

- Decidibilità : concetto legato alla decidibilità del linguaggio che esprime il problema e solitamente alla Macchina di Turing (MdT) che lo esegue.

- Indecidibilità : non esiste nessun programma che risolve il problema:

Appartenenza di una stringa ad un linguaggio

  • Infiniti linguaggi su un alfabeto
  • Programmi scrivibili da un’alfabeto sono numerabili
  • Ho infinitamente più linguaggi che programmi quindi devono esistere problemi indecidibili (Gödel) —> Autorerefenza: paradosso del barbiere (Bertrand Russell) In un paese vive un solo barbiere, un uomo ben sbarbato, che rade tutti e soli gli uomini del villaggio che non si radono da soli. Il barbiere si rade da solo?

- Algoritmo: sequenza di passi

- Flusso: L’ordine di esecuzione della sequenza di passi

- Esecuzione in sequenza: uno dopo l’altro

- Esecuzione non in sequenza: flusso controllato

Nella descrizione dell’algoritmo esistono dei modi per controllare il flusso delle operazioni da eseguire. Alcune operazioni sono soggette a particolari condizioni per essere eseguite, altre essendo ripetitive vanno eseguite più volte Nell’algoritmo di Euclide (MCD) si vedono chiaramente:

  • la valutazione della condizione (se...allora...altrimenti...)
  • La ripetizione (ricomincia dal punto...)
  • Un salto che rompe la sequenzialità Variabile: assimilabile all’ incognita matematica, sono contenitori di valori che possono variare. Gli algoritmi agiscono su variabili Architettura di Von Newman:
  • la variabile risiede in memoria RAM ed ha una dimensione
  • ha associato un identificatore ( x,y,resto ) —> La variabile è un oggetto fisico l’incognita matematica è teorica Si definiscono in termini:

- nome: identificatore (a cui fare riferimento). Un identificatore è il nome di un oggetto. L’identificatore corrisponde ad una area di memoria che

contiene il valore dell’oggetto. Il rapporto tra identificatore e valore è dinamico mentre quello tra identificatore e l’area di memoria è statico. L’identificatore di un variabile dovrebbe essere auto- documentante. Attraverso l’identificatore è riferibile una locazione di memoria che ha una dimensione associata. È un contenitore di valori che possono variare durante il tempo di esecuzione.

- tipo : insieme dei possibili valori che possono essere assunti (numero, carattere ecc.) ed operazioni che possono essere fatte

- valore: assunto.

  • Ha una esistenza limitata
  • Inizia ad esistere dalla sua dichiarazione e cessa alla chiusura del blocco in cui è stata dichiarata
  • In realtà si tratta più correttamente di visibilità non di esistenza
  • Ovvero il programma non riconosce più come definito l’identificatore della variabile
  • Si parla di scope di una variabile per indicarne il suo ambito di visibilità
  • L’assegnamento è l’operazione che cambia il valore di una variabile Si può variare il contenuto attraverso l’ assegnamento : variabile ← espressione Viene calcolato il valore dell’espressione scritta a destra del simbolo ← Il risultato ottenuto è assegnato alla variabile a sinistra del simbolo ← (nel caso sovrascrivendo l’eventuale valore precedentemente contenuto) Se quest’ultima contiene già un valore (e lo contiene sempre) questo viene sovrascritto.
  • L’assegnamento è un operatore e quindi ha un valore che è il valore dell’espressione a destra di =
  • L’assegnamento è associativo destro
  • Oltre al valore ha anche un effetto secondario (side effect) ovvero modifica il valore dell’operando a sinistra
  • Il side effect si materializza dopo la valutazione del valore di =
  • L’assegnamento si può comporre con gli altri operatori aritmetici += -= *= /=
  • L'operatore ++ (incremento) incrementa di 1 una variabile intera
  • L'operatore -- (decremento) decrementa di 1 una variabile intera
  • Si possono usare prefissi (++i e --i) o postfissi (i++ e i- -)
  • se prefissi, eseguono l'operazione subito
  • se postfissi, la eseguono per ultima prima dell'istruzione seguente a=++b; (incrementa b e assegna il valore ad a) a=b++; (assegna b ad a e poi incrementa b)

Il tipo di una variabile specifica:

  • La classe di valori che questa può assumere
  • L’insieme delle operazioni che possono essere effettuate su di essa.
  • Quanto spazio occupa in memoria Ad esempio una variabile x di tipo intero
  • Può assumere come valori solo numeri interi
  • Su di essa possono essere effettuate soltanto le operazioni consentite per i numeri interi
  • La sua occupazione di memoria è solitamente uguale alla dimensione dei registri dell’elaboratore (es. 32, 64 bit) Il concetto di variabile è un’ astrazione del concetto di locazione di memoria. La variabile è il riferimento mnemonico all’indirizzo della locazione di memoria. L’assegnamento di un valore a una variabile è un’astrazione dell’operazione STORE di un ipotetico linguaggio macchina. Tutte le variabili sono rappresentate nella memoria come sequenze di bit, tali sequenze possono essere interpretate diversamente in base ai tipi La tipizzazione delle variabili permette di mantenere il controllo sulla correttezza nello sviluppo del programma. La nozione di tipo fornisce un’ astrazione rispetto alla rappresentazione effettiva dei dati. Il programmatore può utilizzare variabili di tipi differenti, senza necessità di conoscerne l’effettiva rappresentazione. Esiste genericamente un tipo associato ad ogni espressione
  • dipende dai tipi delle variabili o letterali (ovvero numeri cifre ecc.) utilizzati nell’espressione —> un tipo indica:
  • l'insieme dei valori che può assumere
  • il formato binario del valore (es IEEE 754)
  • lo spazio occupato in memoria dal valore
  • le operazioni che si possono compiere —>tipi di dato:
  • Tipi elementari: informazioni composte da un solo valore
  • Tipi strutturati: informazioni composte da più valori concettualmente legati
  • Numeri interi: valori da -2n-1 a 2n-1-1 —> short, int e long
  • Numeri naturali: valori tra 0 e 2n-1 —>unsigned short, unsigned int, unsigned long
  • Numeri reali: in realtà sono numeri razionali —> float, double, long double
  • Caratteri: dipende dagli standard ASCII Latin-1 ecc. Sono un insieme ordinato —> char, unsigned char e signed char

- Se la variabile ha tipo più ampio, nessun problema

- Se la variabile ha tipo più limitato:

  • Valore dell’espressione intero e variabile intera di tipo inferiore o carattere: se il valore rispetta l'intervallo, si converte; altrimenti, diventa indefinito
  • Valore dell’espressione reale e variabile reale di tipo inferiore: se il valore rispetta l'intervallo, si approssima; altrimenti, diventa indefinito
  • Valore dell’espressione reale e variabile intera: si tronca il valore alla parte intera La dichiarazione è un enunciato di linguaggio di programmazione che definisce un identificatore e l'informazione a questo correlata. Si divide in esplicita (richiede di definire una variabile prima del suo utilizzo) e implicita. Di norma dichiaro quello che il programmatore ha la libertà di definire (es. variabili, funzioni ecc.)
  • Molti linguaggi richiedono di dichiarare le variabili utilizzate nel programma in modo esplicito Vantaggi nella dichiarazione:
  • Accresce la leggibilità dei programmi
  • Diminuisce la possibilità di errori
  • Facilita la traduzione efficiente in linguaggio macchina (compilatori efficienti) La dichiarazione dei tipi delle variabili serve per
  • riservare preventivamente in memoria lo spazio giusto alla variabile
  • dirimere la grammatica tramite informazioni addizionali La costante è intuitivamente un valore che non cambia per il lasso di tempo in cui viene usato ma rimane comunque una variabile. La costante è differente dal letterale che è un valore impresso nella memoria (come i numeri)
  • Un letterale è un carattere che viene inserito in una espressione Una costante in programmazione è un modificatore di tipo che indica che una variabile non potrà mai cambiare valore. —> dichiarazioni delle variabili:
  • Sappiamo che un programma C è costituito al minimo da una funzione, il main
  • Per ora abbiamo sempre dichiarato variabili all’interno di blocchi all’interno della funzione main
  • Le variabili dichiarate all’interno di una funzione in generale si chiamano variabili locali o variabili automatiche in terminologia gergale del C
  • Una variabile va dichiarata prima di usarla ed è buona norma dichiarare subito tutto all’interno della funzione/blocco che userà quelle variabili
  • Quando dichiaro una variabile dichiaro il tipo e il suo nome che si chiama anche identificatore —>I tipi di dato possono essere in relazione gerarchica tra di loro
  • Per esempio in C sappiamo che esiste una gerarchia tra char che è contenuto in int e sappiamo che int è contenuto in float che è contenuto in double
  • È possibile effettuare promozioni di tipo che rispettino questa gerarchia: in alcuni casi tali promozioni sono implicite e il programmatore non se ne accorge —> Ci sono regole di conversione implicite e operatori espliciti di conversione. —> la regola è che si converte al tipo più ampio. La conversione implicita avviene quando
  • un'espressione composta ha operandi di tipo diverso
  • un assegnamento ha tipo r-value e l-value diversi
  • una chiamata a funzione ha argomenti di tipo diverso dai parametri corrispondenti
  • un'istruzione return e seguita da un'espressione di tipo diverso dal tipo del risultato della funzione Si parla di casting in caso di conversioni esplicite ‘‘(’’ ‘‘)’’ . Con più operatori, servono regole di precedenza per definire l'ordine di esecuzione:
  • Ogni operatore ha una priorità e quindi si considerano prima gli operatori a priorità superiore e poi gli operatori a priorità inferiore
  • Tuttavia la priorità non ordina completamente gli operatori
  • Se un'espressione ha più operatori della stessa priorità, l'associatività determina se eseguire le operazioni associate Gli operatori aritmetici precedono quelli relazionali i quali precedono quelli logici OPERATORI LOGICI
  • AND (&&) ovvero al congiunzione
  • a&&b vale 1 (vero) se la valutazione di a e di b da vero, vale 0 (falso) altrimenti
  • OR (||) ovvero la disgiunzione
  • a||b vale 1 se la valutazione di almeno uno tra a e b da vero, vale 0 (falso) altrimenti

Un calcolatore secondo l’architettura di von Neumann è in un certo senso la realizzazione concreta di una MdTU

  • La memoria dati/programmi può essere considerata l’equivalente del nastro La potenza computazionale è la medesima Una macchina di von Neumann è un calcolatore universale —> MdTU è un modello astratto degli attuali elaboratori Il linguaggio L è un sottoinsieme delle parole costruibili su un alfabeto Σ, L ⊆ Σ* Il sottoinsieme di parole di un linguaggio può essere descritto attraverso un riconoscitore o un generatore Il riconoscitore è implementato generalmente come automa a stati finiti
  • Lo stato dell’automa rappresenta a pieno la sua evoluzione a seguito di una sequenza di input Ogni programma e quindi algoritmo segue due principali concetti, i quali sono:

- Sintassi : descrive come viene scritta una frase

- Semantica : descrive il significato dello scritto

Un linguaggio di programmazione è quasi sempre descritto da una sintassi formale e da una semantica informale Esempio:

  • Sintassi: la data è un insieme di caratteri D e / : 01/03/2013 è una data
  • Semantica: Il giorno a cui si riferisce non è identificabile considerando solo la sintassi La tecnica generativa per definire un linguaggio include tutte le regole necessarie per generare parole di un linguaggio e non i passaggi per riconoscere una parola come nel caso degli automi a stati (sistema riconoscitivo) La sintassi può essere formalmente definita da una grammatica
  • Esempio le regole per scrivere una data: gg/mm/aaaa I linguaggi di programmazione usano dei mix di notazioni differenti
  • Prefissa: l’operatore è indicato prima degli operandi +ab
  • Postfissa: l’operatore è indicato dopo gli operandi ab+
  • Infissa: l’operatore è indicato tra gli operandi a + b. La notazione infissa richiede delle regole di precedenza o l’uso di parentesi —> associazione sinistra in caso di pari precedenza. Il numero di operandi di un operatore è detto arità
  • Funzioni tipo max (x,y) sono prefisse e hanno l’arità che dipende da quello che c’è in parentesi Una modalità grafica ad albero viene utilizzata per evidenziare i componenti più significativi di un linguaggio = Abstract Syntax Tree (AST)
  • Mostra la a struttura sintattica non la notazione. Utile per rappresentare l’ordine di importanza all’interno di un linguaggio.
  • È indipendente dalla notazione e quindi dalla grammatica La versione specifica dell’ast per una data grammatica è il concrete syntax tree (parse tree)
  • Se una stringa ha più di un parse tree la grammatica è ambigua GRAMMATICA —> La BNF (notazione per descrivere la sintassi, quindi la grammatica, di un linguaggio) e le carte sintattiche Nell'informatica come presentato precedentemente esistono più grammatiche, tutte queste sono state gerarchizzate da Chomsky in una tabella chiamata appunto la Gerarchia di Chomsky :

- Tipo 0 Senza restrizioni, include tutte le grammatiche come le macchine di turing

- Tipo 1 Contestuali, grammatiche contestuali come la macchina di Turing non deterministica

- Tipo 2 Non-contestuali, grammatica non contestuale, descrizione formale dei linguaggi di programmazione come gli automi a pila non

deterministici

- Tipo 3 Regolari, grammatica regolare, componenti sintattici più elementari come gli automi a stati finiti

Tra le quattro tipologie quelle di terzo tipo sono le più semplici, quest'ultime generano linguaggi regolari riconosciuti dagli automi a stai finiti BNF: Formalismo utilizzato nell’informatica

  • → delle regole viene sostituita da ::= (può essere letto come “può essere”)
  • Simboli non terminali racchiusi tra parentesi angolari:
  • Simboli terminali (token) racchiusi tra virgolette (’ ’ o ” ”)

- Elementi opzionali racchiusi tra parentesi tonde o quadre

- Elementi che possono essere ripetuti zero o più volte racchiusi tra parentesi graffe

Le regole di produzione hanno la forma nella grammatica di tipo 3 : Destra-regolare: A - aB oppure B —> b Sinistra-regolare: A -› Ba oppure A —> a con A,B ∈ N e a,b ∈ Σ Un linguaggio regolare è un linguaggio le cui stringhe sono implicate da un’ espressione regolare Grammatiche di tipo 3 sono adatte a rappresentare un ristrettissimo insieme di linguaggi formali Le espressioni regolari vengono utilizzate come linguaggio di input per vari sistemi che trattano stringhe Sono utilizzate nei comandi UNIX (grep) per la ricerca di stringhe e per i generatori di analizzatori lessicali tipo Lex e Flex. Utilizzano le operazioni tipiche di un linguaggio formale L

- Alfabeto Σ

- unione Σ ∪ Σ

- concatenazione Σ · Σ , Σ ∗ , Σ +

Partendo da un linguaggio formato da stringhe di lunghezza unitaria se ne creano altri di lunghezza 2, 3, ... Espressione regolare r ,definita su Σ e su un insieme di metasimboli +,∗,(,),·,∅ non appartenenti a Σ, è una stringa r appartenente all'alfabeto ( Σ ∪+ , *, (, ), · ,∅) tale che valga una delle seguenti condizioni:

  • r=∅
  • r= x, x∈Σ
  • r= (s+ t), s, t espressioni regolari su Σ e + è l'unione
  • r = s · t, s, t espressioni regolari su Σ e · è la concatenazione
  • r = s , s espressione regolare su Σ e ∗ è la chiusura di Kleene LA CARTE SINTATTICHE Sono dei diagrammi che esprimono le regole di una grammatica in forma grafica. In una carta sintattica:
  • I rettangoli indicano simboli non terminali (che andranno espansi con le carte sintattiche corrispondenti)
  • Gli ovali o rettangoli con gli angoli arrotondati, indicano simboli terminali, che quindi non devono essere espansi ulteriormente
  • Le frecce sono definite in modo tale che, seguendo i percorsi da esse delineati, sia possibile ricostruire una sequenza lecita di simboli
  • Ogni biforcazione indica un’alternativa —> linguaggi : Informali: linguaggio naturale Non si usa per la descrizione di programmi perchè è inadatto per una macchina:
  • Ambiguo
  • Vago
  • Complicato Nessuno ha ancora costruito una macchina che capisce l’italiano (o l’inglese) ma ci stanno provando con ottimi risultati usando ML
  • NLP-ChatGPT Semi-formali: Pseudo-codice, Flow chart (diagrammi di flusso)
  • Specifiche iniziali, ancora intellegibili all’essere umano
  • Pseudo-codice: se A>0 allora A=A+1 altrimenti A= Risulta più chiaro percepirlo quando si sa già scrivere del codice piuttosto che al contrario Si rischia di scrivere in pseudo-naturale al posto che pseudo-codice. Ci sono delle Keyword o insieme di keyword che hanno una semantica precisa (es goto) Sequenza di istruzioni, decisione (if then), controllo di flusso (goto) Istruzioni per catturare input ed associarlo a variabili (read) Istruzioni per scrivere in output il risultato (write) —> diagrammi di flusso (flow chart): sono un formalismo grafico di descrizione degli algoritmi. I diversi tipi di istruzioni che caratterizzano questo formalismo sono rappresentati tramite blocchi di varia forma, connessi da frecce:

- Assegnamenti racchiusi in rettangoli

- Decisioni racchiuse in rombi

- Flusso definito da frecce

- Ripetizione si nota un flusso che se percorso permette di ripetere istruzioni

  • Contiene almeno una decisione che determina la fine della ripetizione. Il formalismo dei flow chart
  • Evidenzia graficamente dei costrutti
  • Evidenzia il flusso del programma
  • Generalmente contiene operazioni definite in un linguaggio di programmazione formalizzato Orientato principalmente ad un esecutore umano. Ha il pregio di mettere ben in evidenza il control flow (la presenza di cicli, di salti, di biforcazioni, ecc..). Formale: Linguaggio di programmazione —> già spiegato. Per quanto riguarda il formale e lo pseudo formale:
  • Linguaggi grafici: linguaggi pensati per un esecutore umano
  • Flowchart
  • UML (Unified Modeling Language)
  • Linguaggi di programmazione: adatti ad un esecutore automatico, specificatamente il calcolatore
  • C, Pascal, Java, VisualBasic, ecc. Il linguaggio di programmazione è ideato per tradurre algoritmi in un linguaggio comprensibile al calcolatore
  • Aderenza dettagliata alla macchina (assembler): traduzione quasi immediata in linguaggio binario (quello veramente eseguito dalla macchina). Il processo relativo si chiama assemblatore
  • Livello più vicino al programmatore (altolivello). Tutti i linguaggi di alto livello vengono tradotti in una forma di assembler prima di essere tradotti in binari e quindi eseguibili. Per questi linguaggi si parla di compilatore o interprete.
  • Diversi paradigmi di programmazione, strumenti differenti

LINGUAGGIO C

  • Un programma è come una funzione che prende degli ingressi (x) e ritorna delle uscite (y) ovvero y<-f(x)
  • Possiamo iniziare a pensare che tale funzione debba essere esplicitata quando scriviamo del codice —> formato: ‘‘(’’ ‘‘)’’...
  • Il programma potrebbe essere fatto da sequenze di funzioni a sua volta
  • La funziona principale, quella che ‘‘contiene’’ tutto il programma, viene per convenzione chiamata main
  • Fisicamente un programma scritto in un linguaggio di programmazione risiede su uno o più file di testo
  • Di solito ogni file contiene una o più funzioni differenti
  • Questi file che contengono il programma si chiamano anche sorgenti
  • Un programma può essere fatto da più file sorgente che sono tutti necessari per la sua esecuzione (utile per leggere il codice e per riusarlo)
  • Prima di eseguire il programma quindi vengono in modo automatico riuniti i sorgenti in un’unica entità che poi viene tradotta nel codice eseguibile = questo processo si chiama compilazione
  • La traduzione può avvenire in più fasi avvicinandosi al vero linguaggio del calcolatore che è chiamato linguaggio macchina.
  • Il linguaggio più vicino al linguaggio macchina che non richiede passaggi intermedi nel suo processo di traduzione è l’assembler
  • Gli altri linguaggi ad alto livello vengono prima tradotti in assembler e poi in linguaggio macchina
  • Alto livello vuol dire che il linguaggi si avvicina di più al modo di pensare delle persone che al modo di agire del computer

- In C le variabili vanno dichiarate prima di essere usate

- La dichiarazione prevede di esprimere il tipo il nome ed un eventuale valore di inizializzazione attraverso un assegnamento: int somma=0;

- L'assegnamento in Cè codificato dal simbolo “=“

- Assegna il valore che sta alla destra al contenitore (variabile di norma) che sta alla sinistra

  • Essendo i numeri rappresentati in uno spazio finito di memoria con un numero prefissato di bit, può succedere che una operazione porti ad un numero non rappresentabile da quella quantità di bit
  • Costrutto di selezione in C If () If () else
  • Il blocco sappiamo se è costituito da più di una istruzione è definito da { }
  • Di si valuta il valore logico (vero o falso)
  • In realtà il C per convenzione lavora sempre con i numeri (il tipo booleano è stato introdotto dopo nel C99)
  • Per il C il numero 0 è falso e qualsiasi altro numero è vero (per convenzione si intende l’1)
  • Ogni else si riferisce all'if più vicino non ancora accoppiato, se le parentesi graffe non indicano altrimenti Un’espressione è una sequenza di simboli a cui è associato un valore
  • Abbiamo operatori relazionali (<,>,<=, >=, ==, !=)
  • Operatori aritmetici (+,-,*,/,%)
  • Operatori logici: (!, &&, ||, ...)
  • Si possono usare le parentesi tonde per chiarire le precedenze anche se esiste un ordine di precedenza Il suo valore:
  • Nel caso di una costante: il suo valore
  • Nel caso di una variabile: il suo valore corrente, cioè l'ultimo valore assegnatole
  • Nel caso di una funzione: il valore restituito, cioè il risultato della funzione res=max(10,15);
  • Nel caso di un'espressione composta: il valore ottenuto eseguendo l'operazione indicata dall'operatore sui valori degli operandi —> Operatori aritmetici
  • I tipi degli argomenti hanno un ruolo nel decidere la semantica dell’operatore
  • Per esempio l’operatore modulo % è definito solo tra operandi di tipo intero
  • L’operatore divisione intera / denota la divisione intera se ha operandi interi, la divisione reale se almeno uno degli operandi è un float o double int a=1; float b=2; a/b // è la divisione tra reali int a=1; int b=2; float res; res=a/b; //cosa torna? 0 non 0,5 perché appunto essendo una divisione tra interi anche il risultato sarà intero. Specificatore di formato: %[*][width][modifiers]
  • [ ] indicano una cosa opzionale
    • Serve per ignorare quello che viene letto nel formato che segue
  • width numero di caratteri da leggere
  • modifiers modificatore di dimensioni: h (con d è short int) l (con d long int) L (con f long double)
  • Type come solito d, c, e, f, g, s(stringa), u(unsigned int), o (intero ottale), x (intero esadecimale) L’input da tastiere resta in un buffer fino a che qualcuno non lo rimuove —> scanf cerca nel buffer un pattern (numeri decimali, numeri floating point, caratteri ecc) e si prende quello che riconosce (elimina di solito gli spazi bianchi prima) —> esempio Pattern: es leggere una data (gg/mm/aaaa): —> scanf("%2d/%2d/%4d", &giorno, &mese, &anno);
  • (^) OPERATORE TERNARIO: (espressione1? espressione2 : espressione3 )
  • Costituito dai due simboli? e : che separano i tre operandi
  • Si valuta espressione1 che e di tipo logico
  • se espressione1 e vera, si valuta espressione
  • se espressione1 e falsa, si valuta espressione
  • Si assegna all'espressione composta il valore dell'espressione valutata
  • Posso assegnare il valore dell’operatore ternario ad una variabile v=(espressione1? espressione2 : espressione3 )

switch: L'istruzione switch consente di separare blocchi alternativi che corrispondono ai singoli valori possibili per un'espressione.

  • Il blocco default corrisponde ai casi non elencati esplicitamente, cioè all'ultimo else e quindi può mancare (come l'ultimo else)
  1. Si valuta l'espressione
  2. Si cerca la costante di valore uguale all'espressione
  • Se c'è, si eseguono le istruzioni associate alla costante
  • e quelle associate a tutte le costanti successive
  • Altrimenti si eseguono le istruzioni associate a default switch (espressione) { case costante1 : istruzione/i break; case costante2 : istruzione/i ... break; default: istruzione/i }
  • Per eseguire le istruzioni associate a una sola costante, occorre terminarle con l'istruzione break;
  • Il costrutto switch viola (in teoria) la programmazione strutturata, perché in generale non ha un solo punto di ingresso e un solo punto di uscita:

- ogni case è un diverso punto di entrata

- ogni istruzione break è un diverso punto di uscita

ciclo for Un ciclo per cui ho un numero fissato di iterazioni note a priori (Il while si usa quando non si sa a priori quante volte si ciclerà) for (; ; )

Equivale (logicamente ma non di fatto) a espr-iniz; while (espr-cond) { istruzione espr-incr; }

  • Un sottoinsieme qualunque delle tre espressioni si può omettere. Se espr-cond è omessa, essa è assunta sempre vera.
  • Quindi, for(;;); equivale a un ciclo infinito come while(1);
  • L’espressione espr-iniz è di normale assegnamento che inizializza una variabile indice.
  • L’espressione espr-incr è spesso un incremento o un decremento della variabile indice.
  • Molto frequentemente un post incremento Operatore virgola Modificare più variabili in una sola istruzione, volendo però evitare effetti collaterali multipli poco controllabili
  • L'operatore virgola combina più espressioni qualsiasi in un'istruzione sola espressioneA, espressioneB, espressioneC
  • Associativo da sinistra a destra:
  • si valutano le espressioni da sinistra a destra
  • il suo valore è il valore dell'ultima espressione
  • ha la priorità più bassa (non occorrono parentesi)
  • Si usa con espressioni che hanno effetti collaterali raggruppandole in una sola istruzione leggibile Istruzione break:
  • In C si può uscire da un ciclo in punti diversi dalla condizione di permanenza (ciò e contrario alla programmazione strutturata)
  • L'istruzione break provoca l'uscita dal blocco corrente (nei blocchi switch, while, do-while e for) —> esempio Valuta se n e composto o primo (d sta per \divisore") for (d = 2 ; d < n ; d++) if (n % d == 0) break; return ( (d < n)? 0 : 1 ); —> Il break viola la programmazione strutturata quando usato nei cicli, è indispensabile negli switch Istruzione continue: L'istruzione continue provoca il passaggio all'iterazione successiva, ignorando il resto del corpo
  • Sostituibile attraverso un if con la condizione negata
  • Per la leggibilità il continue andrebbe evitato —> Il continue rende difficile avere degli invarianti di ciclo Tipi definiti da utente: typedef ; Si può dare un nuovo nome ai tipi di dato esistenti (predefiniti) allo scopo di chiarire il significato di un dato e facilitarne le modifiche
  • La definizione di tipo se inserita in un blocco vale solo nel blocco cui appartiene (parte dichiarativa del blocco)
  • Se inserita fra le direttive (fuori da tutto) vale per l'intero file (dichiarazioni globali)
  • La definizione di tipo termina con ; come tutte le istruzioni
  • Effetto: Aggiunge un nuovo nome alla tabella dei tipi di dato
  • Esiste anche la tabella dei simboli a cui vengono aggiunti gli identificatori delle dichiarazioni e l’indirizzo di dove possono essere reperite