Condividi tramite


Funzioni (C++)

Una funzione è un blocco di codice che esegue un'operazione. Una funzione può facoltativamente definire parametri di input che permettono ai chiamanti di passare argomenti nella funzione e può facoltativamente restituire un valore come output. Le funzioni sono utili per incapsulare operazioni comuni in un singolo blocco riutilizzabile, idealmente con un nome che descrive in modo chiaro ciò che la funzione permette di ottenere. La funzione seguente accetta due numeri interi da un chiamante e ne restituisce la somma; a e b sono parametri di tipo int.

int sum(int a, int b)
{
    return a + b;
}

La funzione può essere richiamata o chiamata da un numero qualsiasi di posizioni nel programma. I valori passati alla funzione sono gli argomenti, i cui tipi devono essere compatibili con i tipi di parametro nella definizione della funzione.

int main()
{
    int i = sum(10, 32);
    int j = sum(i, 66);
    cout << "The value of j is" << j << endl; // 108
}

Non esiste un limite pratico per la lunghezza delle funzioni, ma un buon design mira a funzioni che eseguono una singola attività ben definita. È consigliabile suddividere gli algoritmi complessi in funzioni più semplici e di facile comprensione, se possibile.

Le funzioni definite a livello di ambito della classe sono chiamate funzioni membro. In C++, a differenza degli altri linguaggi, una funzione può essere definita anche a livello di ambito dello spazio dei nomi, incluso lo spazio dei nomi globale implicito. Tali funzioni sono chiamate funzioni libere o funzioni non membro, che vengono usate ampiamente nella libreria standard.

Le funzioni possono essere sottoposte a overload, il che significa che versioni diverse di una funzione possono condividere lo stesso nome se differiscono in base al numero e/o al tipo di parametri formali. Per altre informazioni, vedere Overload delle funzioni.

Parti della dichiarazione di una funzione

Una dichiarazione di funzione minima è costituita dal tipo restituito, dal nome della funzione e dall'elenco di parametri (che può essere vuoto), insieme alle parole chiave facoltative che forniscono altre istruzioni al compilatore. L'esempio seguente è una dichiarazione di funzione:

int sum(int a, int b);

Una definizione di funzione è costituita da una dichiarazione, più il corpo, che è tutto il codice tra le parentesi graffe:

int sum(int a, int b)
{
    return a + b;
}

Una dichiarazione di funzione seguita da punto e virgola può apparire in più posizioni in un programma. Deve essere presente prima di qualsiasi chiamata a tale funzione in ogni unità di conversione. La definizione della funzione deve comparire solo una volta nel programma, in base alla regola ODR (One Definition Rule).

Le parti obbligatorie della dichiarazione di una funzione sono le seguenti:

  1. Tipo restituito, che specifica il tipo del valore restituito dalla funzione o void se non viene restituito alcun valore. In C++11 è auto un tipo restituito valido che indica al compilatore di dedurre il tipo dall'istruzione return. In C++14 decltype(auto) è consentito anche . Per altre informazioni, vedere Deduzione dei tipi nei tipi restituiti più avanti.

  2. Nome della funzione, che deve iniziare con una lettera o un carattere di sottolineatura e non può contenere spazi. In generale, i caratteri di sottolineatura iniziali nei nomi delle funzioni della libreria standard indicano funzioni membro private o funzioni non membro non destinate all'uso da parte del codice.

  3. L'elenco di parametri, un set di zero o più parametri racchiuso tra parentesi graffe e delimitato da virgole che specificano il tipo e, facoltativamente, un nome locale in base ai quali è possibile accedere ai valori entro il corpo della funzione.

