Funzioni in C
In questa pagina 7
Definizione e prototipo
#include <stdio.h>
double media(int a, int b); /* prototipo (dichiarazione) */
int main(void) {
printf("%.1f\n", media(3, 4)); /* 3.5 */
return 0;
}
double media(int a, int b) { /* definizione */
return (a + b) / 2.0;
}- Ogni funzione ha un tipo di ritorno e parametri con tipo.
voidcome tipo di ritorno: non restituisce niente;(void)come parametri: nessun parametro. - Una funzione va dichiarata prima dell'uso: o la si definisce sopra
main, o si scrive prima il prototipo (intestazione seguita da;). - Gli argomenti vengono convertiti al tipo dei parametri (
media(3.9, 4)passa3). - Si restituisce un solo valore. Per "restituire" più risultati si usano parametri puntatore (vedi Puntatori in CUn puntatore contiene un indirizzo di memoria; operatori & e *, NULL, puntatori come parametri per modificare variabili del chiamante, aritmetica dei puntatori, legame tra array e puntatori, const, puntatori a puntatori.Puntatori in C →) o una
struct(vedi Strutture (struct) in Cstruct per raggruppare campi di tipo diverso, typedef, accesso con punto e freccia, struct come parametri e valori di ritorno, array di struct, struct allocate dinamicamente e struct autoreferenziali.Strutture (struct) in C →).
Passaggio per valore
In C gli argomenti sono sempre copiati nei parametri: modificare un parametro non modifica la variabile del chiamante.
void incrementa(int n) {
n++; /* modifica la copia locale */
}
int main(void) {
int x = 5;
incrementa(x);
printf("%d\n", x); /* 5 */
}Per modificare una variabile del chiamante si passa il suo indirizzo:
void incrementa(int *p) {
(*p)++; /* modifica la variabile puntata */
}
/* chiamata: incrementa(&x); */
void scambia(int *a, int *b) {
int t = *a;
*a = *b;
*b = t;
}Gli array sono un caso particolare: passando un array si passa l'indirizzo del primo elemento, quindi la funzione può modificarlo e non ne conosce la lunghezza, che va passata a parte (vedi Array e stringhe in CArray di dimensione fissa in memoria contigua, inizializzazione, nessun controllo sugli indici, passaggio a funzioni con la lunghezza, matrici; stringhe come array di char terminati da '\0' e funzioni di string.h.Array e stringhe in C →).
Confronto con Python, dove si passano riferimenti a oggetti: Funzioni in Pythondef e return, parametri posizionali, con nome e con valore predefinito, *args e **kwargs; passaggio per riferimento a oggetto; ambito delle variabili LEGB; docstring, type hint, lambda.Funzioni in Python →.
Ambito e durata delle variabili
| Tipo di variabile | Dove si dichiara | Visibilità | Durata |
|---|---|---|---|
| locale (automatica) | dentro un blocco { } |
dal punto di dichiarazione alla fine del blocco | dall'ingresso all'uscita del blocco (sullo stack) |
| parametro | intestazione | corpo della funzione | come le locali |
| globale | fuori da ogni funzione | tutto il file (dal punto di dichiarazione) | intero programma |
locale static |
dentro una funzione con static |
la funzione | intero programma: conserva il valore tra le chiamate |
int conta_chiamate(void) {
static int n = 0; /* inizializzata una sola volta */
return ++n;
}Non restituire mai l'indirizzo di una variabile locale: alla fine della funzione quella memoria non esiste più (vedi Gestione della memoria in CSegmenti di memoria di un processo (codice, dati statici, stack, heap); durata delle variabili; allocazione dinamica con malloc, calloc, realloc e free; errori classici: memory leak, dangling pointer, double free, buffer overflow; strumenti di controllo.Gestione della memoria in C →).
Compilazione separata e header
Programmi più grandi si dividono in più file. Il file header .h contiene i prototipi (l'interfaccia); il .c le definizioni.
#ifndef GEOMETRIA_H
#define GEOMETRIA_H
double area_cerchio(double r);
#endif#include "geometria.h"
double area_cerchio(double r) {
return 3.141592653589793 * r * r;
}#include <stdio.h>
#include "geometria.h"
int main(void) {
printf("%f\n", area_cerchio(2.0));
return 0;
}> gcc -Wall -o prog main.c geometria.c#include <...>per gli header di sistema,#include "..."per quelli propri.#ifndef / #define / #endif(include guard) evita che l'header venga incluso due volte.- Questa separazione interfaccia/implementazione è la base dei Tipi di dato astrattiUn ADT è definito dalle operazioni e dal loro comportamento, non dalla rappresentazione; interfaccia e implementazione; realizzazione in Python con le classi e in C con header e tipo opaco; esempio di un ADT Frazione.Tipi di dato astratti →.
Librerie standard utili
<stdio.h> I/O, <stdlib.h> (malloc, free, abs, rand, exit), <math.h> (sqrt, pow, fabs; su Linux compilare con -lm), <string.h> stringhe, <ctype.h> (isdigit, toupper), <stdbool.h>, <limits.h>.
Ricorsione
Identica a Python (RicorsioneFunzioni che chiamano se stesse: caso base e passo ricorsivo, stack delle chiamate, esempi su numeri, stringhe e liste, ricorsione multipla e suo costo, divide et impera, confronto con l'iterazione.Ricorsione →): ogni chiamata ha il suo record di attivazione sullo stack.
long long fattoriale(int n) {
return n <= 1 ? 1 : n * fattoriale(n - 1);
}Errori tipici
- Chiamare una funzione prima di dichiararla.
- Aspettarsi che una funzione modifichi un
intpassato per valore. - Funzione non
voidche in qualche ramo non eseguereturn: il valore restituito è indefinito. - Prototipo e definizione con tipi diversi.