Linguaggi | Manuali | Compilatori | Programmi | Script | Software | Linux | Windows | Html
Linguaggi

C
C++
JAVA
PERL
COBOL
PASCAL
MATLAB
FORTRAN77
FORTRAN90

JAVASCRIPT

VISUALBASIC

Sistemi operativi

LINUX
WINDOWS
UNIX
MAC

Software

AUTOCAD
GNUPLOT
OCTAVE
SCILAB

Funzioni
Tutorial 9
Scritto da Roberto Navigli (roberto.navigli@iol.it)
Game Programming Italia: www.gpi.eden.it

Funzioni

Per via della loro grande utilità, le funzioni sono state introdotte sin dall'inizio. È bene però specificare la sintassi con cui esse possono essere definite.
Specificare una funzione significa dire come svolgere una determinata operazione. Consideriamo la funzione come una scatola nera. La scatola ha alcune fessure attraverso le quali è possibile inserire determinate informazioni necessarie a portare a termine il compito che le viene affidato e una sola fessura per restituire il risultato di tale operazione. Ad esempio, una scatola di questo genere può essere la macchinetta del caffè oppure un distributore di biglietti dei treni. Esse prendono in ingresso del denaro e restituiscono, rispettivamente, una tazzina di caffè e un biglietto del treno. Questo è proprio quello che fanno le funzioni: prendono in input degli argomenti e restituiscono in output un risultato.

È bene ora distinguere due concetti fondamentali:
- la dichiarazione di funzione
- la definizione di funzione

La prima è semplicemente una specifica degli argomenti che la funzione prende in input e di ciò che essa restituisce in output. Ad esempio, la dichiarazione della funzione "macina caffè" - scritta in italiano - potrebbe essere la seguente:

Funzione: macina caffè. Input: caffè. Output: caffè macinato.

Ciò equivale a una dichiarazione di funzione in C, così come in C++. Essa avviene con la seguente sintassi:

tipo_output nome_funzione(tipo1_input, …, tipon_input);

dove tipo_output è il tipo di oggetto restituito, nome_funzione è il nome della funzione e dove tra parentesi tonde si specificano gli n tipi di oggetti che la funzione prende in input. Per tornare all'esempio di prima, si avrebbe:

CaffèMacinato macina_caffè(Caffè);

macina_caffè è il nome della funzione, CaffèMacinato è l'output della funzione e Caffè è l'input della stessa.
Un esempio più concreto potrebbe essere una funzione che calcola una determinata imposta:

int calcola_imposta(int);

essa prende in input un valore intero che rappresenta l'imponibile e restituisce un altro valore intero, l'imposta da pagare.
Finora quindi si è parlato solo di dichiarazione di una funzione in C. Ciò significa che si comunica al compilatore come è fatta la funzione-scatola nera. Da quel momento in poi, è possibile far uso della funzione in qualsiasi punto del codice, purché si passino in input il numero richiesto di argomenti e del tipo specificato nella dichiarazione.
Ciò ovviamente non può essere sufficiente, perché il compilatore non sa come macinare il caffè o calcolare le imposte. È quindi necessario dare una definizione delle funzioni che si dichiarano. A tale scopo è sufficiente utilizzare la medesima sintassi della dichiarazione, eliminare il punto e virgola ed inserire un blocco di istruzioni (racchiuse come sempre da parentesi graffe). Ad esempio:

int calcola_imposta(int imponibile)
{
// 20% di imposta
return (imponibile*20)/100;
}

Si noti che non si specifica più solo il tipo dell'argomento in input (un intero), ma anche il nome (imponibile), poiché esso deve poter essere utilizzato all'interno della funzione per poter restituire il risultato. Tale restituzione avviene mediante l'istruzione return. Nell'esempio si restituisce il 20% dell'imponibile passato in input.
Segue un esempio completo:

#include <iostream>

using namespace std;

// dichiarazione della funzione
int calcola_imposta(int);

void main()
{
// richiama la funzione calcola_imposta su un milione
cout << calcola_imposta(1000000);
}

// definizione della funzione
int calcola_imposta(int imponibile)
{
// 20% di imposta
return (imponibile*20)/100;
}

Compilando il file si ottiene il seguente risultato:

200000

che è il 20% di un milione. Si noti che la funzione calcola_imposta viene utilizzata dopo la sua dichiarazione, ma prima della sua definizione. Ciò è possibile perché al compilatore è sufficiente conoscere la forma della "scatola nera" per poterla utilizzare (così come si fa quando si utilizza una macchinetta del caffè).
Quando si scrivono programmi semplici è anche possibile fare a meno della dichiarazione. La definizione di funzione, infatti, ha anche la funzione di dichiarare la stessa (poiché contiene tutte le informazioni necessarie a tale scopo). Ad esempio, il codice precedente si poteva scrivere come segue:

#include <iostream>

using namespace std;

// definizione della funzione
int calcola_imposta(int imponibile)
{
// 20% di imposta
return (imponibile*20)/100;
}

void main()
{
// richiama la funzione calcola_imposta su un milione
cout << calcola_imposta(1000000);
}

È invece errato il seguente:

#include <iostream>

using namespace std;

void main()
{
// richiama la funzione calcola_imposta su un milione
// errore: la funzione non è dichiarata né definita!!!
cout << calcola_imposta(1000000);
}

// definizione della funzione
int calcola_imposta(int imponibile)
{
// 20% di imposta
return (imponibile*20)/100;
}

