Sei sulla pagina 1di 6

LE FUNZIONI IN C (Capitolo 5)

L’esperienza ha dimostrato che il modo migliore, per sviluppare e amministrare un programma corposo, è di
costruirlo partendo da pezzi più piccoli o moduli ognuno dei quali sia più maneggevole del programma
originale. Questa tecnica è detta divide et impera.

I moduli in C sono chiamati funzioni. I programmi C sono scritti tipicamente combinando le nuove funzioni
scritte dal programmatore con quelle "preconfezionate" disponibili nella libreria standard del C. In questo
capitolo discuteremo di entrambi i tipi di funzione.
La libreria standard del C fornisce una ricca collezione di funzioni per eseguire i comuni calcoli matematici,
per la manipolazione delle stringhe e dei caratteri, per l’input/output e per molte altre operazioni utili. Ciò
renderà più semplice il lavoro del programmatore, poiché le suddette funzioni forniranno molte delle
capacità di cui egli avrà bisogno.

Le funzioni sono invocate da una chiamata di funzione che specifica il nome della funzione e fornisce delle
informazioni (gli argomenti) di cui la funzione chiamata ha bisogno per completare le attività per le quali è
stata progettata.

È buona abitudine familiarizzare con la ricca collezione di funzioni incluse nella libreria standard del C.

Di seguito una lista delle funzioni comunemente utilizzate della libreria matematica:
ESERCIZIO:
Scrivere un programma che visualizzi la radice quadrata di tutti i numeri interi tra 1 e 10

#include <stdio.h>
#include <math.h>

int main () {
int a;

for (a=1;a<=10;a++){
printf("La radice quadrata di %d e': %.3f\n", a, sqrt(a));

Le funzioni consentono al programmatore di suddividere in moduli un programma! Questo approccio alla


programmazione comporta numerosi vantaggi:
1) La riusabilità del software: utilizzare funzioni esistenti come blocchi di costruzione ci può
consentire di creare nuovi programmi facilmente.
2) Ripetizione: impacchettare il codice in forma di funzione consentirà allo stesso di essere eseguito in
diversi punti del programma richiamando semplicemente la funzione.
3) …

Tutte le variabili dichiarate nelle definizioni di funzione sono definite locali poiché visibili e modificabili
soltanto dalla funzione che le ha definite. Le variabili definite esternamente invece, sono definite globali e
possono essere utilizzate e modificate da tutte le funzioni definite.

Ognuno dei programmi che abbiamo presentato fino ad ora è stato formato da una funzione chiamata
main che, per eseguire i propri compiti, ha richiamato quelle della libreria standard.
Consideriamo ora in che modo i programmatori possano scrivere e richiamare le proprie funzioni
personalizzate al di fuori della funzione main.

Una funzione personalizzata presenta la seguente struttura:

output nome_della_funzione (input) {


… codice …
}

È buona norma (e solitamente viene fatto) dichiarare un prototipo tutte le funzioni all'inizio del programma,
in modo da ovviare a eventuali problemi di ordine di elaborazione.
Un prototipo è rappresentato in questo modo: tipo_di_output nome_della_funzione (lista_di_input);
Il tipo di valore di ritorno void indica che una funzione non restituirà alcun valore.
La lista dei parametri è un elenco che specifica i parametri, separati da virgole, che saranno ricevuti dalla
funzione quando sarà chiamata. Qualora una funzione non riceva alcun valore, la lista dei parametri sarà
void. Salvo che non sia un tipo di dato int, ognuno dei parametri dovrà essere specificato esplicitamente
(tuttavia è consigliabile includere il tipo di dato per ogni parametro anche qualora questo fosse di tipo int).

Il compilatore farà riferimento al prototipo della funzione, per controllare che le chiamate della funzione
contengano il tipo di ritorno corretto, il numero e tipi appropriati per gli argomenti e che questi siano forniti
nell'ordine corretto.
Le dichiarazioni e le istruzioni inserite all’interno delle parentesi graffe formano il corpo della funzione
(anche definito blocco). Le variabili potranno essere dichiarate in qualsiasi blocco e questi potranno essere
nidificati. Una funzione non può essere mai definita all’interno di un’altra funzione.
ESERCIZIO: Crea un programma che trovi il max tra 3 numeri

