# Linguaggio C

Il C è un linguaggio di programmazione compilato. Ciò vuol dire che è necessario utilizzare un "compilatore" per trasformare il codice sorgente in un programma eseguibile dal sistema operativo. Il livello di programmazione offerto dal C è relativamente basso, pertanto è possibile creare programmi molto performanti e per questo motivo viene utilizzato in molte applicazioni che necessitano di prestazioni elevate (sistemi operativi, giochi per PC, applicazioni embedded, ecc.). La sintassi è molto semplice, ma per diventare esperti programmatori, è necessaria moltissima pratica. Uno dei punti forti del C è sicuramente la gestione della memoria: è il programmatore stesso infatti che alloca determinati blocchi di memoria e si occupa della loro deallocazione. Inoltre è un linguaggio molto flessibile, con tipi di dati che permettono di costruire strutture dati anche molto complesse. Il C è un linguaggio procedurale. Ciò vuol dire che non esistono classi, oggetti, eventi, ecc. come per esempio nel C++, Java e molti altri linguaggi di più alto livello. L'elemento "portante" dei programmi C è costituito infatti dalle funzioni (spiegheremo in seguito cosa sono) o procedure.
A questo punto iniziamo subito a parlare del linguaggio.

## Gli strumenti necessari

Come già specificato, per poter eseguire un programma scritto in C, è necessario compilare i sorgenti. Per far questo sono necessari un compilatore e un linker. Ci sono molti compilatori C, molti dei quali gratuiti (per esempio gcc). Inoltre vi consiglio di utilizzare degli ambienti IDE, poiché offrono molte funzioni utili durante la scrittura dei programmi. Degli ottimi ambienti sono Dev-C++ (per Windows) Xcode (per Mac Os X) e Anjuta (per Linux). Link diretto per il download del compilatore Dev-C++. Link diretto per il download di Anjuta
La compilazione di un sorgente C è eseguita in 3 fasi:
* Precompilazione
* Compilazione
* Linking
La Precompilazione è eseguita dal precompilatore, che si occupa di includere i files header delle librerie utilizzate, sostituire i valori delle costanti e delle macro definite tramite istruzioni #define, ecc.
La Compilazione è eseguita dal compilatore, che si occupa di trasformare i files sorgenti ottenuti dal precompilatori in files oggetto. Un file oggetto è un file che contiene tutte le informazioni necessare al linker per creare il file eseguibile (tabelle dei simboli, ecc.).
Il Linking è eseguito dal linker, che, come dice il nome stesso, si occupa di unire i vari files oggetti e di collegare tutte le variabili e le funzioni.

## Le variabili

Concettualmente, le variabili si possono definire come contenitori di dati. Esse infatti corrispondono a locazioni di memoria di determinate dimensioni, contenenti un certo dato. Il tipo di dato e la dimensione della locazione di memoria sono definiti durante la definizione della variabile.
Ogni variabile ha un tipo che indica appunto il tipo di dato che la variabile può contenere. Questo può essere uno dei seguenti
* Char
* Int
* Float
* Double
Il tipo di dato char contiene un valore numerico corrispondente al codice ascii di un carattere. Ha dimensione 1 byte e viene utilizzato per salvare caratteri. Il range di valori che può contenere, va da 0 a 255 se senza segno, oppure da -128 a 127 se con segno.
Il tipo di dato int è utilizzato per contenere numeri interi. La dimensione di questa variabile dipende dall'implementazione: nei moderni compilatori, il dato di tipo int ha dimensione 4 byte, ovvero 32 bit. Il range di valori per questo tipo va da 0 a (2^32)-1 se senza segno, altrimenti da -2^31 a +(2^31) - 1.
Il tipo di dato float è utilizzato per contenere valori in virgola mobile, a singola precisione. La dimensione di una variabile float è di 32 bit (ovvero 4 byte).
Il tipo di dato double è utilizzato per contenere valori in virgola mobile a doppia precisione. La dimensione di una variabile double è di 64 bit (ovvero 8 byte).
In aggiunta esistono alcuni qualificatori, che possono essere applicati a questi tipi. Per esempio, short e long sono associabili agli interi:
Lo scopo è quello di fornire, dove necessario, delle lunghezze diverse per gli interi: short indica, nel caso di prima, un intero di 16 bit, mentre un long uno di 32.
Oppure i qualificatori signed ed unsigned, per esempio, possono essere associati sia ai char che agli int.
N.B. I numeri unsigned sono sempre positivi o nulli, ed obbediscono alle leggi dell'aritmetica modulo 2^n, dove n è il numero di bit del tipo.
I nomi delle variabili in C devono seguire delle regole ben precise: il primo carattere del nome deve essere una lettera maiuscola o minuscola oppure un 'underscore' ('_'); non possono iniziare con un numero. Inoltre il C è case-sensitive: in poche parole, il C, contrariamente al Basic per esempio, distingue le lettere minuscole da quelle maiuscole. Questo vuol dire che se chiamate una variabile per esempio Prova, non potete poi chiamarla prova perché sono due nomi diversi.
La definizione di variabili in C, si effettua in questo modo:
tipo corrisponde ovviamente ad uno dei tipi elencati sopra. Mentre valore è un valore del tipo tipo. In queste due righe di codice, abbiamo dichiarato una variabile di nome nome_variabile in due modi differenti. Nella prima riga abbiamo solo dichiarato una variabile, ma non le è stato assegnato alcun valore. Ciò non ci permette di utilizzarla in modo sicuro, poiché potrebbe contenere un valore qualunque. Il secondo metodo ci permette invece di inizializzare la variabile al momento della dichiarazione; questo è il metodo che si dovrebbe sempre utilizzare per dichiarare una variabile.
Da quanto detto fino ad ora, è evidente che il C non definisce un tipo per il dato stringa. Questa però non vuol dire che non si possono gestire stringhe: le stringhe vengono infatti definite come vettori (o array) di caratteri, terminati da un carattere nullo. In seguito parleremo dei vettori e ci soffermeremo particolarmente sulle stringhe e la loro manipolazione.

