Argomenti di default delle Funzioni in C++

Concetti Chiave
  • Gli argomenti di default delle funzioni in C++ permettono di specificare valori predefiniti per i parametri di una funzione, consentendo chiamate più flessibili.
  • Gli argomenti di default devono essere specificati nell'ordine corretto: se un parametro ha un argomento di default, tutti i parametri successivi devono anch'essi avere argomenti di default.
  • Gli argomenti di default sono risolti per posizione durante la chiamata della funzione.
  • Le variabili locali non possono essere usate come argomenti di default; gli argomenti di default sono valutati nello scope della dichiarazione della funzione.

Argomenti di Default di una Funzione

Alcune funzioni hanno parametri che ricevono un valore particolare nella maggior parte delle chiamate. In tali casi, possiamo dichiarare quel valore comune come un argomento predefinito o argomento di default per la funzione. Le funzioni con argomenti predefiniti possono essere chiamate con o senza quell'argomento.

Ad esempio, supponiamo di realizzare una funzione che mostra a schermo una finestra. Per creare la finestra, dobbiamo passare alla funzione l'altezza, la larghezza e, magari, il titolo della finestra.

Potremmo usare una string per rappresentare il titolo della finestra. Per default, potremmo volere che la finestra abbia un'altezza, larghezza e titolo particolari. Tuttavia, potremmo anche volere permettere agli utenti di passare valori diversi da quelli predefiniti. Per gestire sia i valori predefiniti che quelli specificati dichiareremmo la nostra funzione per definire la finestra come segue:

Finestra creaFinestra(unsigned int alt = 480,
                      unsigned int larg = 680,
                      string titolo = "Finestra");

La funzione creaFinestra permette di creare una finestra e la restituisce sotto forma di un oggetto ipotetico di tipo Finestra. La funzione accetta tre parametri: alt (altezza), larg (larghezza) e titolo (titolo della finestra). Ogni parametro ha un valore predefinito specificato dopo il segno di uguale (=). Se l'utente non fornisce un valore per uno o più di questi parametri durante la chiamata della funzione, i valori predefiniti verranno utilizzati. Quindi, di default, la funzione creerà una finestra di 480 pixel di altezza, 680 pixel di larghezza e con il titolo "Finestra".

Un argomento predefinito è specificato come un inizializzatore per un parametro nella lista dei parametri. Possiamo definire default per uno o più parametri. Tuttavia, se un parametro ha un argomento predefinito, tutti i parametri che lo seguono devono anche avere argomenti predefiniti. In altre parole, non possiamo avere un parametro senza default dopo uno con default. Ad esempio, la seguente dichiarazione non è valida:

// ERRORE: larg non ha un argomento predefinito
Finestra creaFinestra(unsigned int alt = 480,
                      unsigned int larg,            // ERRORE
                      string titolo = "Finestra");

Chiamare Funzioni con Argomenti Predefiniti

Se vogliamo usare l'argomento predefinito, omettiamo quell'argomento quando chiamiamo la funzione. Poiché creaFinestra fornisce default per tutti i suoi parametri, possiamo chiamare creaFinestra con zero, uno, due, o tre argomenti:

Finestra finestra;

// equivalente a creaFinestra(480,680,"Finestra")
finestra = creaFinestra();

// equivalente a creaFinestra(500,680,"Finestra")
finestra = creaFinestra(500);

// equivalente a creaFinestra(500,256,"Finestra")
finestra = creaFinestra(500, 256);

// equivalente a creaFinestra(500,256,"Ciao")
finestra = creaFinestra(500, 256, "Ciao");

Gli argomenti nella chiamata sono risolti per posizione. Gli argomenti predefiniti sono usati per gli argomenti finali (più a destra) di una chiamata. Ad esempio, per sovrascrivere il titolo di default, dobbiamo anche fornire argomenti per alt e larg:

// ERRORE: possiamo omettere solo gli argomenti finali
finestra = creaFinestra(, , "MioTitolo");

// ERRORE: stiamo passando una stringa al parametro altezza
// che invece si aspetta un valore di tipo unsigned int
finestra = creaFinestra("MioTitolo");

Parte del lavoro di progettare una funzione con argomenti predefiniti è ordinare i parametri in modo che quelli meno probabili ad avere un valore predefinito appaiano per primi e quelli più probabili ad usare un default appaiano per ultimi.