Le parti facoltative della dichiarazione di una funzione sono le seguenti:

  1. constexpr, che indica che il valore restituito della funzione è un valore costante e può essere calcolato in fase di compilazione.

    constexpr float exp(float x, int n)
    {
        return n == 0 ? 1 :
            n % 2 == 0 ? exp(x * x, n / 2) :
            exp(x * x, (n - 1) / 2) * x;
    };
    
  2. Specifica di extern collegamento o static.

    //Declare printf with C linkage.
    extern "C" int printf( const char *fmt, ... );
    
    

    Per altre informazioni, vedere Unità di traduzione e collegamento.

  3. inline, che indica al compilatore di sostituire ogni chiamata alla funzione con il codice della funzione stessa. L'incorporamento può migliorare le prestazioni negli scenari in cui una funzione viene eseguita rapidamente e viene richiamata ripetutamente in una sezione di codice essenziale per le prestazioni.

    inline double Account::GetBalance()
    {
        return balance;
    }
    

    Per altre informazioni, vedere Funzioni inline.

  4. Espressione noexcept che specifica se la funzione può generare o meno un'eccezione. Nell'esempio seguente la funzione non genera un'eccezione se l'espressione is_pod restituisce true.

    #include <type_traits>
    
    template <typename T>
    T copy_object(T& obj) noexcept(std::is_pod<T>) {...}
    

    Per ulteriori informazioni, vedere noexcept.

  5. (solo funzioni membro) Qualificatori cv, che specificano se la funzione è const o volatile.

  6. (Solo funzioni membro) virtual, overrideo final. virtual specifica che una funzione può essere sottoposta a override in una classe derivata. override significa che una funzione in una classe derivata esegue l'override di una funzione virtuale. final indica che non è possibile eseguire l'override di una funzione in un'altra classe derivata. Per altre informazioni, vedere Funzioni virtuali.

  7. (solo funzioni membro) static applicato a una funzione membro significa che la funzione non è associata ad alcuna istanza di oggetto della classe .

  8. (solo funzioni membro non statiche) Qualificatore di riferimento, che specifica al compilatore l'overload di una funzione da scegliere quando il parametro dell'oggetto implicito (*this) è un riferimento rvalue rispetto a un riferimento lvalue. Per altre informazioni, vedere Overload delle funzioni.

Definizioni di funzione

Una definizione di funzione è costituita dalla dichiarazione e dal corpo della funzione, racchiuso tra parentesi graffe, che contiene dichiarazioni di variabili, istruzioni ed espressioni. L'esempio seguente mostra una definizione di funzione completa:

    int foo(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       if(strcmp(s, "default") != 0)
       {
            value = mc.do_something(i);
       }
       return value;
    }

Le variabili dichiarate all'interno del corpo sono definite variabili locali o locali. Escono dall'ambito al termine di una funzione. È quindi necessario che una funzione non restituisca mai un riferimento a una locale.

    MyClass& boom(int i, std::string s)
    {
       int value {i};
       MyClass mc;
       mc.Initialize(i,s);
       return mc;
    }

funzioni const e constexpr

È possibile dichiarare una funzione membro come const per specificare che la funzione non è autorizzata a modificare i valori di tutti i membri dati nella classe . Dichiarando una funzione membro come const, è possibile aiutare il compilatore a imporre la correttezza. Se un utente tenta erroneamente di modificare l'oggetto usando una funzione dichiarata come const, viene generato un errore del compilatore. Per altre informazioni, vedere const.

Dichiarare una funzione come constexpr quando il valore prodotto può essere determinato in fase di compilazione. Una funzione constexpr viene in genere eseguita più velocemente di una funzione regolare. Per ulteriori informazioni, vedere constexpr.

Modelli di funzioni

Un modello di funzione è analogo a un modello di classe. Genera funzioni concrete in base agli argomenti del modello. In molti casi, il modello è in grado di dedurre gli argomenti tipo e non è quindi necessario specificarli in modo esplicito.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs;
}

auto a = Add2(3.13, 2.895); // a is a double
auto b = Add2(string{ "Hello" }, string{ " World" }); // b is a std::string

Per altre informazioni, vedere Modelli di funzione

Parametri e argomenti delle funzioni

Una funzione ha un elenco di parametri delimitato da virgole che include zero o più tipi, ognuno dei quali ha un nome che ne permette l'accesso all'interno della funzione. Un modello di funzione può specificare più parametri di tipo o valore. Il chiamante passa gli argomenti, che sono valori concreti i cui tipi sono compatibili con l'elenco di parametri.

Per impostazione predefinita, gli argomenti vengono passati alla funzione per valore, ovvero la funzione riceve una copia dell'oggetto passato. Per oggetti di grandi dimensioni, la creazione di una copia può essere costosa e non è sempre necessaria. Per fare in modo che gli argomenti vengano passati per riferimento (specificamente riferimento lvalue), aggiungere un quantificatore di riferimento al parametro :

void DoSomething(std::string& input){...}

Quando una funzione modifica un argomento passato per riferimento, modifica l'oggetto originale, non una copia locale. Per impedire a una funzione di modificare tale argomento, qualificare il parametro come const&:

void DoSomething(const std::string& input){...}

C++11: per gestire in modo esplicito gli argomenti passati da rvalue-reference o lvalue-reference, usare una doppia e commerciale sul parametro per indicare un riferimento universale:

