Funzioni e procedure

Da Hacknowledge.

Ogni linguaggio di programmazione ad alto livello mette a disposizione del programmatore gli strumenti delle funzioni e delle procedure, tanto più il C, linguaggio procedurale per eccellenza.

Abbiamo già incontrato nel corso di tutorial un esempio di funzione: il main(). Il main() altro non è che una funzione speciale che viene eseguita all'inizio del programma. Ma ovviamente è possibile definire anche altre funzioni (avevo già accennato che tutto ciò che si fa in C si fa tramite le funzioni. Anche la printf() che abbiamo usato nei paragrafi precedenti non è altro che una funzione definita in stdio.h).

Indice

[modifica] Definizione intuitiva di funzione

Per capire meglio come lavorano le funzioni in C, ci aiuteremo con la definizione matematica di funzione. Sappiamo che una funzione matematica è scritta in genere nella forma y=f(x), ossia ad ogni valore della variabile indipendente x (che può essere o una variabile scalare, quindi una variabile a cui corrisponde un solo valore reale, o un vettore di variabili) corrisponde uno ed un solo valore della variabile dipendente y. Prendiamo ad esempio la funzione f(x)=x+2: ad ogni valore della x corrisponde uno ed un solo valore della funzione f(x), se x è 0, f(x) è 2, se x è 1, f(x) è 3, e così via.

È possibile anche che in una funzione ci sia più di una variabile indipendente: ad esempio, f(x,y)=x+y.

Le "variabili indipendenti" delle funzioni nelle funzioni C sono i parametri, ossia i valori che si danno in input alla funzione (anche se è possibile creare funzioni senza alcun parametro), mentre il "risultato" della funzione (la "variabile dipendente") si ottiene usando la keyword return che abbiamo già incontrato. Ecco la struttura di una funzione in C:

tipo_ritornato  nome_funzione(parametro1,parametro2...parametron)  {
  codice
  codice
  ......
}
 

[modifica] Esempi d'uso di funzioni e standard di utilizzo

Ecco un piccolo esempio:

int square(int x)  {
  return x*x;
}

Questa funzione calcola il quadrato di un numero intero x. La variabile int x è il parametro che passo alla funzione. Ho stabilito all'inizio, dichiarando la funzione come int, che il valore ritornato dalla funzione (la "variabile dipendente") deve essere di tipo int. Attraverso la direttiva return stabilisco quale valore deve ritornare la funzione (in questo caso il quadrato del numero x, ossia x*x). In matematica, una funzione del genere la potrei scrivere come f(x)=x².

Questa funzione la posso richiamare all'interno del main() o di qualsiasi altra funzione del programma. Esempio:

int y;		// Dichiaro una variabile int
y = square(2);	// Passo alla funzione square il valore 2,
                // in modo che calcoli il quadrato di 2
printf ("Quadrato di 2: %d\n",y);

Ovviamente, posso dichiarare un'infinità di funzioni in questo modo. Ecco ad esempio una funzione che calcola la somma di due numeri:

int somma(int a, int b)  {
  return a+b;
}

Invocazione:

int c;
c = somma(2,3);	// c vale 5

La maggior parte delle funzioni matematiche sono dichiarate nel file math.h (ci sono ad esempio funzioni per calcolare il seno, il coseno o la tangente di un numero reale, il logaritmo, la radice quadrata, la potenza n-esima...), quindi se vi interessa fare un programma di impostazione matematica date un'occhiata a questo file per capire quale funzione usare.

Ovviamente, è anche possibile creare funzioni senza alcun parametro in input. Esempio (stupido):

int ritorna_zero()  {
  return 0;
}

Vediamo ora come inserire una funzione nel nostro programma. Le funzioni in C possono andare in qualsiasi parte del codice, ma l'ANSI-C, per evitare confusione, ha imposto che all'inizio del programma ci vadano i prototipi delle funzioni usate dal programma stesso. Un prototipo non è altro che la funzione vera e propria (tipo ritornato, nome e parametri) senza però la sua implementazione, ossia senza il codice fra le parentesi graffe {}. Esempio:

int square(int x);

Ecco qui un programmino d'esempio che calcola il quadrato di un numero intero stabilito attraverso la funzione square():

/* square.c */
 
#include <stdio.h>
 
int square(int x);		// Prototipo della funzione
 
int main()  {
  int y;			// Variabile intera
  y = square(3);		// Ora y vale 9
  printf ("Quadrato di 3: %d\n",y);  
                               // Più brevemente, potremo scrivere:
			       // printf ("Quadrato di 3: %d\n",square(3));
			       // senza neanche "scomodare" la variabile y
  return 0;
}
 
int square(int x)  {	// Implementazione della funzione square()
  return x*x;
}