#include<stdio.h>
int maximum (int x, int y, int z); //inseriamo il nostro prototipo di funzione
int main () {
int num1;
int num2;
int num3;

printf("Scrivere tre numeri interi di cui si vuole sapere il massimo: \n");


scanf("%d%d%d",&num1,&num2,&num3);
printf("Il massimo fra i 3 numeri dati e': %d\n", maximum(num1,num2,num3));

return 0;
}

int maximum(int x, int y, int z) {


int max = x;

if (y > max){
max = y;
}
if (z > max){
max = z;
}
return max;
}

Una chiamata di funzione che non corrisponde al suo prototipo è un errore di sintassi. Sarà generato un
errore anche qualora il prototipo e la definizione della funzione non concordino.

ESERCIZIO: Calcolo della radice quadrata


#include <stdio.h>
#include <math.h>
float radiceq(float);

main() {
float y;
printf("Immettere il numero del quale si vuole conoscere la radice quadrata: \t");
scanf("%f",&y);

printf("%f\n", radiceq(y));

float radiceq (float y) {


float z, epsilon = 0.0001;
z = y;
while (fabs(z*z - y) > epsilon) {
z = (z + y/z)/2;
}
return z;
}

Se in questo caso non avessimo immesso il prototipo di funzione prima del main, avremmo avuto un
warning!
Il compilatore, seguendo l’ordine, avrebbe iniziato a fare supposizioni sulla funzione radiceq(y) e non
sarebbe stato in grado di capire di cosa si trattasse. Immettendo dei prototipi all’inizio di ogni compilazione
possiamo ovviare al problema dell’ordine e richiamare funzioni dove vogliamo senza problemi.
ESERCIZIO:
Risolvi il problema delle torri di Hanoi per n dischi e n+1 pioli (n = 9).

#include <stdio.h>
void hanoi(int);

int n=9;
int c=0;
main (){
printf("La soluzione al problema delle torri di Hanoi con 9 pioli e 10 dischi risulta essere:\n\n");
hanoi(9);
}
void hanoi(int n){
while (c<n){
printf("Sposta il disco da 0 a %d\n",c+1);
c=c+1;
}
c=n-1;
while (c>=1){
printf("Sposta il disco da %d a 10\n",c);
c=c-1;

}
}

ESERCIZIO:
Disegna un rettangolo di dimensione n×m (10×8) .
#include <stdio.h>
void disegna_rettangolo(int,int);

void main(){
disegna_rettangolo(10,8);
}

void disegna_rettangolo(int w, int h){


int i, j;

/* lato superiore */
for(i = 0; i < w; i++){
printf("*");
}
printf("\n");

/* lati sx e dx */
for(j = 0; j < h-2; j++){
printf("*");
for(i = 0; i < w-2; i++){
printf(" "); /*regola il vuoto interno*/
}
printf("*");
printf("\n");
}

/* lato inferiore */
for(i = 0; i < w; i++){
printf("*");
}
printf("\n");
}
Lo stack delle chiamate di funzione e i record di attivazione

Per comprendere con il C effettui le chiamate di sistema, è necessario pensare ad una struttura dati nota come
stack. Possiamo pensare ad uno stack come a qualcosa di analogo ad una pila di piatti. Quando un piatto
viene sistemato nella pila, esso viene normalmente posizionato in cima (push). Similmente, quando un piatto
viene rimosso dalla pila, la rimozione avviene sempre dalla cima (pop). Gli stack sono conosciuti come
strutture dati last-in, first-out (LIFO), ovvero, l’ultimo elemento inserito è anche il primo ad essere rimosso.

Invocare le funzioni: chiamata per valore e per riferimento


In molti linguaggi di programmazione esistono due modi per invocare le funzioni: la chiamata per valore e
la chiamata per riferimento. Quando gli argomenti saranno passati in una chiamata per valore, sarà
preparata una copia dei loro valori e questa sarà passata alla funzione chiamata.
Le modifiche effettuate alla copia non interesseranno il valore originale della variabile definita dal
chiamante. Quando un argomento sarà passato in una chiamata per riferimento, il chiamante consentirà
effettivamente alla funzione chiamata di modificare il valore originale della variabile.
Le chiamate per valore dovrebbero essere utilizzate ogniqualvolta la funzione chiamata non abbia la
necessità di modificare il valore della variabile originale definita dal chiamante. Ciò preverrà gli effetti
collaterali accidentali che intralciano pesantemente lo sviluppo di sistemi software corretti e affidabili. Le
chiamate per riferimento dovrebbero essere utilizzate solo con funzioni affidabili che abbiano bisogno di
modificare la variabile originale.