












Studia grazie alle numerose risorse presenti su Docsity
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Prepara i tuoi esami
Studia grazie alle numerose risorse presenti su Docsity
Prepara i tuoi esami con i documenti condivisi da studenti come te su Docsity
Trova i documenti specifici per gli esami della tua università
Preparati con lezioni e prove svolte basate sui programmi universitari!
Rispondi a reali domande d’esame e scopri la tua preparazione
Riassumi i tuoi documenti, fagli domande, convertili in quiz e mappe concettuali
Studia con prove svolte, tesine e consigli utili
Togliti ogni dubbio leggendo le risposte alle domande fatte da altri studenti come te
Esplora i documenti più scaricati per gli argomenti di studio più popolari
Ottieni i punti per scaricare
Guadagna punti aiutando altri studenti oppure acquistali con un piano Premium
Introduzione alla programmazione e al linguaggio C
Tipologia: Appunti
1 / 20
Questa pagina non è visibile nell’anteprima
Non perderti parti importanti!













INFORMATICA = informazione automatica, elaborazione automatica dell’informazione. L’ informatica si occupa di una disciplina chiamata linguistica computazionale. Claude Shannon ha descritto la teoria dell’informazione che sta alla base della trasmissione dell’informazione, delle telecomunicazioni. DEFINIZIONI DI BASE Sia A^ ≠^ ∅^ ,^ finito^ un alfabeto di simboli : A 0 ={^ ℇ }^ , ℇ ∉ A A^1 = A A k = A × A ×… × A ( k volte ) con k ≥ (^2) ; Definiamo chiusura di Kleene l’insieme A ¿ =¿ k ∈ N A k (infinito contabile) i cui elementi sono detti stringhe ed ℇ^ prende il nome di stringa vuota. Sulle stringhe è possibile definire un’operazione chiamata concatenazione: Sia s^ ∈^ A ¿ ,t ∈ A ¿ allora u = s ∗ t^ →^ posso^ spezzare^ stringa ∈ più^ parti PROPRIETÁ DELLA CONCATENAZIONE: Associatività: (^ s ∗ t )∗ u = s ∗(^ t ∗ u^ )= s ∗ t ∗ u Non commutatività: s ∗ t^ ≠^ t ∗ s Identità: s ∗ ℇ^ = ℇ ∗ s = s (^) ∀ s ∈ A ¿^ ∃! k : s ∈ Ak ⟹ (^) posso definire lunghezza della stringa | s |= k
¿ , t ∈ A ¿ | s ∗ t |=| s |+| t | Altre definizioni: o Sia s , t ∈ A ¿ diciamo che t è una sottostringa di s ( fattore ) se ∃u , v ∈ A ¿ : s = u ∗ t ∗ v Se u , v esistono, cioè sono ≠^ ℇ^ allora s viene detta sottostringa propria. o t^ ∈^ A ¿ viene dette prefisso di s, se ∃^ s^ ∈^ A ¿ : s = t ∗ v o t ∈ A ¿ viene dette suffisso di s, se ∃ s ∈ A ¿ : s = v ∗ t IL LINGUAGGIO
Dato un alfabeto di simboli chiamiamo linguaggio L^ ⊆^ A ¿ il sottoinsieme dell’insieme di tutte le possibili stringhe. Possiamo definire un linguaggio o più in generale un qualunque insieme solitamente in due modi: Caso estensionale: enumerazione di tutti gli elementi di un insieme (perfetto nel caso di insieme finiti); Caso intensionale: definisco gli insiemi per proprietà (perfetto nel caso di linguaggi). Es. L ={ s^ ∈^ A ¿ : p ( s )} NB: p(s) viene definita tramite la morfologia 1 e la grammatica 2 della lingua. C’è un terzo modo per esprimere la struttura dei linguaggi nel caso dei linguaggi di espressioni caratterizzati da: Insieme finito K di costanti zeroari ( arità 3 0); Insieme finito L di operatori unari postfissi (es. 0!); Insieme finito R di operatori unari prefissi (es. -0); Insieme finito B di operatori binari (es. 0+0); Insieme di simboli (es. parentesi tonde). L’unione di questi insiemi e simboli forma l’ALFABETO. NB: le parentesi devono essere bilanciate, cioè ad ogni parentesi aperta deve corrispondere una parentesi chiusa. Inoltre, devono avere al loro interno una stringa non vuota, così come a sinistra (risp. destra) di un operatore postfisso (risp. prefisso) non ci può essere una stringa vuota. SEMANTICA DEL LINGUAGGIO DEF Dato un linguaggio L chiamiamo formula ben formata una qualsiasi stringa di L: WFF di L⟶ s ∈ L Noi, finora, abbiamo costruito la sintassi 4 sulla base della morfologia e della grammatica; ora, dobbiamo definire la semantica del linguaggio. La semantica si basa sul dominio di interpretazione π^ sul quale trasferisco le WFF : Sia π^ ≠^ 0,^ chiamiamo funzione di interpretazione σ^ : σ : L ⊆ A ¿ ⟶ π Questa funzione mi permette di dare una semantica al mio linguaggio. (^1) Parte della linguistica che studia come le parole si possono modificare e come da una parola se ne ottiene un’altra. (^2) Complesso di regole di una lingua. (^3) N° di argomenti del singolo operatore. (^4) Lo studio delle funzioni proprie della struttura della frase.
⌊ a b ⌋ se ab > 0 ⌈ a b ⌉ se ab < 0 0 se b≠ 0, a = 0 IMP. se b = 0
¿ σ ¿ ¿ ¿ σ ¿ ¿ ¿ σ [ 3 ] + σ [(( 2 )∗( 5 ))] ¿ 3 + σ (^) [ ( 2 )∗( 5 ) (^) ] ¿ 3 + σ ¿ ¿ 3 + σ [ 2 ] ∙σ (^) [ ( 5 ) (^) ] ¿ 3 + 2 ∙ σ [ ( 5 ) ] ¿ 3 + 2 ∙ σ [ 5 ] ¿ 3 + 2 ∙ 5 = 13 Analogamente, possiamo estendere queste funzioni a un sottoinsieme di numeri razionali. Sia D^ ⊆^ Q^ e consideriamo l’insieme K^ D tale che K^ D Ki = ∅. Possiamo definire una relazione biunivoca ρ ↓ , p − 1 ↑ tra questi due insiemi tale che: σ (^ k )= ρ − (^1) ( k )^ se k è un elemento di KD Le costanti con il punto appartengono a K^ D : es. 1.0=^1 ∈^ K^ D NB: 0.1 non è rappresentabile in C, non ha lo stesso significato di 1 10
Con K^ D vediamo inoltre in fenomeno di overloading perché l’operatore / assume il significato di divisione intera se i letterali appartengono a Ki , mentre quello di divisione in Q^ se appartengono a K^ D. In presenza sia di variabili int che double le operazioni si estendono automaticamente a K^ D , quindi è di tipo double. Esempio: τ : L → {∫ , double }, τ si calcola nell’espressione a parentesi complete; τ (( ( 3 ) +( 2.0)∗( 5 ) ) ) 5 → 5.0 (^) diventa un double TIPI DATO David Ritchie è partito dal linguaggio B di Thompson, che conteneva solo n° interi, e lo ha esteso con i tipi dato per ottenere il linguaggio C.
_Bool flag; _Bool è un tipo di intero (più precisamente un tipo di intero senza segno) e quindi non è altro che una variabile intera camuffata. Tuttavia, a differenza delle variabili intere ordinarie, a una variabile _Bool possono essere assegnati solo i valori 0 o 1. Più genericamente, cercare di memorizzare un valore diverso da zero in una variabile _Bool fa sì che alla variabile venga assegnato il valore 1: flag = s; /* a flag viene assegnato il valore 1 */ È ammesso (anche se non consigliabile) eseguire dei calcoli aritmetici con le variabili _Bool. Tipi floating point : Gli interi non sono appropriati per tutti i tipi di applicazioni. A volte può essere necessario usare variabili in grado di immagazzinare numeri con delle cifre dopo la virgola, oppure numeri che sono eccezionalmente grandi o piccoli. Tali numeri vengono memorizzati nel formato a virgola mobile (chiamato così perché il separatore decimale è “flottante”). Il C fornisce tre tipi floating point , corrispondenti a differenti formati:
Il tipo float è appropriato per i casi in cui la precisione non è un fattore critico (per esempio quando si calcolano temperature con una sola cifra decimale); il tipo double fornisce una precisione maggiore (sufficiente per la maggior parte dei programmi); il tipo long double, che fornisce la precisione più grande, viene usato raramente nella pratica. Lo standard C non specifica quale debba essere la precisione dei tipi float, double e long double dato che computer diversi potrebbero memorizzare i numeri a virgola mobile in modi differenti. I computer più moderni seguono le specifiche degli standard IEEE Standard 754 (conosciuto come IEC 60559), per questo motivo useremo questa specifica come esempio. Lo standard floating point dell'IEEE
Lo standard IEEE 754 sviluppato dall’lnstitute of Electrical and Electronics Engineers prevede due formati principali per i numeri a virgola mobile: singola precisione (32 bit) e doppia precisione (64 bit). I numeri vengono memorizzati seguendo una notazione scientifica, dove ogni numero è costituito da tre parti: il segno, l'esponente, e la mantissa. Il numero di bit riservato per l'esponente determina quanto grandi (e quanto piccoli) possono essere i numeri. Nei numeri a precisione singola l'esponente è lungo 8 bit mentre la mantissa occupa 23 bit. Ne risulta che i numeri a singola precisione hanno un valore massimo corrispondente all'incirca a 3,40 × 10 38 , con una precisione di circa 6 cifre decimali. NB : guardare in fondo come rappresentare numeri in IEEE 754. Tipi char : I valori del tipo char possono variare da computer a computer a causa del fatto che le varie macchine possono basarsi su un diverso set di caratteri. Attualmente il set di caratteri più diffuso è quello ASCII (American Standard Code for Information lnterchange) un codice a 7 bit capace di rappresentare 128 caratteri diversi. Nello standard ASCII, per esempio, l'intervallo dei codici per i caratteri va da 00000000 fino a 11111111, e questi possono essere pensati come un sottoinsieme di interi da 0 a 127. Esiste infatti una corrispondenza biunivoca: il carattere 'a' ha il valore 97, 'A' ha il valore 65,’ ‘ ha il valore 32.
NB : è bene sottolineare che gli intervalli indicati nella tabella non sono stabiliti dallo standard C e possono quindi variare da un compilatore a un altro. Gli intervalli rappresentati fanno riferimento a una macchina a 32 bit. Definizione di tipi La direttiva #define può essere usata per creare una macro che potrebbe essere usata come un tipo Booleano: es. #define Bool int Un modo migliore per creare un tipo Booleano è quello di usare la funzionalità detta di definizione di tipo : es. typedef int Bool Osserviamo come il nome del tipo che deve essere definito venga posto alla fine. Adesso Bool può essere utilizzato nello stesso modo dei tipi nativi; in pratica, il compilatore tratta Bool come un sinonimo di int.
Quando scriviamo un programma dobbiamo scegliere un nome per le variabili, le funzioni e le altre entità. Questi nomi vengono chiamati identificatori. In C un identificatore può contenere lettere, cifre e underscore ma deve iniziare con una lettera o con un underscore: (¿) ¿ L (¿∨ L ∨ N ) ¿ Es. get_next_char; _done; Nella scrittura di un programma dobbiamo tenere un contesto di valutazione, ambiente di valutazione , una tabella caratterizzata da un identificativo, un tipo e un valore. Esempio: ID TIPO VALORE x float 2. a char ‘A’ x float 3. NB : se ho due indentificativi uguali ma con valori diversi si sceglie come convenzione di prendere l’ultimo valore dal basso. ASSEGNAZIONE (14 xfy) = operazione binaria che ha alla sua destra un’espressione e alla sua sinistra una variabile. L’operatore di assegnazione è il carattere “=”. Quindi, l’operazione di assegnazione prende l’espressione, la valuta e va a inserire il valore all’interno del nostro ambiente di valutazione, in corrispondenza della variabile scelta.
Sia && che || eseguono la "cortocircuitazione" del calcolo dei loro operandi. Questo significa che questi operatori per prima cosa calcolano il valore del loro operando sinistro e successivamente di quello destro. Se il valore dell'espressione può essere dedotto dal valore del solo operando sinistro, allora l’operatore destro non viene esaminato. Esempio: (i != 0) && (j / i > 0) Per trovare il valore dell'espressione dobbiamo per primi cosa calcolare il valore di (i != 0). Se i non è uguale a 0, allora abbiamo bisogno di calcolare il valore di (j / i > 0) per sapere se l'intera espressione è vera o falsa. Tuttavia, se i è uguale a 0 allora l'intera espressione deve essere falsa e quindi non c'è bisogno di calcolare (j / i > 0). FUNZIONI
Dato EC e il nostro ambiente di valutazione, estendiamo la nostra grammatica con le funzioni in modo da ottenere un linguaggio di espressione con simboli di funzione. In C una funzione è semplicemente un raggruppamento di una serie di istruzioni al quale è stato assegnato un nome. Alcune funzioni calcolano un valore, altre no. Una funzione che calcola un valore utilizza l'istruzione “return” per specificare il valore che deve restituire. Per esempio, una funzione che somma 1 al suo argomento dovrà eseguire l'istruzione: return x + l; Una funzione è formata da un identificatore seguito da tonda con all’interno degli argomenti cioè espressioni E 1 ,^ E 2 ,…^ separate da una virgola e infine chiusa tonda. Il nostro ambiente di valutazione conterrà la dichiarazione delle funzioni, la quale prende il nome di prototipo o signature ed è realizzata in questo modo: tr ↓ tipo risultato id ↓ nome funzione ( identificatore ) ( t 1 ,^ t 2 ,^ …^ ,^ tn ) ↓ tipi degli oggetti Esempio:
Tutto questo ci permette di definire un PROGRAMMA, insieme di funzioni che vengono impacchettate in un insieme che però può essere attivato tramite un comando. Diremo che un programma è in precisione arbitraria se il risultato è limitato solo dalla memoria o dal tempo di esecuzione. Il linguaggio del computer che permette di scrivere le procedure da inserire nella sua memoria è il linguaggio macchina. Noi vogliamo raggiungere un livello di astrazione più alto del linguaggio macchina: linguaggio ad alto livello. Il linguaggio C non appartiene però a questo tipo ma è una mezza via: linguaggio a medio livello. A questo punto ho bisogno di un traduttore che prende il nome di compilatore , il quale genera la sequenza di comandi per il mio esecutore traducendo il testo sorgente nella destinazione o testo eseguibile. Noi, nella pratica, ci serviremo di: SORGENTE = Code Blocks ( linguaggio C ) ↓ TRADUTTORE = Compilatore GCC ↓ DESTINAZIONE / ESGUIBILE Quindi, utilizziamo Code Blocks per scrivere il codice sorgente in C, salvando il file come “nome.c”; in questo modo il compilatore assume le funzioni di default. Infine, traduciamo con GCC, GNU compiler collection, il codice sorgente e i risultati che poi andremo a vedere, cioè una volta ottenuto il testo eseguibile chiedo al computer di attivarlo eseguendo i comandi impartiti. Scrivere un testo sorgente in C significa descrivere delle funzioni un po’ diverse da quelle della matematica perché descritte tramite l’insieme dei comandi del linguaggio C che devono essere eseguiti per calcolare il risultato. Esempio: scrivo funzione somma. Ambiente di valutazione Name Type Valore a int … b int … int somma (int a, int b) { return a+b; } Quando apro una graffa inizio un blocco , sequenza di comandi dall’alto verso il basso separati, o meglio, terminati da un “;” chiamati statements.
I comandi vengono eseguiti uno dopo l’altro finché il blocco non finisce seguendo l’ordine. Quando si chiude il blocco, scompaiono le variabili a e b e compare il risultato della funzione grazie al comando “return”. Infatti, questo comando prende l’espressione iniziale, la valuta e una volta valutata associa il risultato della valutazione a quello della funzione. Una funzione non void deve usare l’istruzione return per specificare il valore che sarà restituito. COMANDI DA ESEGUIRE: Apro un new file vuoto e lo salvo come “nome.c”. Il testo sorgente che andrò a scrivere contiene almeno una funzione main per essere eseguibile; nel caso non la contenesse si direbbe codice oggetto. La funzione int main (“n° arbitrario di parametri”) è la procedura principale , cioè la procedura d’ingresso del programma che contiene il codice eseguibile. All’inizio del programma bisogna scrivere la linea: #include <stdio.h> Infatti, questa direttiva 6 indica che le informazioni contenute in <stdio.h> devono essere incluse nel programma prima che venga compilato. I. particolare, l’header <stdio.h> contiene le informazioni riguardanti la libreria standard di I/O (input/output) del C, ossia la maggior parte delle funzioni che useremo nella pratica. Tra le parentesi graffe scriviamo la sequenza di comandi; la graffa si allinea automaticamente dove finisce il blocco. Una volta scritto il testo sorgente, posso tradurlo premendo la rotellina ( build ). Il traduttore ci avverte se abbiamo commesso eventuali errori di scrittura: view → logs → build messages Buildato il programma, le funzioni e gli altri oggetti del programma si colorano perché sono parole speciali in C. Se è andato tutto bene la funzione int main ritorna a 0. Esempio: #include <stdio.h> int main (void) { . . (^6) Le direttive iniziano sempre con il carattere “#” che le distingue dagli altri oggetti presenti in un programma in C. Per default le direttive sono lunghe una sola riga e non vi è nessun punto e virgola o altro indicatore speciale alla loro fine in quanto riguardano il preprocessore.
La memoria centrale di un computer è di circa 4 GB oppure 8 GB. Essa serve per due operazioni:
univoco che lo distingue dagli altri presenti in memoria: se nella memoria ci sono n byte, allora possiamo pensare che gli indirizzi vadano da 0 a n – 1. Ogni variabile presente nel programma occupa uno o più byte della memoria. L'indirizzo di inizio, quello del suo primo byte viene considerato l'indirizzo della variabile stessa: La variabile a occupa i byte corrispondenti agli indirizzi da 102 a 105, di conseguenza l’indirizzo di a è 102, cioè quello più in basso. È qui che entrano in gioco i puntatori. Sebbene gli indirizzi siano rappresentati da numeri, il loro intervallo di valori può differire da quello degli interi, di conseguenza non possiamo salvarli nelle variabili intere ordinarie. Possiamo invece memorizzarli all'interno di speciali variabili: le variabili puntatore. Quando memorizziamo l'indirizzo di una variabile i in una variabile puntatore p diciamo che p "punta" a i. In altre parole: un puntatore non è altro che un indirizzo, e una variabile puntatore è semplicemente una variabile che può memorizzare quell'indirizzo.