Nei programmi di grandi dimensioni, in genere si mette il prototipo della funzione in un file header (con estensione .h), l'implementazione in un file .c e poi il programma vero e proprio nel file main.c. Esempio:

/* Questo è il file square.h */
int square(int x);
 
/* Questo è il file square.c */
int square(int x)  {
  return x*x;
}
 
/* Questo è il file main.c */
#include <stdio.h>
#include "square.h"
 
// Ovviamente includo il file square.h
 
int main()  {
  printf ("Quadrato di 4: %d\n",square(4));
  return 0;
}

Quando vado a compilare questo programma devo fare una cosa del genere:

gcc  -o square   main.c   square.c

Ma per i nostri piccoli programmini non è il caso di fare una cosa del genere! Ci va tutto in un file.

[modifica] Procedure

Un discorso simile a quello delle funzioni vale anche per le procedure; le procedure non solo altro che funzioni "speciali", funzioni che non hanno un valore ritornato: eseguono un pezzo di codice ed escono. Per concludere una procedura non è necessario il return (in quanto non ritorna alcun valore): al massimo ci possiamo mettere un return;. Per dichiarare una procedura userò la keyword void:

void hello()  {
  printf ("Hello world!\n");
  return;			// Questa riga è opzionale
}

Quando voglio chiamare questa procedura all'interno di una qualsiasi funzione, basterà fare così:

hello();
 

Esempio:

#include <stdio.h>
 
void hello();		// Prototipo della procedura
 
int main()  {
  hello();		// Stampo la scritta "Hello world!" 
                        // attraverso la procedura hello()
  return 0;
}
 
void hello()  {		// Implementazione della procedura
  printf ("Hello world!\n");
}

Anche alle procedure posso passare qualche parametro. Esempio:

void stampa_var(int x)  {
  printf ("Valore della variabile passata: %d\n",x);
}

Invocazione:

stampa_var(3);	// L'output è: "Valore della variabile passata: 3
 

Nota tecnica: attenzione a non fare cose del genere!

int square(int x);
double square(double x);

Quando vado a chiamare la funzione:

square(3);
 

il compilatore non sa che funzione chiamare e va nel pallone. Proprio per evitare ambiguità del genere, la maggior parte dei compilatori danno un errore (o almeno un warning) quando nel programma compaiono scritture del genere (tuttavia, nel C++ cose del genere sono possibili, con l'overloading delle funzioni, ossia con la dichiarazione di più funzioni con lo stesso nome MA con la lista dei parametri differente. In ogni caso, una scrittura come quella di sopra darà problemi anche in C++, in quanto entrambe le funzioni hanno un solo parametro e il compilatore, nel momento dell'invocazione, non sa quale funzione chiamare).

[modifica] Funzioni statiche

Le funzioni statiche hanno proprietà molto simili alle variabili statiche. Tali funzioni, al pari delle corrispettive variabili, sono

  • Istanziate in memoria quando il programma viene creato, e distrutte quando il processo corrispondente è terminato
  • Visibili e utilizzabili solo all'interno del file che le ha dichiarate

La seconda proprietà impone delle limitazioni d'uso delle funzioni statiche, in modo da rendere più modulare il programma, più protetto ed evitare che qualsiasi file del programma possa richiamare qualsiasi funzione del programma.

Esempio:

/* file: foo.c */
 
#include <stdio.h>
 
static void foo1()  {
  printf ("Sono una funzione statica\n");
}
 
void foo2()  {
  printf ("Richiamo una funzione statica\n");
  foo1();   // Chiamata valida. La funzione foo1() è contenuta
            // nello stesso file della funzione foo2()
}
/* file: main.c */
 
#include <stdio.h>
#include "foo.c"
 
main()  {
  foo2();  // Chiamata valida. La funzione foo2() è visibile
           // al main e non è una funzione statica
  foo1();  // ERRORE! foo1() è statica
}

Il meccanismo della visibilità delle funzioni e delle variabili è ancora un po' primitivo nel C, e si basa tutto sul concetto di staticità, mentre verrà decisamente approfondito in linguaggi a oggetti come C++, Java e Smalltalk.

[modifica] Funzioni Globali\Locali

C'è, infine, un'altro metodo, per creare una funzione, sconsigliato per il fatto che rende meno modulare il codice.
Questo metodo consiste nel creare una funzione locale ad un'altra funzione. Ovvero una funzione visibile e richiamabile solo all'interno della funzione in cui è stata dichiarata.

Un esempio della sua creazione è:

#include <stdio.h>
 
int main(void)
{
 void hello_local_function(void) {
      printf("Local Function is Ready!\n");
 }
 
 printf("Richiamo la funzione interna...\n");
 hello_local_function();
 printf("Esco.\n\n");
 
 return 0;
}
Strumenti personali