DICHIARAZIONE DI UNA FUNZIONE (PROTOTIPO)
La dichiarazione di una funzione, nota anche come prototipo, è come un "biglietto da
visita" della funzione. Il suo scopo è specificare al compilatore come deve essere
utilizzata la funzione prima che questa venga effettivamente definita.
- Sintassi: La sintassi generale è tipo_ritorno nome_funz (tipo_par1, ...,
tipo_parN);.
- Componenti:
tipo_ritorno: Indica il tipo di valore che la funzione restituirà al programma
chiamante al termine della sua esecuzione. Ad esempio, se la funzione calcola
un numero intero, il tipo_ritorno sarà int.
nome_funz: È il nome identificativo della funzione, quello che useremo per
richiamarla.
Parentesi (): Racchiudono la lista dei parametri.
Lista dei parametri: Contiene il tipo di ciascun parametro che la funzione si
aspetta di ricevere. Opzionalmente, si può specificare anche il nome del
parametro, ma non è strettamente necessario nella dichiarazione (tipo [nome]).
- Caso void: Se il tipo_ritorno è void, significa che la funzione non restituisce alcun
valore al chiamante. In questo caso, la funzione viene genericamente chiamata
procedura
DEFINIZIONE DI UNA FUNZIONE
La definizione di una funzione è la parte in cui si scrive il codice effettivo che la
funzione eseguirà. È qui che si implementa la logica della funzione. Questa fase
prevede
- Sintassi: tipo_ritorno nome_funz (tipo_par1 par1, ..., tipo_parN parN) { ...corpo
della funzione... }.
- Parametri formali: I parametri dichiarati nella definizione (es. par1, parN) sono
chiamati parametri formali.
- Importanza dei parametri:
È importante l'ordine in cui sono dichiarati.
È importante il tipo di ciascun parametro.
È importante riconoscere se un parametro è di ingresso (la funzione lo usa), di
uscita (la funzione lo modifica per restituire un risultato) o di ingresso-uscita (lo
usa e lo modifica).
CHIAMATA DI UNA FUNZIONE
Per utilizzare una funzione, è necessario chiamarla (o invocarla) dal programma
principale (main) o da un'altra funzione. Durante la chiamata, si passano i parametri
effettivi, che sono i valori o le variabili che la funzione userà per la sua esecuzione.
•Requisiti dei parametri effettivi:
◦Devono avere lo stesso tipo della dichiarazione (o tipi compatibili per conversione
implicita).
◦Devono essere passati nello stesso ordine della dichiarazione.
◦Il loro nome può essere anche diverso dai nomi dei parametri formali.
ALLOCAZIONE DI MEMORIA E VISIBILITA’ DELLE VARIABILI
Quando una funzione viene chiamata, il sistema gestisce la memoria in un modo
specifico:
1.Viene allocato spazio in memoria per i parametri formali della funzione chiamata.
2.Il valore dei parametri effettivi viene copiato negli spazi allocati per i rispettivi
parametri formali. Questo è il meccanismo del "passaggio per valore".
3.Viene allocato spazio per le variabili locali dichiarate all'interno della funzione.
4.La funzione viene eseguita.
5.Al termine dell'esecuzione della funzione, lo spazio delle variabili locali viene
deallocato.
6.Successivamente, lo spazio dei parametri formali viene deallocato.
7.L'esecuzione procede con il programma chiamante (ad esempio, il main).
•
Osservazioni cruciali:
Alla funzione vengono passati i valori dei parametri effettivi, non le variabili stesse.
Di conseguenza, tutti i cambiamenti effettuati sulle variabili locali e sui parametri
formali all'interno della funzione vengono persi una volta che la funzione termina la
sua esecuzione.
Questo comportamento è ideale per i parametri di ingresso, poiché la funzione usa i
valori ma non modifica le variabili originali.
Tuttavia, può rappresentare un problema per i parametri di uscita, poiché se si
desidera che la funzione modifichi una variabile nel chiamante, il semplice passaggio
per valore non è sufficiente.
•Variabili globali e locali: Il documento ribadisce che anche la funzione main è una
funzione come le altre. Si sconsiglia l'uso di variabili globali se non assolutamente
necessario, poiché possono rendere il codice più difficile da gestire e "debuggare" a
causa della loro visibilità da qualsiasi parte del programma.
PARAMETRI DI USCITA: IL CASO DEL VETTORE
La gestione dei vettori (array) come parametri di funzione ha una peculiarità:
•Quando si passa un vettore a una funzione (ad esempio, per stamparlo o leggerne gli
elementi), si dichiara la funzione in un modo simile a void stampa(int v[], int n); o void
stampa(int v, int n);.
•Alla chiamata, si passa semplicemente il nome del vettore, ad esempio
stampa(vettore, 100);.
•Cosa viene passato? A differenza delle variabili primitive (come int, float), quando si
passa un vettore, non viene copiato l'intero vettore. Viene invece passato l'indirizzo
del primo elemento del vettore in memoria
•Cosa comporta? Questo significa che la funzione opera direttamente sulla memoria
del vettore originale [non esplicitamente detto nella fonte, ma implicito dal
comportamento che segue e dalla domanda "Cosa comporta?" in]. Pertanto, qualsiasi
modifica apportata agli elementi del vettore all'interno della funzione sarà visibile
anche nel programma chiamante dopo che la funzione è terminata. Questo rende i
vettori un'eccezione al "passaggio per valore" generale e li rende adatti a fungere da
"parametri di uscita" impliciti, permettendo alla funzione di popolare o modificare dati
che saranno poi usati dal chiamante. Il documento lo suggerisce ponendo le domande
"Parametri di ingresso?" e "Parametri di uscita?" in.
Il documento introduce il problema di come gestire più parametri di uscita da una
funzione. L'esempio classico è lo scambio di due variabili (swap).
•Un tentativo intuitivo di implementare swap (es. void swap(int a, int b) { int temp =
a; a = b; b = temp; }) non funziona come desiderato. Questo perché, come spiegato
prima, i cambiamenti ai parametri formali a e b vengono persi al termine della
funzione, e le variabili originali nel chiamante rimangono inalterate.
•Il documento conclude questa sezione affermando che si ritornerà su questo
problema dopo aver studiato i puntatori. I puntatori sono il meccanismo in C che
permette di passare l'indirizzo di una variabile a una funzione, consentendo alla
funzione di accedere e modificare direttamente il valore della variabile originale nel
contesto del chiamante, risolvendo così il problema dei parametri di uscita multipli o
della modifica di variabili primitive.
OVERLOAD DELLE FUNZIONI
Il termine "overload" (o sovraccarico) si riferisce alla capacità di avere funzioni diverse
che condividono lo stesso nome ma si distinguono per altri aspetti. In generale, in
programmazione (e in C, per quanto riguarda le convenzioni del compilatore), funzioni
diverse possono distinguersi per:
•Il nome (es. somma vs max).
•Il numero dei parametri (es. float somma(float a, float b); vs float somma(float a, float
b, float c);).
•L'ordine dei parametri (es. funzione(int a, float b); vs funzione(float a, int b);).
•In C, le funzioni sono identificate principalmente dal loro nome e dai tipi/numero dei
loro parametri. Il tipo di ritorno non è sufficiente da solo a distinguere due funzioni con
lo stesso nome e gli stessi parametri (sebbene il documento mostri esempi come float
somma(float a, float b); e double somma(float a, float b); dove il tipo di ritorno è
l'unica differenza apparente tra funzioni con lo stesso nome e stesso numero/tipo di
parametri, in C puro questo non è "overloading" nel senso di C++, ma semplicemente
funzioni con lo stesso nome che il compilatore potrebbe non distinguere facilmente
senza altri meccanismi come la firma completa che include il tipo di ritorno per alcune
implementazioni o l'uso di linkaggio esterno che distingue per nome mangiato).
FILE
Le variabili in C esistono solo durante l'esecuzione del programma: una volta
terminato, tutti i dati contenuti in esse vengono persi. È necessario quindi introdurre il
concetto di file.
Un file è una unità di memorizzazione dati gestita dal FileSystem.
Il suo scopo principale è consentire la memorizzazione di informazioni su memorie di
massa (come dischi o CD) in modo non volatile, così che i dati memorizzati nelle
variabili di un programma non vanno persi al termine dell'esecuzione del programma
Concettualmente, un file è una sequenza di registrazioni uniformi, ovvero dello stesso
tipo. Possono essere visti come un'astrazione di
memorizzazione con una dimensione potenzialmente illimitata (ma non infinita) e un
accesso sequenziale.
A livello di sistema operativo, ogni file è identificato in modo univoco dal suo nome
assoluto, che include il percorso e il nome relativo. I dati all'interno di un file sono
salvati come byte
Esistono due tipologie di file:
- File di testo: Ogni byte è la rappresentazione ASCII di un carattere.
n pratica, è come leggere un normale documento di testo: quello che si vede è
esattamente quello che è contenuto nel file.
In C, un flusso di testo è una sequenza di caratteri, spesso organizzata in righe
che finiscono con il carattere speciale di "a capo" (\n).
Quando scriviamo un numero in un file di testo (per esempio un int), il numero
viene convertito in caratteri. Ad esempio, il numero 123 viene memorizzato
come i caratteri '1', '2' e '3'. Questo significa che lo spazio occupato nel file
dipende dal valore del numero.
- File binari: Ogni byte è la rappresentazione dell'informazione specifica
dell'applicazione (ad esempio, Word, Excel). Nel linguaggio C, un flusso binario è
semplicemente una sequenza di byte, esattamente uguale a come i dati sono
memorizzati nella RAM del computer. Non ci sono conversioni in caratteri o
formattazioni.
Nel linguaggio C, i file sono visti come sequenze lineari di byte. Il C offre solo alcune
funzioni di base per operare sui file. È importante notare che nel C non esiste il
concetto di record o di chiave di ricerca e, di conseguenza, non ci sono funzioni
predefinite per l'inserimento, la modifica, la cancellazione o la ricerca di record
all'interno di un file. Le operazioni consentite sono
- la creazione
- rimozione
- lettura
- scrittura di byte/testo da/su file
Quando un programma in C deve leggere o scrivere qualcosa, non si collega
direttamente al dispositivo fisico (come hard disk o monitor). Viene infatti utilizzato un
canale intermedio che prende il nome di flusso o stream. Dobbiamo seguire tre
passaggi fondamentali
-
Sottoprogrammi
-
Calcolatori elettronici I - Sottoprogrammi e parametri su Stack
-
Sottoprogrammi
-
Calcolatori elettronici I - Sottoprogrammi e parametri su Stack