Il compilatore emette un messaggio di errore perché, quando incontra la chiamata alla funzione calcola_imposta, essa non è stata ancora definita, né tantomeno dichiarata!
Ricorsione e mutua ricorsione

A volte può tornare utile richiamare la funzione dall'interno della funzione stessa. Ciò non pone alcun problema, purché ci si accerti che la funzione non chiami se stessa infinite volte. Ad esempio, il programma seguente non termina:

// fun è una funzione che non prende argomenti in input
// e non restituisce nulla in output
void fun()
{
// chiama se stessa
fun();
}

void main()
{
fun();
}

Nel main si chiama la funzione fun, la quale chiama se stessa, entrando in un circolo vizioso, poiché ad ogni successiva chiamata essa chiamerà se stessa. In genere quando si fa uso della ricorsione si stabilisce una condizione che ponga fine alla catena di chiamate in cascata. Ad esempio:

int fattoriale(int n)
{
if (n > 1) return n*fattoriale(n-1);
else return 1;
}

è una funzione che calcola il fattoriale di un intero. Come noto, il fattoriale di n, ovvero n!, è definito come n*(n-1)*(n-2)*…*1. Quello che fa la funzione ricorsiva è di moltiplicare il numero in input per il risultato della stessa funzione applicata al numero decrementato di 1. Ciò è possibile perché:

n! = n*(n-1)!

ovvero, in C:

fattoriale(n) == n*fattoriale(n-1)

Spesso è conveniente fare a meno della ricorsione, poiché essa rende allo stesso tempo meno leggibile e meno efficiente il codice. Nonostante ciò, alcuni problemi possono essere risolti semplicemente solo per mezzo di tale tecnica. Segue la versione iterativa della funzione fattoriale (che fa uso del costrutto for):

int fattoriale(int n)
{
// copia n nella variabile i
int i = n;
// decrementa n
n--;
// da n-1 fino a 1
for (; n > 0; n--) i *= n;

// restituisce il risultato
return i;
}

Passaggio di puntatori e passaggio di array

A volte una funzione può avere l'esigenza di modificare le variabili che essa riceve in input. Ad esempio:

#include <iostream>
using namespace std;

void azzera_coordinate(int *x, int *y)
{
*x = *y = 0;
}

void main()
{
int x, y;

// la funzione inserisce le coordinate in x e in y
azzera_coordinate(&x, &y);

// stampa le coordinate
cout << x << "," << y;
}

La funzione azzera_coordinate ha bisogno di modificare due variabili. Esse vengono passate per indirizzo, nel senso che la funzione riceve gli indirizzi delle celle di memoria in cui dovranno essere contenuti i valori richiesti. Chiaramente una funzione del genere ha senso se viene invocata molto spesso su coppie di interi in diversi punti del proprio programma, sebbene questo non sia l'unico modo in cui sia possibile gestire le coppie, come si comprenderà in seguito.

Il seguente esempio, invece, non è un buon esempio di programmazione:

void dammi_la_somma(int a, int b, int *risultato)
{
*risultato = a+b;
}

Infatti il risultato avrebbe potuto essere restituito direttamente dalla funzione, senza far uso di puntatori:

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

Spesso i puntatori generano confusione. Infatti è bene tenere a mente che, ogni qualvolta si passa un puntatore in input a una funzione, il contenuto della locazione "puntata" può essere modificato.

Ci si può chiedere come passa in input un array. La chiave sta nel fatto che un array non è altro che una sequenza di elementi dello stesso tipo. In C (come in C++) è sufficiente quindi passare un puntatore al primo elemento della sequenza. Ad esempio:

#include <iostream>
using namespace std;

int cerca_massimo(int *sequenza)
{
int max = -1;

for (int k = 0; sequenza[k] != -1; k++)
if (sequenza[k] > max) max = sequenza[k];

return max;
}

void main()
{
// array di numeri
int numeri[] = { 1, 5, 3, 7, 0, 10, 9, -1 };

cout << cerca_massimo(numeri);
}

Nell'esempio la funzione prende in input un puntatore a una sequenza di elementi interi (definita all'interno del main). Essa scorre la sequenza fino a trovare l'intero -1 restituendo quindi il massimo dei numeri esaminati. Un'implementazione più efficiente della funzione fa uso della cosiddetta aritmetica dei puntatori:

int cerca_massimo(int *sequenza)
{
int max = -1;
while(*sequenza != -1)
{
if (*sequenza > max) max = *sequenza;
sequenza++;
}

return max;
}

Il puntatore sequenza viene incrementato in modo tale da farlo puntare di volta in volta all'elemento successivo, finché non si incontra l'intero -1.

Argomenti al main

Anche la funzione main può avere degli argomenti, ovvero i parametri che seguono il nome del file eseguibile se esso viene richiamato da console (sotto DOS e Linux, ad esempio):

#include <iostream>
using namespace std;

void main(int argc, char *argv[])
{
for (int k = 0; k < argc; k++)
// stampa l'argomento k-esimo
cout << argv[k] << endl;
}

Gli argomenti (fissi) del main sono un intero (contenente il numero di parametri) e un array di puntatori a stringhe (i parametri stessi). Ad esempio argv[0] contiene il nome del file eseguibile, argv[1] contiene il primo parametro (se esso viene specificato) e così via.

 

Partner

Guida Fortran
Guida Matlab

English Version
Tutorials
Programming
Lavoro
Lavoro in rete
Telelavoro
Webmaster
Webmaster
Xml

Gratis
Autore
G. Ciaburro
Curriculum
Tesi