## Operatori

Il C fornisce vari tipi di operatori: aritmetici, logici, ecc.
Operatori di assegnazione e confronto
La differenza tra l'operatore = e l'operatore == è molto importante, poiché se si vogliono confrontare due variabili e si usa l'operatore =, il risultato sarà solo l'assegnazione alla variabile a sinistra del valore della variabile a destra e verrà restituito tale valore.
Operatori aritmetici
Come è possibile notare da quest'elenco, alcuni operatori "includono" l'operatore uguale. Tali operatori sono in realtà delle scorciatoie ad alcune operazioni come mostrato negli esempi seguenti:
L'inserimento delle parentesi negli esempi precedenti non ha molto valore in questo caso, ma hanno un significato particolare. Quando si utilizzano questi operatori infatti, è necessario porgere molta attenzione a come vengono strutturate le istruzioni, poiché acquisiscono come 'secondo parametro' tutto ciò che è a destra dell'operatore come se fosse racchiuso tra parentesi. Di seguito sono infatti riportati alcuni esempi per spiegare meglio quanto descritto:
Questo esempio dovrebbe essere più che sufficiente per comprendere il modo corretto di utilizzare questi operatori.
Operatori logici
Operatori bit a bit
Gli operatori &= e |= funzionano nello stesso modo degli operatori +=, -=, *=, /=.

## Primo programma

