Prosegue il corso collaborativo di programmazione.
Riassunto delle puntate precedenti:
- installazione dell'IDE, cenni di architettura degli elaboratori
- sintassi del C, programmazione strutturata
- ancora sulla sintassi, i tipi di dato, le variabili
- variabili parte seconda: gli array
In questo capitolo:
- le funzioni
- un nuovo sguardo sul main
Le funzioni
Ripartiamo ancora una volta dal nostro hello world.
void main()
{
printf("Hello World!\n");
system("pause");
}
Ormai conosciamo quasi tutto il significato di questo codice: sia come viene dichiarato il main (e il suo scopo), le stringhe, i segni di punteggiatura, le graffe, ecc.Rimane un solo argomento non trattato: le funzioni.
printf e system (ma anche il main stesso) sono delle funzioni. Vi ricordate cosa sono le funzioni in matematica? Formalmente sono delle regole, delle leggi, delle indicazioni per mettere in relazione elementi di un insieme con quelli di un altro.
In questo esempio, la funzione f mette in relazione 1 con A, 2 con C e 3 con D.
Le funzioni possono essere arbitrarie come in questo caso, oppure avere comportamenti più regolari (come quelle che siamo abituati a trattare). Per fare un esempio, potremmo avere una funzione chiamata successore tale che, preso un qualsiasi numero intero in ingresso, lo mette in relazione con il suo successore:
Come implementare una funzione del genere in C? Una prima strada, ingenua, potrebbe essere quella di fare un programma di questo tipo (pseudocodice):
main {
se ( dato_in_ingresso è 1 ) allora successore = 2
se ( dato_in_ingresso è 2 ) allora successore = 3
se ( dato_in_ingresso è 3 ) allora successore = 4
...
}
Salta subito agli occhi un problemino di fondo di questo approccio: enumerare tutti i casi è improponibile!In informatica si utilizza un altro approccio, di natura induttiva: si progetta un algoritmo risolvente che funzioni per qualsiasi input e ritorni un output coerente. Il modo più elegante per realizzare questo è creare una funzione (pseudocodice):
crea nuova funzione "successore"
input X: numero intero
output Y: numero intero
{
Y = X + 1
ritorna Y
}
Abbiamo già anticipato nelle scorse lezioni che la sintassi del C traduce questo pseudocodice in maniera leggermente diversa. Ecco come:
int successore( int X ) {
int Y;
Y = X+1;
return Y;
}
Poiché la funzione ritorna un valore (di un qualsiasi tipo di dato che abbiamo visto nelle due lezioni precedenti, anche void) il compilatore vuole che noi gli si riservi un blocco di memoria. Ecco dunque che la funzione inizia dichiarando il tipo del valore di ritorno. La seconda cosa da dichiarare è il nome delle funzione.
La terza sono i parametri in ingresso: quali dati utilizza la mia funzione per elaborare? In questo caso, sappiamo che vogliamo il successore di un numero intero, quindi dichiareremo la funzione con un solo parametro intero. I parametri si mettono tra parentesi tonde e possono essere in numero arbitrario. Si osservi che non abbiamo scritto
int successore( int ) {
...
ma abbiamo specificato anche il nome di una variabile
int successore( int X ) {
...
Perché? perché noi vogliamo poter utilizzare un qualsiasi valore arbitrario passato alla funzione, senza conoscerlo in anticipo. Dunque, nelle parentesi, avvengono delle vere e proprie dichiarazioni di variabili. Attenzione ora. Le variabili dichiarate all'interno delle funzioni sono utilizzabili solo dentro il corpo delle funzioni: il main (e tutte le altre funzioni) non possono utilizzarle. Questa "visibilità" delle variabili è detta scopo e va immaginata come delle vere e proprie strutture a matrioska: una variabile dichiarata dentro una matrioska più piccola non è visibile da quella più grande. Vale invece il viceversa: se dichiaro una variabile prima della funzione, la potrò utilizzare.
Abbiamo già anticipato che le funzioni ritornano un valore. Ciò avviene con l'istruzione return, che si occupa anche di interrompere l'esecuzione della funzione e restituire il controllo al chiamante (ad esempio il main).
int successore( int X ) {
int Y;
Y = X+1;
return Y;
}
Dunque, traduciamo in linguaggio naturale questo codice C: - dichiariamo una funzione che ritorna un numero intero chiamata successore
- questa funzione accetta un (e un solo) parametro in ingresso, di tipo intero che, alla chiamata di funzione, verrà inserito nella variabile X
- dichiariamo un'altra variabile Y
- mettiamo in Y il valore X+1
- ritorniamo Y e chiudiamo l'esecuzione della funzione
Riprendendo in mano le funzioni viste nell'hello world, ora intuiamo che printf e system accettano come parametri in ingresso delle stringhe. In realtà, la printf è più potente di così: accetta anche altri parametri, che consentono di formattare l'output a video. Inoltre, entrambe le funzioni ritornano dei valori. Torneremo su di esse in seguito.
Un nuovo sguardo sul main
Chiaramente, in funzioni dichiarate con void non dobbiamo restituire nulla; questo è il caso del nostro main dell'hello world. Tuttavia, la sintassi corretta del main è questa:
int main( int argc, char* argv[] ) {
...
}
Vediamo che il main deve restituire un intero (ormai lo sapevate già ) e riceve due parametri in ingresso. Di questi parametri parleremo più avanti (perché ancora non sapete quell'asterisco cosa significa), ma se avete mai utilizzato una console intuite già che si riferiscono ai parametri che si possono passare ai programmi.Ora vediamo come scrivere un programma che, letto un numero dall'utente, ne stampa il successore.
int successore( int X ) {
return X+1;
}
int main( int argc, char* argv[] ) {
/* dichiaro le variabili che userò */
int valore;
int ritorno;
/* leggo input */
printf( "Inserire un numero intero: " );
scanf ( "%d" , &valore );
/* stampo output */
ritorno = successore( valore );
printf( "Il suo successore è: " );
printf( "%d", ritorno );
/* fine: inseriamo una pausa e poi ritorniamo zero (=tutto okay) */
system( "pause" );
return 0;
}
Seguono quattro osservazioni.Osservazione 1: la funzione deve essere dichiarata PRIMA del corpo principale, sennò il main non sa di cosa stiamo parlando ("successore??! che è??") e il compilatore ci da errore.
Osservazione 2: nella funzione successore non ho dichiarato una variabile Y di appoggio. In effetti è inutile, possiamo ritornare direttamente il risultato se la funzione è semplice: risparmiamo memoria.
Osservazione 3: nel main ho inserito dei commenti tramite /* ... */ . E' buona norma commentare sempre il proprio codice, perché qualunque sviluppatore può confermarvi quanto sia difficile rimettere mano al proprio codice (o, peggio, a quello altrui) se non è stato adeguatamente commentato. Altre buone regole sono l'indentazione e l'uso di nomi di variabili significativi (e non cose tipo pippo = pluto + 1).
Osservazione 4: come vedete, sia scanf che printf hanno richiesto l'uso di %d, mentre nell'esempio precedente usavamo %s. Sarà spiegato in futuro il motivo, ma possiamo anticipare che è legato al fatto che prima manipolavamo stringhe e ora dei numeri (decimals); entrambe le funzioni, per lavorare, han bisogno di sapere su che tipi di dato stanno agendo.
L'esercitatore Kaehell vi fornirà ora dei succosi esercizi per testare la vostra comprensione della prima tranche dell'argomento funzioni.