void DoSomething(const std::string&& input){...}

Una funzione dichiarata con la singola parola chiave void nell'elenco di dichiarazioni di parametri non accetta argomenti, purché la parola chiave void sia il primo e solo membro dell'elenco di dichiarazioni di argomenti. Gli argomenti di tipo void altrove nell'elenco generano errori. Ad esempio:

// OK same as GetTickCount()
long GetTickCount( void );

Anche se non è consentito specificare un void argomento, ad eccezione di quanto descritto qui, i tipi derivati dal tipo void (ad esempio i puntatori a void e le matrici di ) possono essere visualizzati in qualsiasi punto dell'elenco di dichiarazioni di voidargomenti.

Argomenti predefiniti

È possibile assegnare un argomento predefinito agli ultimi parametri nella firma di una funzione, per indicare che il chiamante può tralasciare l'argomento quando chiama la funzione, a meno che non voglia specificare un altro valore.

int DoSomething(int num,
    string str,
    Allocator& alloc = defaultAllocator)
{ ... }

// OK both parameters are at end
int DoSomethingElse(int num,
    string str = string{ "Working" },
    Allocator& alloc = defaultAllocator)
{ ... }

// C2548: 'DoMore': missing default parameter for parameter 2
int DoMore(int num = 5, // Not a trailing parameter!
    string str,
    Allocator& = defaultAllocator)
{...}

Per altre informazioni, vedere Argomenti predefiniti.

Tipi restituiti di funzione

Una funzione potrebbe non restituire un'altra funzione o una matrice predefinita; tuttavia può restituire puntatori a questi tipi o un'espressione lambda, che produce un oggetto funzione. Ad eccezione di questi casi, una funzione può restituire un valore di qualsiasi tipo incluso nell'ambito oppure non restituisce alcun valore, nel qual caso il tipo restituito è void.

Tipi restituiti finali

Un tipo restituito "normale" si trova sul lato sinistro della firma della funzione. Un tipo restituito finale si trova sul lato più a destra della firma ed è preceduto dall'operatore -> . I tipi restituiti finali sono particolarmente utili nei modelli di funzione, quando il tipo di valore restituito dipende dai parametri del modello.

template<typename Lhs, typename Rhs>
auto Add(const Lhs& lhs, const Rhs& rhs) -> decltype(lhs + rhs)
{
    return lhs + rhs;
}

Quando auto viene usato insieme a un tipo restituito finale, funge semplicemente da segnaposto per qualsiasi espressione decltype produce e non esegue la deduzione del tipo.

Variabili locali delle funzioni

Una variabile dichiarata all'interno di un corpo della funzione è detta variabile locale o semplicemente locale. Le variabili locali non statiche sono visibili solo all'interno del corpo della funzione e, se vengono dichiarate nello stack escono dall'ambito quando la funzione viene chiusa. Quando si crea una variabile locale e la si restituisce per valore, il compilatore può in genere eseguire l'ottimizzazione del valore restituito denominato per evitare operazioni di copia non necessarie. Se si restituisce una variabile locale per riferimento, il compilatore emetterà un avviso, poiché qualsiasi tentativo da parte del chiamante di usare tale riferimento si verificherà dopo l'eliminazione della locale.

In C++ una variabile locale può essere dichiarata come statica. La variabile è visibile solo all'interno del corpo della funzione, ma esiste una singola copia della variabile per tutte le istanze della funzione. Gli oggetti statici locali vengono eliminati definitivamente durante la chiusura specificata da atexit. Se un oggetto statico non è stato costruito perché il flusso del controllo del programma ha ignorato la dichiarazione, non viene effettuato alcun tentativo di distruggere l'oggetto.

Deduzione del tipo nei tipi restituiti (C++14)

In C++14 è possibile usare auto per indicare al compilatore di dedurre il tipo restituito dal corpo della funzione senza dover fornire un tipo restituito finale. Si noti che auto dedurre sempre un valore restituito per valore. Usare auto&& per indicare al compilatore di dedurre un riferimento.

In questo esempio, auto verrà dedotto come copia del valore non const della somma di lhs e rhs.

template<typename Lhs, typename Rhs>
auto Add2(const Lhs& lhs, const Rhs& rhs)
{
    return lhs + rhs; //returns a non-const object by value
}

