Come ogni moderno linguaggio, sia il C che il C++ consentono di
dichiarare sottoprogrammi che possono essere invocati nel corso dell'esecuzione di
una sequenza di istruzioni a partire da una sequenza principale (il corpo del
programma). Nel caso del C e del C++ questi sottoprogrammi sono chiamati
funzioni e sono simili alle funzioni del Pascal. Anche il corpo del programma
e` modellato tramite una funzione il cui nome deve essere sempre main
(vedi esempio).
Dichiarazione e chiamata di una
funzione
Una funzione C/C++, analogamente ad una funzione Pascal,
e` caratterizzata da un nome che la distingue univocamente nel suo scope (le regole
di visibilita` di una funzione sono analoghe a quelle
viste per le variabili), da un insieme (eventualmente vuoto) di argomenti
(parametri della funzione) separati da virgole, e eventualmente il tipo del valore
ritornato:
// ecco una funzione che riceve due interi // e restituisce un altro intero int Sum(int a, int b);Gli argomenti presi da una funzione sono quelli racchiusi tra le parentesi tonde, si noti che il tipo dell'argomento deve essere specificato singolarmente per ogni parametro anche quando piu` argomenti hanno lo stesso tipo; la seguente dichiarazione e` pertanto errata:
int Sum2(int a, b); // ERRORE!Il tipo del valore restituito dalla funzione deve essere specificato prima del nome della funzione e se ommesso si sottointende int; se una funzione non ritorna alcun valore va dichiarata void, come mostra il seguente esempio:
// ecco una funzione che non ritorna alcun valore void Foo(char a, float b);Non e` necessario che una funzione abbia dei parametri, in questo caso basta non specificarne oppure indicarlo esplicitamente:
// funzione che non riceve parametri // e restituisce un int (default) Funny(); // oppure Funny2(void);Il primo esempio vale solo per il C++, in C non specificare alcun argomento equivale a dire "Qualsiasi numero e tipo di argomenti" il secondo metodo invece e` valido in entrambi i linguaggi, in questo caso void assume il significato "Nessun argomento".
void Esempio1(...); void Esempio2(int Args, ...);Il primo esempio mostra come dichiarare una funzione che prende un numero imprecisato (eventualmente 0) di parametri; il secondo esempio invece mostra come dichiarare funzioni che prendono almeno qualche parametro, in questo caso bisogna prima specificare tutti i parametri necessari e poi mettere ... per indicare eventuali altri parametri.
// la funzione Sum vista sopra poteva // essere dichiarata anche cosi`: int Sum(int, int);Per implementare (definire) una funzione occorre ripetere il prototipo, specificando il nome degli argomenti (necessario per poter riferire ad essi, ma non obbligatorio se l'argomento non viene utilizzato), seguito da una sequenza di istruzioni racchiusa tra parentesi graffe:
int Sum(int x, int y) { return x+y; }La funzione Sum e` costituita da una sola istruzione che calcola la somma degli argomenti e restituisce tramite la keyword return il risultato di tale operazione. Inoltre, benche` non evidente dall'esempio, la keyword return provoca l'immediata terminazione della funzione; ecco un esempio non del tutto corretto, che pero` mostra il comportamento di return:
// calcola il quoziente di due numeri int Div(int a, int b) { if (b==0) return "errore"; return a/b; }Se il divisore e` 0, la prima istruzione return restituisce (erroneamente) una stringa (anzicche` un intero) e provoca la terminazione della funzione, le successive istruzioni della funzione quindi non verrebbero eseguite.
int Sum(int a, int b) { a + b; } // ERRORE! Nessun valore restituito. int Sum(int a, int b) { return; } // ERRORE! Nessun valore restituito. int Sum(int a, int b) { return a + b; } // OK! void Sleep(int a) { for(int i=0; i < a; ++i) {}; } // OK! void Sleep(int Delay) { for(int i=0; i < a; ++i) {}; return; } // OK!La chiamata di una funzione puo` essere eseguita solo nell'ambito dello scope in cui appare la sua dichiarazione (come gia` detto le regole di scoping per le dichiarazioni di funzioni sono identiche a quelle per le variabili) specificando il valore assunto da ciascun parametro formale:
void Sleep(int Delay); // definita da qualche parte int Sum(int a, int b); // definita da qualche parte void main(void) { int X = 5; int Y = 7; int Result = 0; /* ... */ Sleep(X); Result = Sum(X, Y); Sum(X, 8); // Ok! Result = Sleep(1000); // Errore! }La prima e l'ultima chiamata di funzione mostrano come le funzioni void (nel nostro caso Sleep) siano identiche alle procedure Pascal, in particolare l'ultima istruzione e` un errore poiche` Sleep non restituisce alcun valore.
void Assign(int a, int b) { a = b; // Tutto OK, operazione lecita! }tuttavia qualsiasi modifica ai parametri formali (quelli cioe` che compaiono nella definizione, nel nostro caso a e b) non si riflette (per quanto visto fin'ora) automaticamente sui parametri attuali (quelli effettivamente usati in una chiamata della funzione):
#include < iostream.h > void Assign(int a, int b) { a = b; } void main() { int X = 5; int Y = 10; cout << "X = " << X << endl; cout << "Y = " << Y << endl; // Chiamata della funzione Assign // con parametri attuali X e Y Assign(X, Y); cout << "X = " << X << endl; cout << "Y = " << Y << endl; }L'esempio appena visto e` perfettamente funzionante e se eseguito mostrerebbe come la funzione Assign, pur eseguendo una modifica ai suoi parametri formali, non modifichi i parametri attuali. Questo comportamento e` perfettamente corretto in quanto i parametri attuali vengono passati per valore: ad ogni chiamata della funzione viene cioe` creata una copia di ogni parametro locale alla funzione stessa; tali copie vengono distrutte quando la chiamata della funzione termina ed il loro contenuto non viene copiato nelle eventuali variabili usate come parametri attuali.
int Sum (int a = 0, int b = 0) { return a+b; }Quella che abbiamo appena visto e` la definizione della funzione Sum ai cui argomenti sono stati associati dei valori di default (in questo caso 0 per entrambi gli argomenti), ora se la funzione Sum viene chiamata senza specificare il valore di a e/o b il compilatore genera una chiamata a Sum sostituendo il valore di default (0) al parametro non specificato. Una funzione puo` avere piu` argomenti di default, ma le regole del C++ impongono che tali argomenti siano specificati alla fine della lista dei parametri formali nella dichiarazione della funzione:
void Foo(int a, char b = 'a') { /* ... */ } // Ok! void Foo2(int a, int c = 4, float f) { /* ... */ } // Errore! void Foo3(int a, float f, int c = 4) { /* ... */ } // Ok!La dichiarazione di Foo2 e` errata poiche` quando viene specificato un argomento con valore di default, tutti gli argomenti seguenti (in questo caso f) devono possedere un valore di default; l'ultima definizione mostra come si sarebbe dovuto definire Foo2 per non ottenere errori.
// riferendo alle precedenti definizioni: Foo(1, 'b'); // chiama Foo con argomenti 1 e 'b' Foo(0); // chiama Foo con argomenti 0 e 'a' Foo('c'); // ????? Foo3(0); // Errore, mancano parametri! Foo3(1, 0.0); // chiama Foo3(1, 0.0, 4) Foo3(1, 1.4, 5); // chiama Foo3(1, 1.4, 5)Degli esempi appena fatti, il quarto, Foo3(0), e` un errore poiche` non viene specificato il valore per il secondo argomento della funzione (che non possiede un valore di default); e` invece interessante il terzo (Foo('c');): apparentemente potrebbe sembrare un errore, in realta` quello che il compilatore fa e` convertire il parametro attuale 'c' di tipo char in uno di tipo int e chiamare la funzione sostituendo al primo parametro il risultato della conversione di 'c' al tipo int. La conversione di tipo sara` oggetto di una apposita appendice.