I files sorgenti dei programmi scritti in C hanno estensione .c. Per creare un programma, è necessario definire una funzione particolare chiamata main. Questa funzione rappresenta l'entry point del programma, ovvero il punto di partenza. Questa funzione restituirà un valore intero che indicherà al sistema operativo o all'eventuale programma chiamante un codice di uscita, che servirà a capire se il programma è stato chiuso per un errore oppure se tutto è andato a buon fine. Le funzioni verranno comunque spiegate in dettaglio nel capitolo dedicato all'argomento.
Iniziamo creando un file prova.c. A questo punto è necessario includere un file di intestazione, in cui sono dichiarate le funzioni standard di input/output del C, di cui parleremo durante il resto del documento. Per far questo, iniziamo il programma in questo modo:
A questo punto dobbiamo dichiarare la funzione main, che come spiegato prima, ci permetterà di eseguire il programma. Ciò va fatto aggiungendo il seguente codice:
In questo modo abbiamo dichiarato la funzione main che per la semplicità estrema di questo programma di esempio, conterrà tutto il codice necessario. Se tentassimo di compilare il file prova.c così ottenuto
otterremmo un programma perfettamente funzionante ma che non esegue nessuna operazione (provare per credere). Per creare un primo programma che stampi la classica frase "Hello world!" sul video, utilizzeremo la funzione di libreria printf, di cui parleremo in seguito, in questo modo:
Questo codice va inserito tra le due parentesi graffe della funzione main in questo modo:
La sequenza di caratteri \n, è una sequenza di escape che consente di inserire un carattere new line in una stringa.
Se siete in ambiente linux, potete compilare ed eseguire questo file prova.c in questo modo:
dove $ indica la shell. In questo caso è stato utilizzato il compilatore gcc: il parametro -o prova indica che il file eseguibile deve chiamarsi prova; mentre prova.c è il file sorgente che deve essere compilato. In questo esempio ho mostrato anche l'output del programma.
Ora proviamo a modificare questo programma per eseguire qualche operazione in più: dichiariamo tre variabili, assegnamo loro alcuni valori, facciamone la somma e stampiamola:
In questo esempio abbiamo introdotto alcune novità: dichiarazione di più variabili in un'unica riga, novità sull'utilizzo della funzione printf, l'aggiunta dell'istruzione return 0. Andiamo per ordine. Precedentemente avevamo dichiarato sempre una sola variabile per volta, è possibile però dichiarare più variabili separandole con una virgola, purché siano tutte dello stesso tipo.
La variabile risultato non è stata inizializzata durante la dichiarazione perché viene comunque inizializzata alla riga 6, quando le viene assegnata la somma dei valori delle variabili a e b.
La funzione printf è una funzione che permette di stampare a video testo formattato. Essa riceve infatti almeno un parametro (secondo esempio) che definisce il formato dell'output. Come primo parametro infatti bisogna passare una stringa con eventualmente alcuni "segnaposto" che elencheremo prossimamente, poi un ulteriore parametro per ogni segnaposto inserito nella stringa e nello stesso ordine. Il risultato di questo programma sarà:

## Strutture di controllo

Le strutture di controllo sono delle particolari istruzioni, tipiche dei linguaggi imperativi, che permettono di eseguire delle istruzioni secondo determinate condizioni.
Teorema di Bohm-Jacopini Gli informatici Corrado Bohm e Giuseppe Jacopini, nel 1966 enunciarono il seguente teorema: Ogni algoritmo può essere implementato utilizzando tre sole strutture
* Sequenziali: blocchi di codice procedurale le quali istruzioni vengono eseguite nella stessa sequenza con le quali sono state scritte nel codice sorgente. Queste possono contenere strutture di controllo condizionali e/o iterative
* Condizionali: permettono di specificare due rami o blocchi (uno del vero ed uno del falso) di codice, di cui solo uno verrà eseguito in base al risultato booleano dell'espressione condizionale (vero/falso)
* Iterative: eseguono lo stesso blocco di codice, in modo ripetitivo, finquando l'espressione condizionale è vera

### Blocchi di istruzioni sequenziali

Questo tipo di struttura permette di eseguire una serie di istruzioni in sequenza ognuna terminata dal carattere ; (punto e virgola). Questa sequenza è quella dettata dal codice sorgente. Abbiamo visto gia' alcuni esempi precedenti di codice C ma la cosa importante da capire è che queste istruzioni verranno eseguite solo nel momento in cui il programma entrerà nel blocco di istruzioni, e non in altri momenti dell'esecuzione del programma o in sequenza diversa.
Pertanto, non potrà mai succedere che l'istruzione2 venga eseguita dopo l'istruzione3. Questo discorso vale anche per le istruzioni di assegnazione e di lettura delle variabili, per le quali bisogna fare attenzione all'irreversibilità dell'operazione di scrittura:
Come si può notare, con il primo printf si visualizzerà il numero 2 contenuto in var, ma con l'istruzione seguente il numero visualizzato è 3. Ciò significa che il valore 2 inserito in var è stato perso: l'operazione di assegnazione non mantiene in memoria i valori precedentemente scritti nelle variabili.

### Strutture condizionali
#### IF

