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


Appunti di Programmazione e Linguaggio C, Appunti di Programmazione C

Introduzione alla programmazione e al linguaggio C

Tipologia: Appunti

2019/2020

In vendita dal 09/09/2020

Andr3_v1
Andr3_v1 🇮🇹

4.1

(7)

28 documenti

1 / 20

Toggle sidebar

Questa pagina non è visibile nell’anteprima

Non perderti parti importanti!

bg1
APPUNTI DI PROGRAMMAZIONE A
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
A1=A
A
k
=A × A ×… × A
(
k volte
)
con k 2
;
Definiamo chiusura di Kleene l’insieme
A
¿
=¿kN 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
sA¿,t A¿
allora
PROPRIETÁ DELLA CONCATENAZIONE:
Associatività:
(
st
)
u=s
(
tu
)
=stu
Non commutatività:
st ts
Identità:
s=s=s
sA¿! k :sAk
posso definire lunghezza della stringa
|
s
|
=k
sA¿, t A¿
|
st
|
=
|
s
|
+
|
t
|
Altre definizioni:
oSia
s , t A
¿
diciamo che t è una sottostringa di s (fattore) se
u,vA¿:s=utv
Se u, v esistono, cioè sono
allora s viene detta sottostringa propria.
o
tA¿
viene dette prefisso di s, se
sA
¿
:s=tv
o
tA¿
viene dette suffisso di s, se
sA
¿
:s=vt
IL LINGUAGGIO
pf3
pf4
pf5
pf8
pf9
pfa
pfd
pfe
pff
pf12
pf13
pf14

Anteprima parziale del testo

Scarica Appunti di Programmazione e Linguaggio C e più Appunti in PDF di Programmazione C solo su Docsity!

APPUNTI DI PROGRAMMAZIONE A

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 = st^ ^ posso^ spezzare^ stringapiù^ parti PROPRIETÁ DELLA CONCATENAZIONE:  Associatività: (^ st )∗ u = s ∗(^ tu^ )= stu  Non commutatività: st^ ^ ts  Identità: s^ = s = s  (^) ∀ s ∈ A ¿^ ∃! k : s ∈ Ak ⟹ (^) posso definire lunghezza della stringa | s |= k

 ∀ s ∈ A

