Perché le funzioni
Finora tutto è vissuto dentro main. Funziona finché un programma fa una sola cosa, poi arrivano tre problemi insieme: lo stesso blocco di codice viene copiato in tre posti, main cresce oltre quello che chiunque riesca a tenere a mente, e non c'è modo di dare un nome allo scopo di un pezzo di codice.
Una funzione risolve tutti e tre. Dà un nome a un blocco di codice, le sue variabili locali, una lista di input e un singolo valore da restituire.
square è scritta una volta e chiamata tre volte, di cui una sul proprio risultato. Hai già chiamato delle funzioni: printf è una funzione, e lo è anche main.
Anatomia di una funzione
tipoDiRitorno nome(listaDeiParametri) {
// corpo
return valore;
}
Quattro parti:
- Tipo di ritorno: il tipo del valore che la funzione restituisce.
int,double,char, un tipo puntatore, oppurevoidper "niente". - Nome: segue le stesse regole del nome di una variabile, e dovrebbe dire cosa la funzione fa o produce.
- Lista dei parametri: gli input, ognuno con il suo tipo, separati da virgole.
(void)significa che non ne prende. - Corpo: le istruzioni, tra graffe, che terminano con un
returna meno che il tipo di ritorno siavoid.
Nota che pi() ha comunque bisogno delle parentesi vuote nel punto della chiamata: è questo che la rende una chiamata e non una semplice menzione del nome. E printBanner(20); è un'istruzione a sé, perché non produce alcun valore da usare.
Scrivi (void), non (), per una funzione senza parametri. In C le due forme significano cose diverse, e () è quella permissiva: ne parliamo tra poco.
Chiamare e restituire
Una chiamata trasferisce il controllo alla funzione, ne esegue il corpo e torna indietro con un valore che sostituisce la chiamata nell'espressione circostante.
return fa due cose insieme: fornisce il valore e termina la funzione sul posto. Tutto ciò che viene dopo un return sullo stesso percorso non viene mai eseguito, ed è questo che fa funzionare lo stile a uscita anticipata di larger: non serve nessun else, perché arrivare all'ultima riga significa già che a > b era falso.
Una funzione può avere più istruzioni return. Ma a ogni chiamata ne può eseguire solo una.
Funzioni void
Una funzione che esiste per il suo effetto più che per il suo valore ha tipo di ritorno void: stampare, aggiornare qualcosa tramite un puntatore, disegnare.
Dentro una funzione void, return; senza valore esce in anticipo. Lì return someValue; è un errore di compilazione, e lo è anche usare la chiamata in un'espressione: int x = printTable(7, 5); non compila, ed è il sistema dei tipi che fa il suo lavoro.
La scomposizione: il vero punto
Le funzioni non servono soprattutto a evitare ripetizioni. Servono a trasformare una lunga procedura in pochi passi con un nome, così che main si legga come una descrizione del programma e non come la sua implementazione.
Ora main è fatta di quattro righe di output e un calcolo. Ogni funzione di supporto fa una cosa sola, ha un nome che dice cos'è quella cosa e si può testare da sola. Nota che la lunghezza dell'array viaggia come secondo parametro: una volta passato un array a una funzione, lì il trucco di sizeof non funziona più, perché l'array decade a puntatore.
Tre regole pratiche per dividere il lavoro in funzioni:
- Un compito per funzione. Se il nome ha bisogno di una "e", probabilmente sono due funzioni.
- Prendi quello che ti serve, restituisci quello che produci. Una funzione che legge lo stato globale è più difficile da capire e impossibile da testare in isolamento.
- Abbastanza corta da vederla tutta insieme. Non esiste un numero magico di righe, ma una funzione che non sta in uno schermo di solito nasconde al suo interno una funzione più piccola.
Funzioni che chiamano funzioni
Una funzione può chiamare qualsiasi funzione già dichiarata, comprese quelle che hai scritto tu:
main chiama report, che chiama sumEvens, che chiama isEven dentro un ciclo. Ogni chiamata riceve un proprio insieme di variabili locali nuove, impilate sopra quelle del chiamante, che vengono scartate quando la funzione termina.
Le definizioni qui sopra sono in ordine di dipendenza, isEven prima di sumEvens prima di report, perché il C richiede che una funzione sia dichiarata prima di essere chiamata. Chiamane una che compare più in basso nel file e il compilatore si lamenta di una dichiarazione implicita. La soluzione generale è un prototipo vicino all'inizio del file, che ti permette di ordinare le definizioni come preferisci. Anche una funzione che chiama se stessa è valida e utile: si chiama ricorsione.
main è una funzione
main è una funzione normale che il runtime chiama per te. Il suo tipo di ritorno int è lo stato di uscita del programma: 0 significa successo, un valore diverso da zero significa errore, e gli script di shell e gli strumenti di build lo leggono.
int main(void) {
/* ... */
return 0;
}
Da C99, arrivare alla fine di main senza un return restituisce implicitamente 0: è una regola speciale che vale solo per main. Qualsiasi altra funzione non void che termina senza restituire un valore produce comportamento indefinito quando il chiamante usa il risultato.
L'altra forma standard è int main(int argc, char *argv[]), che riceve gli argomenti da riga di comando. Entrambe sono corrette; void main() non lo è, qualunque cosa dicano i vecchi tutorial.
Errori comuni
- Dimenticare
returnin una funzione non void. Il chiamante legge un valore spazzatura.gcc -Walllo segnala. - Dichiarare nel corpo una variabile con lo stesso nome di un parametro. Nasconde il parametro, e l'assegnazione che volevi fare all'input non arriva da nessuna parte.
- Aspettarsi che una funzione modifichi i suoi argomenti. Il C passa tutto per valore, quindi una funzione riceve delle copie.
void reset(int x) { x = 0; }non cambia nulla nel punto della chiamata: è l'argomento della prossima pagina. - Omettere le parentesi in una chiamata.
printBanner;è un'espressione valida che calcola l'indirizzo della funzione e lo scarta. Compila, non fa niente, e-Wallla segnala. - Scrivere
()invece di(void). È valido, ma nei dialetti C più vecchi disattiva il controllo degli argomenti.
Domande frequenti
Come si dichiara una funzione in C?
Scrivi il tipo di ritorno, il nome e una lista di parametri tra parentesi, poi il corpo tra graffe: int add(int a, int b) { return a + b; }. Una funzione che non prende nulla usa (void), e una che non restituisce nulla ha tipo di ritorno void.
Cosa fa return in una funzione C?
Termina subito la funzione e restituisce un valore al chiamante. Il tipo del valore deve corrispondere al tipo di ritorno dichiarato dalla funzione. In una funzione void puoi scrivere un semplice return; per uscire in anticipo, oppure ometterlo e lasciare che la funzione finisca alla graffa di chiusura.
Perché main restituisce un int in C?
Il valore di ritorno di main è lo stato di uscita del programma, che il sistema operativo e gli script di shell possono leggere. return 0; significa successo e qualsiasi valore diverso da zero significa errore. Da C99, arrivare alla fine di main senza un'istruzione return restituisce implicitamente 0.