Sintassi
Questa struttura funziona nel seguente modo: viene verificata la condizione dell'istruzione if, se essa risulta vera, vengono eseguite le istruzioni immediatamente successive, altrimenti, se risulta falsa, viene saltato il blocco dell'if (quindi non vengono eseguite le istruzioni interne all'if) e vengono eseguite le istruzioni immediatamente successive
Esempio

#### IF... ELSE

Sintassi
Esiste anche un modo molto più sintetico per scrivere l'istruzione if... else, ed è il seguente
condizione ? istruzioni1 : istruzioni2;
dove:
if = ? else = :
esempio: (numero == 7) ? numero++ : numero--;
Esempio
Esempio completo
In questo codice non ha molto senso contare i promossi e i bocciati (visto che viene acquisito un solo voto, ma tuttavia è a titolo di esempio). Vedremo invece come modificarlo, e quindi renderlo effettivamente funzionale, quando verranno introdotte le strutture iterative.

#### SWITCH (selezione multipla)

Sintassi
Molto simile alla logica dell'istruzione if... else, lo switch viene usato quando bisogna confrontare i diversi valori (case1, case2... casen) che una variabile o un'espressione (switch (val)) può assumere e quindi in base a essi eseguire azioni distinte.L'istruzione break serve per uscire dal blocco dello switch, infatti non avrebbe senso (in molti casi) continuare a confrontare ( ma questo non vuol dire che, in alcune situazioni, il break ci debba sempre essere).
Esempio

### Strutture iterative
#### WHILE

Sintassi
I comandi di iterazione consentono di eseguire ripetutamente determinate istruzioni finché una certa condizione rimane vera.
Esempio-Sommare i numeri da 1 a 100
Questo esempio mostra come avviene la somma dei numeri interi da 1 a 100 mediante l'uso del comando di iterazione while. In questo caso, per permettere l'uscita dal while, viene usata una variabile contatore; la variabile i, che deve essere prima di tutto inizializzata, poi incrementata (o decrementata a seconda dello scopo) per permettere che essa raggiunga il valore che non verificherà più la condizione del while
Esempio completo 1-Scrivere un programma che trovi il massimo tra 15 valori inseriti
Esempio completo 2-Scrivere un programma che prenda in input il lato di un quadrato (valori tra 1 e 15) e quindi lo disegni utilizzando asterischi

#### DO... WHILE

Il do... while è molto simile al while e ha la seguente sintassi:
Osservando la struttura del do... while si può notare che l'istruzione interna al blocco viene eseguita almeno una volta, senza tener conto se la condizione nel while sia vera o falsa.
Esempio
Riprendiamo ora l'esempio del conteggio dei bocciati e promossi visto nella sezione delle strutture condizionali e rendiamolo funzionale.
Esempio completo: scrivere un programma che legge da tastiera i voti di 10 studenti e quindi conta quanti promossi e bocciati ci sono stati

#### FOR

La sintassi del for è la seguente:
La logica del comando for è molto simile al while, infatti potrebbero essere benissimo usati per gli stessi scopi. Rimane il fatto che il for è molto più sintetico del while e viene usato quando si conosce già il numero di iterazioni che bisogna fare
Esempio1-Stampare a video una successione di cifre da 2 a 20
Esempio completo1-Sommare tutti gli interi pari da 2 a 100
Esempio completo2-Acquisire da tastiera 2 numeri, x e y, tra 1 e 20 e stampare in output una matrice di "?" x * y
Esempio3-Scrivere un programma che calcoli e quindi visualizzi i primi 10000 numeri primi
Istruzione break in un comando for
L'istruzione break (come si può intuire dall'esempio) provoca l'uscita immediata dal ciclo appena si verifica una specifica condizione.
Istruzione continue in un comando for
L'istruzione continue salta un blocco di istruzioni al verificarsi di una certa condizione, continuando però con l'iterazione.

## Funzioni

