Funzioni in C
In questa pagina 7
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;
}Definizione (funzione C, tipo di ritorno e prototipo)
- 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 →).
Esempio. media(3, 4) restituisce 3.5, cioè ; media(3.9, 4) converte 3.9 in 3 e restituisce lo stesso valore.
Passaggio per valore
Definizione (passaggio per valore in C). In C gli argomenti sono sempre copiati nei parametri: modificare un parametro non modifica la variabile del chiamante.
Esempio. Con void incrementa(int n) { n++; }, dopo incrementa(x) la variabile x = 5 resta 5; con void incrementa(int *p) { (*p)++; } e incrementa(&x) diventa 6.
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
Definizione (ambito e durata delle variabili in C)
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 staticdentro una funzione con staticla funzione intero programma: conserva il valore tra le chiamate
Esempio. Con static int n = 0; return ++n;, tre chiamate successive di conta_chiamate() restituiscono 1, 2, 3.
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.
Versione ripasso
Definizione e prototipo
- Ogni funzione ha un tipo di ritorno (o
voidse non restituisce niente) e parametri con tipo. - La funzione va dichiarata prima dell'uso tramite definizione o prototipo (intestazione con
;). - Si restituisce un solo valore (per più risultati 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 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: modificarli non modifica la variabile del chiamante. Per modificarla si passa il suo indirizzo (es. incrementa(&x) usando int *p). Gli array passano l'indirizzo del primo elemento e possono essere modificati (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 →).
Ambito e durata delle variabili
| Tipo di variabile | Visibilità | Durata |
|---|---|---|
| locale / parametro | blocco / corpo funzione | dall'ingresso all'uscita del blocco (sullo stack) |
| globale | tutto il file | intero programma |
locale static |
la funzione | intero programma: conserva il valore tra le chiamate |
Non restituire mai l'indirizzo di una variabile locale (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
- Il file header
.hcontiene i prototipi; il.cle definizioni. #ifndef / #define / #endif(include guard) evita inclusioni multiple.- Compilazione:
gcc -Wall -o prog main.c geometria.c.
Librerie standard utili
<stdio.h>, <stdlib.h>, <math.h> (compilare con -lm), <string.h>, <ctype.h>, <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 →), con record di attivazione sullo stack.
Errori tipici
- Chiamare una funzione prima di dichiararla.
- Aspettarsi che una funzione modifichi un
intpassato per valore. - Funzione non
voidsenzareturnin qualche ramo. - Prototipo e definizione con tipi diversi.