Si noti che auto non mantiene la const-ness del tipo dedotto. Per le funzioni di inoltro il cui valore restituito deve mantenere la const-ness o ref-ness dei relativi argomenti, è possibile usare la decltype(auto) parola chiave , che usa le decltype regole di inferenza del tipo e mantiene tutte le informazioni sul tipo. decltype(auto) può essere usato come valore restituito ordinario sul lato sinistro o come valore restituito finale.

L'esempio seguente (basato sul codice di N3493), mostra decltype(auto) l'uso per abilitare l'inoltro perfetto degli argomenti della funzione in un tipo restituito non noto fino a quando non viene creata un'istanza del modello.

template<typename F, typename Tuple = tuple<T...>, int... I>
decltype(auto) apply_(F&& f, Tuple&& args, index_sequence<I...>)
{
    return std::forward<F>(f)(std::get<I>(std::forward<Tuple>(args))...);
}

template<typename F, typename Tuple = tuple<T...>,
    typename Indices = make_index_sequence<tuple_size<Tuple>::value >>
   decltype( auto)
    apply(F&& f, Tuple&& args)
{
    return apply_(std::forward<F>(f), std::forward<Tuple>(args), Indices());
}

Restituzione di più valori da una funzione

Esistono diversi modi per restituire più di un valore da una funzione:

  1. Incapsulare i valori in un oggetto struct o classe denominato. Richiede che la definizione della classe o dello struct sia visibile al chiamante:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        S s = g();
        cout << s.name << " " << s.num << endl;
        return 0;
    }
    
  2. Restituisce un oggetto std::tuple o std::p air:

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    
    int main()
    {
        auto t = f();
        cout << get<0>(t) << " " << get<1>(t) << " " << get<2>(t) << endl;
    
        // --or--
    
        int myval;
        string myname;
        double mydecimal;
        tie(myval, myname, mydecimal) = f();
        cout << myval << " " << myname << " " << mydecimal << endl;
    
        return 0;
    }
    
  3. Visual Studio 2017 versione 15.3 e successive (disponibile in /std:c++17 modalità e versioni successive): usare associazioni strutturate. Il vantaggio delle associazioni strutturate è che le variabili che archiviano i valori restituiti vengono inizializzate contemporaneamente vengono dichiarate, che in alcuni casi possono essere notevolmente più efficienti. Nell'istruzione auto[x, y, z] = f(); le parentesi quadre introducono e inizializzano i nomi inclusi nell'ambito per l'intero blocco di funzioni.

    #include <tuple>
    #include <string>
    #include <iostream>
    
    using namespace std;
    
    tuple<int, string, double> f()
    {
        int i{ 108 };
        string s{ "Some text" };
        double d{ .01 };
        return { i,s,d };
    }
    struct S
    {
        string name;
        int num;
    };
    
    S g()
    {
        string t{ "hello" };
        int u{ 42 };
        return { t, u };
    }
    
    int main()
    {
        auto[x, y, z] = f(); // init from tuple
        cout << x << " " << y << " " << z << endl;
    
        auto[a, b] = g(); // init from POD struct
        cout << a << " " << b << endl;
        return 0;
    }
    
  4. Oltre a usare il valore restituito stesso, è possibile "restituire" valori definendo un numero qualsiasi di parametri da usare pass-by-reference in modo che la funzione possa modificare o inizializzare i valori degli oggetti forniti dal chiamante. Per altre informazioni, vedere Argomenti della funzione reference-type.

Puntatori funzione

C++ supporta i puntatori di funzione in modo analogo al linguaggio C. Un'alternativa più indipendente dai tipi consiste in genere nell'usare un oggetto di funzione.

È consigliabile usare typedef per dichiarare un alias per il tipo di puntatore di funzione se dichiara una funzione che restituisce un tipo di puntatore a funzione. Ad esempio:

typedef int (*fp)(int);
fp myFunction(char* s); // function returning function pointer

In caso contrario, è possibile dedurre la sintassi corretta per la dichiarazione di funzione dalla sintassi del dichiaratore per il puntatore alla funzione sostituendo l'identificatore (fp nell'esempio precedente) con il nome delle funzioni e l'elenco di argomenti, come indicato di seguito:

int (*myFunction(char* s))(int);

La dichiarazione precedente equivale alla dichiarazione che usa typedef in precedenza.

Vedi anche

Overload di funzioni
Funzioni con elenchi di argomenti variabili
Funzioni impostate come predefinite ed eliminate in modo esplicito
Ricerca del nome dipendente dall'argomento nelle funzioni (Koenig)
Argomenti predefiniti
Funzioni inline