Quando si deve sviluppare un programma complesso , oppure di grandi dimensioni, un'ottima tecnica è quella di utilizzare delle funzioni. Le funzioni non sono altro che "pezzi"(meglio moduli) più piccoli del programma. È come se prendessimo il programma principale e lo dividessimo in piccole parti. Quindi un lavoro svolto in questo modo risulta sicuramente più lineare e agevole.
Le variabili dichiarate nella definizione di una funzione sono chiamate variabili locali La maggior parte delle funzioni contengono una lista di parametri
Dichiarazione di una funzione
Esempio completo1-Scrivere un programma che calcoli il quadrato dei numeri da 1 a 5 utilizzando una funzione "quadrato"
Quando viene chiamata (invocata) la funzione (in questo caso printf ("% d\t", quadrato (x))) è come se il valore di x viene copiato nel parametro y della funzione quadrato, quindi viene calcolato e ritornato come un intero (int quadrato...)al chiamante (programma principale)
Prototipi di funzione La logica è simile a una dichiarazione di variabile, solo che questa volta è una funzione. Il prototipo serve, quindi, per indicare al compilatore il tipo di dato che la funzione deve restituire , il numero di parametri che si aspetta di ricevere , l'ordine in cui sono attesi e il tipo di parametri. Se viene omesso il prototipo, il compilatore non riuscirà a riconoscere la funzione.
Esempio completo2-Scrivere un programma che somma 2 numeri a e b utilizzando una funzione "somma"
Esempio completo3-Trovare il maggiore tra 3 numeri utilizzando la funzione "maggiore"
FUNZIONE RAND
La funzione rand genera dei numeri casuali compresi tra 0 e RAND_MAX (costante simbolica che viene definita nel file d'intestazione stdlib.h)
1°Caso Caso in cui la funzione rand genera un numero compreso tra 0 e rand_max
2°Caso
Utilizziamo un seme
Possiamo notare che quando il seme è uguale a 10la sequenza di numeri è la stessa. Lo steesso avviene quando il seme è rand_max (essendo questa una costante).
Per ovviare al problema possiamo utilizzare una istruzione di questo tipo
srand(time(NULL))
che permette la lettura dell'orologio interno al sistema, ottenendo cosi automaticamente un valore per il seme. La funzione time restituisce l'ora espressa in secondi. Il prototipo di questa funzione si trova nel file d'intestazione time.h.
Esempio1-Simulare il lancio di un dado 10 volte
L'istruzione faccia = 1 + rand%6 genera un numero compreso uguale tra 1 e 6. Una divisione intera per 6 può generare i resti (0, 1, 2, 3, 4, 5), per questo motivo viene aggiunto 1.
Esempio3-Simulare il lancio di un dado 500 volte e verificare quante volte esce 1, 2, 3, 4, 5, 6

### Ricorsione

Prendiamo come esempio la successione
{2n}
essa è definita ricorsivamente in questo modo
    $a_{0}=0$
    $a_{n}=a(n-1)+2$
Partendo da $a_{0}=0$ si ha che
Il termine successivo è $a_{1}=a_{0}+2=0+2=2$
Quello ancora successivo è $a_{2}=a_{1}+2=(a_{0}+2)+2=4$ e cosi via
È importante individuare
* La clausola base che serve per indicare il primo termine della successione
* La clausola induttiva che serve ad indicare le operazioni da compiere per calcolare i valori successivi

#### La serie di Fibonacci

0, 1, 1, 2, 3, 5, 8, 13, 21...
Può essere definita in modo ricorsivo come segue
fibonacci(0) = 0
fibonacci(1) = 1
fibonacci(n) = fibonacci(n-1) + fibonacci(n-2)
Vale a dire che l'n-esimo numero di fibonacci viene calcolato sommando i due termini precedenti (fibonacci (n-1) + fibonacci (n-2)
Ad esempio se volessimo calcolare fibonacci (3) la sequenza sarebbe la seguente
f(3) = f(2) + f(1) ritorna 1 (f(1))
f(2) = f(1) + f(0) ritorna 1 (f(1)) e ritorna 0 (f(0))
quindi
f (3) = 2 infatti il quarto (la successione parte da n = 0) numero di fibonacci è proprio il 2
* Versione ricorsiva
* Versione Iterativa

## Arrays

Un array non è altro che un puntatore. Il linguaggio C consente di "vedere" i puntatori in due modi diversi:
* Appunto, come puntatori; in questo caso sono disponibili gli operatori "indirizzo di", "valore di", gli operatori di incremento, di decremento, ecc.
Esempio: int i; int* ptr = &i;.
* Arrays. In questo caso il linguaggio rende disponibili gli operatori di accesso agli array.
Esempio: int array = ...; int value = array[...];.
Nota: è sempre scorretto utilizzare il termine "vettore" per indicare un array. I vettori sono entità definiti nelle librerie standard (#include <vector.h>).
La dimensione dello spazio di memoria allocato da un array, se espresso in bytes, è pari al prodotto tra il numero di elementi dell'array stesso e la dimensione, in bytes, del tipo di dato a cui esso fa riferimento.
Ad esempio:
* int array[4];
Se un valore di tipo 'int' occupa 4 bytes in memoria (per i processori a 32 bits è, in generale, così), la memoria allocata da array è pari a 16 bytes.

### Esempi di utilizzo

Dichiarazione di arrays
Sintassi
Esempio1-Definire un array e inizializzarlo con un ciclo for
Bisogna notare la differenza tra il terzo elemento del vettore (V[2] = 4) e l'elemento 3 del vettore (V[3] = 6), visto che l'indice parte da 0
Esempio2-Definire (con una costante simbolica), inizializzare da tastiera e visualizzare un vettore V di 5 elementi interi
Esempio3-Sommare gli elementi di un vettore V di 5 interi, calcolare la media e visualizzare il vettore
Esempio4-Rappresentare graficamente gli elementi di un vettore con dei "+"
Esempio5-Lanciare un dado 500 volte e riassumere i risultati in un vettore

#### Esempi di operazioni su numeri primi

Esempio6-Il crivello di Eratostene

### Passare i vettori alle funzioni

I vettori vengono passati alle funzioni per riferimento (viene passato l'indirizzo di memoria e non solo il valore), infatti il nome del vettore è l'indirizzo del primo elemento. Questo significa che, quando all'interno della funzione avvengono delle modifiche al vettore, la funzione starà modificando gli elementi originali del vettore e non una loro copia, quindi la modifica avrà effetto anche sul chiamante (programma principale) e non solo all'interno della funzione.
Esempio1-Inizializzare e visualizzare un vettore con delle funzioni
La parola VOID indica alla funzione che non deve ritornare alcun valore.
Esempio2-Passare alle funzioni un vettore , modificarlo e modificare un singolo elemento del vettore
In questo esempio bisogna notare come gli elementi del vettore vengono modificati. Quando viene chiamata la funzione "modifica" viene passato l'intero vettore, e quindi i suoi elementi sono passati per riferimento (viene modificato non solo all'interno della funzione "modifica" ma anche nel programma principale) modificando il vettore originale. Mentre quando viene chiamata la funzione "modifica_elem" viene passato un singolo elemento del vettore. Questo provocherà un passaggio per valore e non per riferimento (viene passato un singolo elemento e non l'intero array) , quindi la modifica avrà effetto solo all'interno della funzione "modifica_elem" e non all'interno della funzione main (principale)

### Ordinamento

In questo esempio utilizzeremo la tecnica del Bubble Sort
Esempio- Scrivere un programma che ordini dal più piccolo al più grande gli elementi di un array (implementare il programma con delle funzioni)

### Ricerca
#### Ricerca lineare

In questo esempio utilizzeremo la tecnica della ricerca lineare
Esempio-Ricerca lineare

#### Ricerca binaria
### Arrays multidimensionali (matrici)

Per rappresentare tabelle (dette anche "matrici") vengono usati degli arrays con più di un indice.
es. m 3 X 4
Una matrice può essere vista come un array i cui elementi siano a loro volta altri arrays (e così via), quindi un puntatore a puntatore (a puntatore...)
Nel caso particolare di una matrice del tipo m[i][j], i due indici i e j vengono detti rispettivamente "indice di riga" e "indice di colonna" (ma, ovviamente, il fatto di adottare il primo indice per le righe ed il secondo per le colonne è puramente convenzionale).
* DICHIARAZIONE
Viene dichiarata una matrice 4 X 4 di nome m.
I valori nel primo insieme di parentesi inizializzano la riga 0; quelli del secondo insieme, la riga 1... e cosi via
Esempio1-Dichiarazione e inizializzazione
Esempio2-Scrivere un programma che: inizializza, visualizza, trova il massimo, la media e la media delle righe di una matrice. Il tutto deve essere implementato con delle funzioni
Esempio3-Scrivere un programma che inizializzi con dei valori casuali una matrice 4*4, visualizzi la matrice in formato tabulare, calcoli la matrice trasposta e visualizzi quest'ultima sempre in formato tabulare. Il programma deve essere implementato mediante funzioni
Esempio4-Scrivere un programma che ricerca il valore massimo e il valore minimo in una matrice di interi 4*4. Il programma deve tenere conto anche se il valore del minimo e del massimo si è presentato più volte. Bisogna implementare il tutto mediante funzioni
Esempio5- Inizializzare una matrice di interi 4*4 e ordinarla in modo ascendente

## Introduzione ai puntatori

I puntatori sono un particolare tipo di dato presente nel C.
Una variabile di tipo puntatore contiene un indirizzo di memoria nel quale è salvato un particolare tipo di dato.
Quando noi definiamo una variabile:
int n;
a questa variabile al momento dell'esecuzione del programma viene assegnato uno spazio in memoria (4 byte).
Noi per modificare il valore di questa variabile scriviamo semplicemente n = 5...
Un altro modo per modificarne il valore è però quello di passare per il suo indirizzo in memoria.
I puntatori ci permettono appunto di fare questo.
Nel mio esempio la cosa ha poco senso, ma può essere fondamentale quando si usano le funzioni.
I puntatori si possono considerare croce e delizia della programmazione : talvolta possono essere un po' difficili da comprendere, per questa natura di far riferimento ad un dato indirettamente anziché direttamente.Però mano a mano che un programma diventa complesso , il puntatore diventa utilissimo per consentire il risparmio della memoria.
ESEMPIO 1. Ci sono delle situazioni in cui bisogna manipolare uno stesso dato più volte contemporaneamente. Possiamo usare i puntatori per far più volte riferimento allo stesso dato anziché fare tante copie di quel dato e affollare la memoria del computer.

### Utilizzo dei puntatori

Vediamo ora come si usa questo nuovo tipo di dato.
Un puntatore va dichiarato specificando anche il tipo di dato a cui si riferisce:
In fase di dichiarazione * specifica che si tratta di un puntatore, poi avrà un altro significato.
int indica che si riferisce a un'area di memoria in cui è contenuto un valore int.
Altro comando per usare i puntatori è &.
Questo comando restituisce l'indirizzo in memoria di una variabile.
Ad esempio &n da come risultato l'indirizzo in memoria di n:
Ora sappiamo dove è salvata n in memoria, come facciamo a cambiarne il valore?
Semplice, ci viene in aiuto un altro comando, l'operatore di indirezione * .
*punt equivale a n.(questa operazione si chiama dereferenziazione).
Come n può essere usato sia a destra che a sinistra del segno di uguale.
E se è posta a sinistra l'assegnamento modifica il valore effettivo di n in memoria:

### Puntatori e funzioni
#### Passaggio dei parametri per riferimento

I parametri in C vengono passati alle funzioni per valore. Questo significa che durante l'esecuzione di un programma ogni chiamata ad una funzione dovrà ricevere una copia di tali parametri. Inoltre, ogni volta che la funzione tenterà di modificare un parametro ricevuto, questa avrà effetto solo all'interno della funzione stessa; possiamo dire che avrà effetto sulla copia del parametro e non sull'originale. Esistono ovviamente casi in cui c'è la necessità di dover modificare una variabile passata a una funzione e altri casi in cui la copia di un parametro passato per valore sarebbe troppo dispendioso l'utilizzo in termini di tempo e memoria nel caso di grandi quantità di dati. Il C permette di simulare un passaggio per riferimento alle funzioni attraverso l'uso dei puntatori. La funzione non riceverà più una copia della variabile ma l'indirizzo di tale variabile. In questo modo ogni modifica sul parametro passato avrà effetto anche sulla variabile originale. Consideriamo il classico esempio della funzione swap che scambia due variabili. Nella prima versione i parametri sono passati per valore, nella seconda vengono passati per riferimento.

### Array e puntatori

Array e puntatori in C sono strettamente legati. È importante comprendere questa relazione per due ragioni principalmente: avere un'idea di come il C è stato progettato e capire i programmi esistenti. Questo aiuterà a possedere una buona conoscenza di questo linguaggio. Il nome di un array in realtà altro non è che il puntatore al suo primo elemento. Quindi nello scrivere char a[100], verrà definito un array a di 100 caratteri dove a sarà il puntatore al suo primo carattere a[0]. Consideriamo il seguente frammento di codice:
Per le considerazioni fatte si noti come scrivere pa = a e pa = &a[0] equivale alla stessa cosa. Entrambe le assegnazioni permettono di far puntare pa all'array a e, più precisamente, al primo elemento dell'array a. Si può quindi accedere ad a attraverso pa. Scrivendo *pa = 'C'; inseriamo il carattere C nel primo elemento dell'array a, a[0] ora contiene C.

### Aritmetica dei puntatori

Non è molto interessante osservare che un puntatore punti ad un elemento di un array. È interessante invece capire come, attraverso l'aritmetica dei puntatori (o aritmetica degli indirizzi), si possono elaborare vettori in modo alternativo. I puntatori prendono il posto degli indici di un array. In C le uniche forme di aritmetica dei puntatori sono le seguenti tre:
* Somma intero con puntatore
* Sottrazione intero con puntatore
* Sottrazione puntatore con puntatore

##### Somma di un intero a un puntatore

La somma di un intero j a un puntatore p che punti a un elemento del vettore a, sposterà quel puntatore di j posizioni avanti rispetto a dove stava puntando p in precedenza. In altre parole se consideriamo il seguente frammento di codice:
pa inizialmente sta puntando al quarto elemento dell'array a, pa = &a[3]. L'istruzione pa = pa + j farà in modo che pa punti all'ottavo elemento a[7].
In generale diciamo che se p punta ad a[i], allora p + j punterà ad a[i + j], sempre se a[i+j] esista.

##### Sottrazione di un intero a un puntatore

Analogo discorso si fa per quanto riguarda la sottrazione di un intero a un puntatore. Consideriamo ancora il seguente frammento:
pa inizialmente punta ad a[7]. In questo caso l'istruzione pa -= j farà in modo che pa punti all'elemento a[1] dell'array. In generale, se un puntatore p punta ad un elemento di un array a[i], allora l'operazione p - j farà puntare p ad a[i-j], sempre se a[i-j] esista.

##### Sottrazione di un puntatore a un puntatore

L'operazione di sottrarre un puntatore ad un altro puntatore restituirà un intero come risultato che rappresenta la distanza tra i due puntatori, cioè mi dice quanti elementi del vettore ci sono tra i due puntatatori. Consideriamo il seguente frammento di codice:
Attenzione! Eseguire questo tipo di operazioni richiede che i puntatori puntino ad elementi di un array. In particolar modo eseguire una sottrazione tra puntatori richiede che entrambi puntino allo stesso array.

### Puntatori a puntatori

Il discorso fatto per la variabile di tipo int vale in generale per qualsiasi tipo di dato, anche per gli stessi puntatori.
La cosa può sembrare strana, ma si possono dichiarare anche puntatori a puntatori.
Il loro utilizzo si basa comunque sulle regole scritte sopra e verrà specificato nel capitolo sull'allocazione della memoria dove dovranno essere usati per alcune funzioni.

### Stampare il contenuto di un puntatore

Infine può essere utile poter stampare il valore di una variabile di tipo puntatore.
Il suo contenuto è un indirizzo di memoria in formato esadecimale.
La direttiva da inserire nella printf per stampare un indirizzo è %p:
In alternativa è anche possibile stamparlo nella forma esadecimale con % x:
Esempio-Stampare la locazione e il valore di una variabile intera
Scrivendo *p, la variabile puntatore p viene dereferenziata, quindi assume il valore effettivo a cui punta (in questo caso il valore di var, che nell'esempio è 26). Essendo un intero viene usata la specifica % d, mentre p contiene l'indirizzo di memoria di var, e quindi viene passata la specifica %p.

## Allocazione dinamica della memoria

L'allocazione dinamica della memoria è gestita dalle funzioni seguenti:
La funzione calloc alloca memoria per un vettore di n elementi di dimensione size. La memoria è inizializzata a zero.La funzione restituisce il puntatore ad una zona di memoria allocata in caso di successo e NULL in caso di fallimento.
Per esempio:
Alloca size byte senza inizializzare la memoria. La funzione restituisce il puntatore ad una zona di memoria allocata in caso di successo e NULL in caso di fallimento.
Per esempio (utilizzando una struct):
Cambia la dimensione del blocco allocato all'indirizzo *ptr portandola a size. La funzione restituisce il puntatore ad una zona di memoria allocata in caso di successo e NULL in caso di fallimento.
Libera lo spazio di memoria puntato da * ptr. Se *ptr è NULL non fa niente.
Per esempio:

## Le Librerie

Una libreria è un insieme di tools che realizzano specifiche funzioni. Senza librerie il linguaggio c si dimostrerebbe inutile, dato che non possiede funzioni native a differenza di altri linguaggi.
Le librerie standard contengono materialmente queste funzioni, e quindi sono una sorta di "collezione" delle funzioni scritte per un certo sistema operativo. Ma è possibile anche creare nuove librerie che contengano al loro interno determinate funzioni da richiamare nel programma principale o relativi sottoprogrammi.
Per creare delle librerie, per poi andarle ad includerle nel programma principale, occorre dapprima creare un file .c e un file .h, che racchiudono le eventuali operazioni che deve svolgere la nuova libreria, e la chiamata della libreria nel programma.
Esempio:
Creando un file chiamato prova.c si vanno ad includere tutte le librerie che si trovano nel progetto creato.
Creando un file