Dichiarazioni di Argomenti Predefiniti

Benché sia pratica comune dichiarare una funzione una volta all'interno di un header, è legale dichiarare nuovamente una funzione più volte. Tuttavia, ogni parametro può avere il suo default specificato solo una volta in un dato scope. Quindi, qualsiasi dichiarazione successiva può aggiungere un default solo per un parametro che non ha precedentemente avuto un default specificato. Come al solito, i default possono essere specificati solo se tutti i parametri a destra hanno già default. Ad esempio, dato

// Nessun default per i parametri altezza o larghezza
Finestra creaFinestra(unsigned int alt,
                      unsigned int larg,
                      string titolo = "Finestra");

non possiamo cambiare un valore default già dichiarato:

// ERRORE: titolo ha già un default
Finestra creaFinestra(unsigned int alt,
                      unsigned int larg,
                      string titolo = "Default");

ma possiamo aggiungere un argomento predefinito come segue:

// OK: aggiunge un default per larg e alt
Finestra creaFinestra(unsigned int alt = 480,
                      unsigned int larg = 640,
                      string titolo);

Gli argomenti predefiniti normalmente dovrebbero essere specificati con la dichiarazione della funzione in un header appropriato.

Inizializzatori di Argomenti Predefiniti

Le variabili locali non possono essere usate come argomento predefinito. Eccetto questa restrizione, un argomento predefinito può essere qualsiasi espressione che ha un tipo che è convertibile al tipo del parametro:

// OK: le dichiarazioni di LARG, ALT e TITOLO sono nello scope globale
unsigned int LARG = 640;
unsigned int alt();
std::string TITOLO = "Finestra";

Finestra creaFinestra(unsigned int alt = alt(),
                      unsigned int larg = LARG,
                      string titolo = TITOLO);

I nomi usati come argomenti predefiniti sono risolti nello scope della dichiarazione della funzione. Il valore che quei nomi rappresentano è valutato al momento della chiamata:

void f2()
{
    // cambia il valore di un argomento predefinito
    TITOLO = "NuovoTitolo";

    // nasconde la definizione esterna di LARG ma non cambia il default
    unsigned int LARG = 100;

    // chiama creaFinestra(alt(), 100, "NuovoTitolo")
    finestra = creaFinestra();
}

All'interno di f2, abbiamo cambiato il valore di TITOLO. La chiamata a creaFinestra passa questo valore aggiornato. La nostra funzione ha anche dichiarato una variabile locale che nasconde la variabile LARG esterna. Tuttavia, la variabile LARG locale nominata non è correlata all'argomento predefinito passato a creaFinestra.

Esercizi

  • Quale, se qualcuna, delle seguenti dichiarazioni contiene errori? Perché?

    int ff(int a, int b = 0, int c = 0);
    char *init(int alt = 24, int larg, Colore sfondo);
    

    Risposta: La seconda dichiarazione contiene un errore perché il parametro larg non ha un argomento predefinito, ma segue un parametro (alt) che ne ha uno. Tutti i parametri successivi a un parametro con argomento predefinito devono anch'essi avere argomenti predefiniti. La prima, invece, è corretta.

  • Quale, se qualcuna, delle seguenti chiamate sono illegali? Perché? Quali sono legali ma non corrispondono alle intenzioni dello sviluppatore? Perché?

    char *init(int alt, int larg = 80, Colore sfondo = Colore::Nero);
    
    init();
    init(24, 10);
    init(14, '*');
    

    Risposta: La prima chiamata init(); è illegale perché manca l'argomento obbligatorio alt. La seconda chiamata init(24, 10); è legale e corrisponde alle intenzioni dello sviluppatore, poiché fornisce un valore per alt e larg, utilizzando il valore predefinito per sfondo. La terza chiamata init(14, '*');, sebbene legale, non corrisponde alle intenzioni dello sviluppatore perché il secondo argomento '*' non è del tipo previsto per larg, che dovrebbe essere un intero. In altre parole, il tipo dell'argomento non corrisponde al tipo del parametro, tuttavia il char può essere implicitamente convertito in un int, quindi la chiamata è legale ma potrebbe non riflettere l'intento originale del programmatore.