← Programmazione
C++ · Funzioni

Funzioni e Passaggio Parametri

Anatomia, passaggio per valore/riferimento/puntatore, array e simulatore interattivo.

🎓 3° anno ITIS/SIA 🔧 Simulatore parametri ⏰ ~40 min
int somma(int a, int b) { return a + b; // restituisce int } void raddoppia(int& x) { x *= 2; // alias: modifica orig. }

Anatomia di una funzione

Una funzione è un blocco di codice con un nome che esegue un compito specifico e può essere richiamato più volte. Permette di evitare la duplicazione del codice e di suddividere un problema complesso in parti più gestibili.

Immagina una funzione come una scatola nera: dall'esterno vedi solo cosa entra e cosa esce — non come funziona internamente. Può ricevere zero o più valori in ingresso (i parametri) e può produrre output in due modi: tramite il valore di ritorno (al massimo uno, dichiarato prima del nome) oppure tramite parametri passati per riferimento usati come canali di uscita. Grazie a quest'ultimo meccanismo una funzione può di fatto restituire più risultati contemporaneamente.

PARAMETRI IN int a string nome 0 o più parametri FUNZIONE logica interna (nascosta) chi chiama non la vede OUTPUT return max 1 valore tipo& ref N output possibili 0 o più output valore di ritorno param. per riferimento (output)
C++ · anatomia
// DEFINIZIONE: tipo_ritorno nome(parametri) { corpo }
int somma(int a, int b) {      // riceve due int, restituisce un int
    return a + b;              // valore restituito al chiamante
}

int main() {
    int ris = somma(3, 5);    // CHIAMATA: passa 3 e 5 come argomenti
    cout << ris;               // stampa 8
    return 0;
}

Funzioni void e return

C++ · void e return
// void: non restituisce nulla
void saluta(string nome) {
    cout << "Ciao, " << nome << "!" << endl;
    // return; si usa solo per uscire anticipatamente (es. controllo errori)
    // in assenza di condizioni particolari, non va scritto
}

// con valore di ritorno: calcola e restituisce un risultato al chiamante
float media(float a, float b) {
    return (a + b) / 2.0f;
}

int main() {
    saluta("Mario");
    float m = media(7.5f, 8.0f);
    cout << "Media: " << m << endl;  // 7.75
    return 0;
}

Passaggio di parametri

Quando si chiama una funzione si passano i valori su cui deve lavorare. Il C++ offre quattro modi, ognuno con effetti diversi sulla variabile originale nel chiamante.

Per valore

La funzione riceve una copia del valore. Qualsiasi modifica alla copia resta locale e non si riflette sulla variabile originale.

C++ · per valore
void prova(int x) {       // x è una COPIA del parametro
    x = x * 2;            // modifica solo la copia locale
    cout << "dentro: " << x << endl;
}

int main() {
    int a = 5;
    prova(a);
    cout << "fuori: " << a << endl;  // stampa 5 — a NON è cambiato
    return 0;
}

Per riferimento (&)

L'operatore & nella firma trasforma il parametro in un alias della variabile originale. Non viene creata nessuna copia: la funzione opera direttamente sulla variabile del chiamante.

C++ · per riferimento
void raddoppia(int& x) {  // & = riferimento: x è un alias di chi viene passato
    x = x * 2;            // modifica DIRETTAMENTE la variabile del chiamante
}

void scambia(int& a, int& b) {
    int tmp = a; a = b; b = tmp;
}

int main() {
    int n = 5;
    raddoppia(n);
    cout << n << endl;          // 10 — n è cambiato

    int x = 3, y = 7;
    scambia(x, y);
    cout << x << " " << y;     // 7 3
    return 0;
}

const riferimento (const tipo&)

Passare per riferimento evita di copiare il dato — utile con tipi grandi come string o struct. Aggiungere const impedisce alla funzione di modificare il valore: si ottiene efficienza senza rischio di modifiche accidentali.

C++ · const riferimento
// senza const: efficiente ma la funzione potrebbe modificare s
void stampa(string& s) { cout << s; }

// con const: zero copie + garanzia che s non venga modificata
void stampa(const string& s) {
    cout << s << endl;
    // s = "altro";  // ❌ errore di compilazione: s è const
}

int main() {
    string nome = "Mario Rossi";
    stampa(nome);   // zero copie, nessuna modifica possibile
    return 0;
}
💡 Quando usare const riferimento

Per tipi piccoli (int, float, char) il passaggio per valore è sufficiente — copiarli è istantaneo. Per tipi grandi (string, struct, array) usa const tipo& se non devi modificare il dato, e tipo& se devi modificarlo.

Per puntatore (*)

La funzione riceve l'indirizzo della variabile. L'effetto è lo stesso del riferimento — si modifica l'originale — ma la sintassi è diversa: nella chiamata si usa & per passare l'indirizzo, e dentro la funzione * per accedere al valore. È lo stile del C, ancora diffuso.

C++ · per puntatore
void raddoppia(int* p) {
    if (p == nullptr) return;  // controlla sempre prima di dereferenziare
    *p = *p * 2;              // * = vai all'indirizzo e modifica il valore
}

int main() {
    int n = 5;
    raddoppia(&n);             // & = passa l'indirizzo di n
    cout << n << endl;         // 10
    return 0;
}

Array come parametro

Array monodimensionali

Quando passi un array a una funzione, il compilatore lo tratta automaticamente come un puntatore al primo elemento (tecnicamente: array decay). L'array non viene copiato: la funzione lavora direttamente sull'originale. La dimensione deve essere passata come parametro separato.

C++ · array monodimensionale
// queste tre firme sono equivalenti per il compilatore:
void stampa(int arr[], int n)      // forma più leggibile — preferita
void stampa(int* arr, int n)      // equivalente: arr diventa puntatore
void stampa(int arr[10], int n)   // la dimensione [10] è ignorata

// funzione che LEGGE l'array (const = nessuna modifica accidentale)
void stampa(const int arr[], int n) {
    for (int i = 0; i < n; i++)
        cout << arr[i] << " ";
    cout << endl;
}

// funzione che MODIFICA l'array
void raddoppia(int arr[], int n) {
    for (int i = 0; i < n; i++)
        arr[i] *= 2;   // modifica l'array originale
}

int main() {
    int v[5] = {1, 2, 3, 4, 5};
    stampa(v, 5);      // 1 2 3 4 5
    raddoppia(v, 5);
    stampa(v, 5);      // 2 4 6 8 10
    return 0;
}
⚠ sizeof non funziona sugli array passati come parametro

Dentro la funzione, sizeof(arr) restituisce la dimensione del puntatore (8 byte su 64 bit), non quella dell'array. La dimensione va sempre passata come parametro separato n.

Array bidimensionali

Con le matrici la regola cambia: la prima dimensione può essere omessa, la seconda è obbligatoria. Il compilatore ne ha bisogno per calcolare la posizione di ogni elemento in memoria: mat[i][j] corrisponde a *(mat + i * COL + j), quindi senza sapere quante colonne ha ogni riga non può fare i conti.

C++ · array bidimensionale
// prima dimensione [] omessa: ok
// seconda dimensione [3]: OBBLIGATORIA
void stampa(int mat[][3], int righe) {
    for (int i = 0; i < righe; i++) {
        for (int j = 0; j < 3; j++)
            cout << mat[i][j] << "\t";
        cout << endl;
    }
}

void azzera(int mat[][3], int righe) {
    for (int i = 0; i < righe; i++)
        for (int j = 0; j < 3; j++)
            mat[i][j] = 0;   // modifica la matrice originale
}

int main() {
    int m[2][3] = {{1,2,3}, {4,5,6}};
    stampa(m, 2);   // 1 2 3 / 4 5 6
    azzera(m, 2);
    stampa(m, 2);   // 0 0 0 / 0 0 0
    return 0;
}
⚠ La seconda dimensione è parte del tipo

Una funzione che accetta int mat[][3] accetta solo matrici con 3 colonne. Per matrici con numero di colonne variabile si usano altre tecniche (array di puntatori o allocazione dinamica) che vanno oltre questo livello.

Prototipi di funzione

Il compilatore C++ deve conoscere una funzione prima di incontrarne la chiamata. La soluzione più semplice è scrivere la definizione sopra il main. Quando invece si preferisce avere il main in cima al file — più leggibile per chi studia il programma — si usa il prototipo: una sola riga che dichiara nome, tipo di ritorno e tipi dei parametri, senza il corpo.

C++ · prototipi
// ── PROTOTIPI ─────────────────────────────────────────────
// Il compilatore conosce le funzioni ancora prima di vederle
int  somma(int a, int b);       // tipo ritorno + nome + tipi parametri
void saluta(string nome);        // i nomi parametro sono opzionali

// ── MAIN ──────────────────────────────────────────────────
int main() {
    int ris = somma(4, 7);       // OK: somma è già dichiarata
    saluta("Mario");              // OK: saluta è già dichiarata
    cout << "Somma: " << ris << endl;
    return 0;
}

// ── DEFINIZIONI ───────────────────────────────────────────
// Devono corrispondere esattamente al prototipo
int somma(int a, int b) {
    return a + b;
}

void saluta(string nome) {
    cout << "Ciao, " << nome << "!" << endl;
}
⚠ Prototipo e definizione devono corrispondere

Tipo di ritorno e tipi dei parametri devono essere identici. I nomi dei parametri nel prototipo sono opzionali: int somma(int, int); è equivalente a int somma(int a, int b); — ma scriverli rende il codice più leggibile.

📌 File header (.h) nei progetti multi-file

Nelle applicazioni più grandi il codice viene diviso in più file sorgente. I prototipi vengono raccolti in un file header (.h), le definizioni nel file sorgente (.cpp). Ogni file che usa le funzioni include l'header con #include "miefunzioni.h". È un argomento che affronteremo in una pagina dedicata alla compilazione separata.

Tabella comparativa

ModalitàSintassi firmaChiamataModifica originale?Quando usare
Per valorevoid f(int x)f(a) No — copiaLettura, tipi piccoli
Per riferimentovoid f(int& x)f(a) Sì — aliasOutput, swap, modifica
const riferimentovoid f(const string& s)f(a) No — protettoLettura di tipi grandi
Per puntatorevoid f(int* p)f(&a) Sì — via indirizzoStile C, puntatore nullable
Array 1Dvoid f(int arr[], int n)f(v, 5) Sì — decayElaborazione array
Array 2Dvoid f(int m[][3], int r)f(m, 2) Sì — decayElaborazione matrici
🖨 Scheda riassuntiva stampabile

Tutti e cinque i casi in una pagina sola — pronta per la stampa o il salvataggio in PDF dal dialogo di stampa del browser.

🔧 Simulatore passaggio parametri

Imposta un valore iniziale, scegli la modalità e osserva cosa succede alla variabile nel chiamante prima, durante e dopo la chiamata.

🔧 Simulatore Parametri Prima · Durante · Dopo
Valore iniziale di a
Operazione nella funzione
👤 Chiamante (main)
a = —
Prima della chiamata
→
chiama
⚙️ Dentro la funzione
x = —
—
👤 Chiamante — DOPO la chiamata
a = —
—
Scegli modalità, imposta il valore e premi Esegui.