Una funzione può:
Una funzione viene definita nel seguente modo:
Ogni funzione presenta un valore di ritorno:
All’atto della chiamata di una funzione il controllo nell’esecuzione viene passato alla prima istruzione del corpo della funzione stessa.
Esistono due modi per restituire il controllo al programma chiamante:
Nel primo caso l’espressione viene, se necessario, convertita al tipo ritornato dalla funzione.
E’ opportuno controllare sempre che la chiamata di una funzione ed il suo valore di ritorno siano consistenti.
Il metodo di passaggio delle variabili è per valore, ossia, nella funzione chiamata si farà una copia locale delle variabili passate e non si potrà modificarne il loro valore globale, a meno di non passare alla funzione il “riferimento” della variabile.
E’ responsabilità del programmatore controllare che il numero ed il tipo degli argomenti passati ad una funzione corrisponda con quanto specificato nella dichiarazione della funzione stessa.
Con l’introduzione del prototipi ad ogni funzione è assegnato un prototipo di chiamata, quindi il compilatore è in grado di controllare tipi e numero di argomenti passati.
Il prototipo di una funzione non rappresenta altro che una dichiarazione di funzione antecedente alla sua definizione.
int function(int x, char c); int function(int,char);
Nell’ANSI C l’uso dei prototipi è obbligatorio quando la definizione di una funzione avviene successivamente al suo utilizzo.
Lo scopo dei prototipi delle funzioni è quello di permettere al compilatore di compiere il controllo sui tipi degli argomenti che vengono passati ad una funzione.
Se i prototipi vengono inseriti prima della definizione di una funzione, quando il compilatore incontra la dichiarazione conosce il numero ed i tipi degli argomenti e può controllarli.
La dichiarazione e la definizione di una funzione devono essere consistenti sia nel numero che nel tipo dei parametri.
Attenzione se dichiarazione e definizione di funzione si trovano nello stesso file sorgente eventuali non corrispondenze nei tipi degli argomenti saranno rilevati dal compilatore, in caso contrario al più ci sarà un warning.
Un array è una collezione di variabili dello stesso tipo che condividono un nome comune.
Un array viene dichiarato specificando il tipo, il nome dell’array e uno o più coppie di parentesi quadre contenenti le dimensioni.
Esempio:
Il C memorizza i valori degli elementi di un array in locazioni consecutive di memoria.
E’ molto importante conoscere come sono stati memorizzati gli elementi poiché questo consente di capire come sia possibile puntare ad un preciso elemento.
In C è obbligatorio specificare in modo esplicito la dimensione di un array in fase di dichiarazione.
In una definizione di funzione, come vedremo, non occorre specificare la dimensione del vettore passato come parametro; sarà il preprocessore a risolvere l’ambiguità all’atto della chiamata.
Per accedere ad un array si usano gli indici.
E’ compito del programmatore fare in modo di non andare oltre i limiti di dimensione dell’array in questione.
Gli array partono dall’indice 0 fino alla lunghezza dichiarata meno 1;
Referenziare un array al di fuori dei suoi limiti può portare a errori di indirizzamento (memory fault) oppure può “sporcare” altre variabili in memoria diventando così molto difficile da localizzare.
Per copiare elementi da un array all’altro bisogna copiare singolarmente ogni elemento.
Una stringa è un array monodimensionale di caratteri ASCII terminati da un caratter null ‘\0′
Ad esempio “Questa è una stringa” è un array di 21 caratteri.
L’array è quindi il seguente:
elemento zero ‘Q’
primo elemento ‘u’
secondo elemento ‘e’
…………
ventesimo elemento ‘a’
ventunesimo elemento ‘\0′
Il metodo di default di passaggio delle variabili è per valore, quindi si potrà modificare solo una copia locale della variabile.
Eccezione a questa regola globale sono gli array.
Gli array, infatti, vengono sempre passati per reference ossia è il loro indirizzo che viene passato invece del loro contenuto.
Questo consente, solo nel caso dei vettori, di poter agire direttamente sulla variabile e non sulla copia. Ovvero le azioni che si effettuano sull’argomento della funzione avranno effetto anche al termine dell’esecuzione della funzione.
Un puntatore è una variabile che contiene l’indirizzo di un’altra variabile.
I puntatori sono “type bound” cioè ad ogni puntatore è associato il tipo a cui il puntatore si riferisce.
Nella dichiarazione di un puntatore bisogna specificare un asterisco (*) di fronte al nome della variabile pointer.
Esempio:
int *pointer; = puntatore a intero
char *pun_car; = puntatore a carattere
float *flt_pnt; = puntatore a float
Prima di poter usare un pointer questo deve essere inizializzato, ovvero deve contenere l’indirizzo di un oggetto.
Per ottenere l’indirizzo di un oggetto si usa un operatore unario &.
int volume, *vol_ptr;
vol_ptr = &volume;
Il puntatore vol_ptr contiene ora l’indirizzo della variabile volume.
Per assegnare un valore all’oggetto puntato da vol_ptr occorre utilizzare l’operatore di indirezione ” * “.
*vol_ptr= 15;
Vantaggi nell’uso dei puntatori:
Aritmetica dei puntatori:
Il preprocessore C è una estensione al linguaggio che fornisce le seguenti possibilità:
I comandi del preprocessore iniziano con # nella prima colonna del file sorgente e non richiedono il “;” alla fine della linea.
Un compilatore C esegue la compilazione di un programma in due passi successivi. Nel primo passo ogni occorrenza testuale definita attraverso la direttiva # viene sostituita con il corrispondente testo da inserire (file, costanti, macro)
Attraverso la direttiva #define del preprocessore è possibile definire delle costanti:
Sintassi
E’ uso comune usare lettere maiuscole per le costanti di #define
Perché usare queste costanti?
L’suo della direttiva #define consente anche di definire delle macro.
Una macro è una porzione di codice molto breve che è possibile rappresentare attraverso un nome; il preprocessore provvederà ad espandere il corrispondente codice in linea.
Una macro può accettare degli argomenti, nel senso che il testo da sostituire dipenderà dai parametri utilizzati all’atto della chiamata. Il preprocessore espanderà il corrispondente codice in linea avendo cura di rimpiazzare ogni occorrenza del parametro formale con il corrispondente argomento reale.
Anche se ciò può trarre in inganno, le macro NON sono funzioni.
Per esempio sugli argomenti delle macro non esiste controllo sui tipi.
Inoltre, una chiamata del tipo : MAX (i++, j++) verrà sostituita con:
( (i++ > j++) ? (i++) : (j++);
E’ importante stare attenti all’uso delle parentesi, ad esempio in:
#define square(x) x*x
Una chiamata del tipo: x = square(3+1); , genera:
x = 3 + 1 * 3 + 1; –> x = 7 ??????
Le direttive :
consentono di associare la compilazione di alcune parti di codice alla valutazione di alcune costanti in fase di compilazione.
#if < espressione_costante>
< statement_1>
#else
#endif
Se l’espressione costante specificata, valutata in compilazione ritorna TRUE allora verranno compilati gli statement_1 altrimenti verranno compilati gli statement_2
1. Introduzione al Corso - Il Linguaggio C (I parte)
2. Linguaggio C – Seconda Parte
3. Ordinamento, Ricorsione e Code di Priorità
4. Esercitazione su Ricorsione e Code di Priorità
5. Stack e Code
6. Esercitazione di Laboratorio su Stack e Code
7. Implementazioni di Liste puntate
8. Esercitazione di laboratorio su Liste Puntate Semplici
9. Implementazioni di Liste Doppiamente Puntate e Circolari
10. Esercitazione di laboratorio su Liste Doppiamente puntate
12. Esercitazione di laboratorio su Alberi Binari di Ricerca
13. Alberi Binari di Ricerca. Cancellazione di un nodo
14. Esercizio di Laboratorio. Gioco su alberi
15. Grafi: Implementazione ed operazioni di base
16. Esercitazione di laboratorio: Implementazione operazioni di bas...
17. Grafi: Inserimento e Cancellazione di un nodo. Visite in ampiez...
18. Esercitazione di laboratorio: Problema del venditore Prima part...
19. Componenti fortemente connesse e alberi minimi di copertura
20. Esercitazione di laboratorio: Problema del venditore Seconda pa...