¿ , t ∈ A ¿ | st |=| s |+| t | Altre definizioni: o Sia s , t ∈ A ¿ diciamo che t è una sottostringa di s ( fattore ) se ∃u , v ∈ A ¿ : s = utv Se u , v esistono, cioè sono ^ ^ allora s viene detta sottostringa propria. o t^ ^ A ¿ viene dette prefisso di s, se ^ s^ ^ A ¿ : s = tv o t ∈ A ¿ viene dette suffisso di s, se ∃ s ∈ A ¿ : s = vt 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.

  • / % yfx 3 Esempio: 3+5* Applico formula delle parentesi complete ovvero ogni costante, letterale ha attorno due parentesi e ogni operazione ha due operandi racchiusi tra le parentesi. (3)+(5)(2) ((3)+((5)(2))) 0 4 0 ^ perché tra parentesi PROPRIETÁ DI σ σ (^) è una funzione ricorsiva perché il valore della funzione dipende dalle sue sottoparti; σ^ trasforma la nostra stringa in qualcosa di più semplice, l’accorcia.
  1. σ^ (^ (^ E^ )^ )= σ^ (^ E^ )
  2. σ (^ K )^ = ρ − 1 ( K )
  3. σ^ (^ +^ E^ )= σ^ (^ E )
  4. σ^ (− E^ )=− σ^ (^ E^ )
  5. σ^ (^ A^ ±^ B^ )= σ^ (^ A^ )^ ±^ σ^ (^ B^ )
  6. σ^ (^ AB )= σ^ (^ A^ )^ ^ σ^ (^ B )
  7. σ^ ( A B ) = σ ( Aσ ( B ) Dove ¿ : Z 2 → Z è la divisione intera tra interi tale che: a ÷ b =

⌊ a b ⌋ se ab > 0 ⌈ a b ⌉ se ab < 0 0 se b≠ 0, a = 0 IMP. se b = 0

  1. σ^ (^ A^ %B^ )= σ^ (^ A )^ mod^ σ^ ( B ) Dove %^ è il resto della divisione intera. Questa operazione assume un senso preciso solo se σ^ (^ B )^ >^0 ; se σ^ ( B )<^0 dipende, perché σ^ (^ B )^ non è standardizzato in C. Esempio: 3 + 2 ∗ 5 ⟹ σ (^) [ (( 3 ) +( ( 2 )∗( 5 ) ) (^) ) (^) ]

¿ σ ¿ ¿ ¿ σ ¿ ¿ ¿ σ [ 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:

 float = floating point a singola precisione

 double = floating point a doppia precisione

 long double = floating point con precisione estesa

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.

AMBIENTE DI VALUTAZIONE

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 (¿∨ LN ) ¿ 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:

  1. (^) ∫ sgn^ ¿
  2. (^) ∫ max^2 ¿
  3. double^ max^^2 ¿ Prima si valutano da sinistra a destra le espressioni con valori di cui devo conoscere il tipo dato (parametri) e infine valuto la funzione. Es: 2 ∗ max^^2 (^ 3.0+7,^1 /^3 )=^2 ∗ max^ (^ 10.0,0^ .0)=20. Se una funzione non è presente nel mio ambiente di valutazione la posso ridefinire io, con quel nome, altrimenti questo non è possibile. Ci sono 3 diversi modi di definire una funzione, che a loro volta si riferiscono ai tre paradigmi fondamentali di programmazione :
  1. PROGRAMMAZIONE FUNZIONALE (John McCartney)
  2. PROGRAMMAZIONE DICHIARATIVA (Kowolski)
  3. PROGRAMMAZIONE PROCEDURALE, divisa in due famiglie: a) Imperativa (C, Pascal) b) Orientata agli oggetti (Java, C++) La programmazione funzionale prevede di scrivere le funzioni come si scriverebbero in matematica. Questo tipo di programmazione ha come “padre” il linguaggio lisp , ormai in disuso rispetto, ad esempio, al linguaggio ml che è già più moderno. ¿ max 2 ( a ,b ) { ¿ a if a ≥ b ¿ b if a < b → condizione dimutua esclusività

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:

  1. Scrivere dati;
  2. Leggere dati. La memoria centrale contiene numeri poiché tutti i nostri dati sono convertiti in numeri per essere immagazzinati ed è organizzata come sequenza di celle. Ogni cella immagazzina la quantità di dato minima che riesce a scrivere e leggere; questa quantità di dati atomici minima è il byte (B), unità di misura della capienza minima. Non bisogna confondere il byte con il bit (b): 1 B = 2 3 b Le nostre celle memorizzano in modo unitario un singolo B che, essendo una sequenza di otto bit, può assumere 256 valori, da 0 a 255, ma posso compiere altre operazioni unendo i byte. Vediamo i tipi e il n° di B garantito:  char ≥ 1  short ≥ 2  (^) ∫ 2  long ≥ 4  float = 4  double ≥ 8 La memoria centrale, per come è costruita, è scritta come potenza di 2: K → 1024 = 2 10 M → 2 20 G → 2 30 Ogni cella della memoria, cioè ogni byte, ha un proprio indirizzo. La possibilità di manipolare queste celle in C mi permette di scrivere gli array manipolando blocchi di celle. Inoltre, il C ci fornisce uno strumento aggiuntivo per manipolare gli indirizzi delle nostre celle: le variabili puntatore. LE VARIABILI PUNTATORE Come abbiamo già discusso nel paragrafo precedente, nella maggior parte dei computer moderni la memoria è suddivisa in byte, ognuno dei quali è in grado di memorizzare otto bit di informazione e ogni byte possiede un indirizzo

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.